diff --git a/alpine/distributionscanner.go b/alpine/distributionscanner.go index 1f95bc020..b9179c085 100644 --- a/alpine/distributionscanner.go +++ b/alpine/distributionscanner.go @@ -6,12 +6,11 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "regexp" "runtime/trace" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/osrelease" @@ -61,12 +60,10 @@ func (*DistributionScanner) Kind() string { return scannerKind } // If the files are found but all regexp fail to match an empty slice is returned. func (s *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "alpine/DistributionScanner.Scan", + slog.DebugContext(ctx, "start", "version", s.Version(), "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + defer slog.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { return nil, fmt.Errorf("alpine: unable to open layer: %w", err) @@ -102,13 +99,13 @@ func readOSRelease(ctx context.Context, sys fs.FS) (*claircore.Distribution, err return nil, err } if id := m[`ID`]; id != `alpine` { - zlog.Debug(ctx).Str("id", id).Msg("seemingly not alpine") + slog.DebugContext(ctx, "seemingly not alpine", "id", id) break } vid := m[`VERSION_ID`] idx := strings.LastIndexByte(vid, '.') if idx == -1 { - zlog.Debug(ctx).Str("val", vid).Msg("martian VERSION_ID") + slog.DebugContext(ctx, "martian VERSION_ID", "val", vid) break } v := vid[:idx] @@ -126,9 +123,9 @@ func readOSRelease(ctx context.Context, sys fs.FS) (*claircore.Distribution, err PrettyName: m[`PRETTY_NAME`], }, nil case errors.Is(err, fs.ErrNotExist): - zlog.Debug(ctx). - Str("path", osrelease.Path). - Msg("file doesn't exist") + slog.DebugContext(ctx, + "file doesn't exist", + "path", osrelease.Path) default: return nil, err } @@ -153,7 +150,7 @@ func readIssue(ctx context.Context, sys fs.FS) (*claircore.Distribution, error) ms := issueRegexp.FindSubmatch(b) if ms == nil { - zlog.Debug(ctx).Msg("seemingly not alpine") + slog.DebugContext(ctx, "seemingly not alpine") break } v := string(ms[1]) @@ -164,9 +161,9 @@ func readIssue(ctx context.Context, sys fs.FS) (*claircore.Distribution, error) PrettyName: fmt.Sprintf(`Alpine Linux v%s`, v), }, nil case errors.Is(err, fs.ErrNotExist): - zlog.Debug(ctx). - Str("path", issuePath). - Msg("file doesn't exist") + slog.DebugContext(ctx, + "file doesn't exist", + "path", issuePath) default: return nil, err } diff --git a/alpine/distributionscanner_test.go b/alpine/distributionscanner_test.go index b21adc1e3..50a8fb20a 100644 --- a/alpine/distributionscanner_test.go +++ b/alpine/distributionscanner_test.go @@ -8,14 +8,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" + "github.com/quay/claircore/test" ) func TestScanFs(t *testing.T) { - ctx := context.Background() - ctx = zlog.Test(ctx, t) + ctx := test.Logging(t) td := os.DirFS("testdata") ms, err := fs.Glob(td, "3.*") @@ -46,7 +45,6 @@ func scanFsTestcase(ctx context.Context, sys fs.FS) func(*testing.T) { if err != nil { t.Fatal(err) } - ctx := zlog.Test(ctx, t) got, err := s.scanFs(ctx, sys) if err != nil { t.Error(err) diff --git a/alpine/fetcher.go b/alpine/fetcher.go index f496ab94a..717172352 100644 --- a/alpine/fetcher.go +++ b/alpine/fetcher.go @@ -4,27 +4,24 @@ import ( "context" "fmt" "io" + "log/slog" "net/http" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/pkg/tmp" ) func (u *updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "alpine/Updater.Fetch") - - zlog.Info(ctx).Str("database", u.url).Msg("starting fetch") + slog.InfoContext(ctx, "starting fetch", "database", u.url) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.url, nil) if err != nil { return nil, hint, fmt.Errorf("alpine: unable to construct request: %w", err) } if hint != "" { - zlog.Debug(ctx). - Str("hint", string(hint)). - Msg("using hint") + slog.DebugContext(ctx, + "using hint", + "hint", string(hint)) req.Header.Set("if-none-match", string(hint)) } @@ -41,25 +38,27 @@ func (u *updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCl } fallthrough case http.StatusNotModified: - zlog.Info(ctx).Msg("database unchanged since last fetch") + slog.InfoContext(ctx, "database unchanged since last fetch") return nil, hint, driver.Unchanged default: return nil, hint, fmt.Errorf("alpine: http response error: %s %d", res.Status, res.StatusCode) } - zlog.Debug(ctx).Msg("successfully requested database") + slog.DebugContext(ctx, "successfully requested database") tf, err := tmp.NewFile("", u.Name()+".") if err != nil { return nil, hint, fmt.Errorf("alpine: unable to open tempfile: %w", err) } - zlog.Debug(ctx). - Str("name", tf.Name()). - Msg("created tempfile") + slog.DebugContext(ctx, + "created tempfile", + "name", tf.Name()) var success bool defer func() { if !success { if err := tf.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + slog.WarnContext(ctx, + "unable to close spool", + "reason", err) } } }() @@ -71,13 +70,13 @@ func (u *updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCl if n, err := tf.Seek(0, io.SeekStart); err != nil || n != 0 { return nil, hint, fmt.Errorf("alpine: unable to seek database to start: at %d, %v", n, err) } - zlog.Debug(ctx).Msg("decompressed and buffered database") + slog.DebugContext(ctx, "decompressed and buffered database") success = true hint = driver.Fingerprint(res.Header.Get("etag")) - zlog.Debug(ctx). - Str("hint", string(hint)). - Msg("using new hint") + slog.DebugContext(ctx, + "using new hint", + "hint", string(hint)) return tf, hint, nil } diff --git a/alpine/fetcher_test.go b/alpine/fetcher_test.go index aa2b93e45..5388fc669 100644 --- a/alpine/fetcher_test.go +++ b/alpine/fetcher_test.go @@ -1,13 +1,13 @@ package alpine import ( - "context" "net/http" "net/http/httptest" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" + + "github.com/quay/claircore/test" ) func serveSecDB(t *testing.T) (string, *http.Client) { @@ -17,7 +17,8 @@ func serveSecDB(t *testing.T) (string, *http.Client) { } func TestFactory(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) + root, c := serveSecDB(t) fac := &Factory{} err := fac.Configure(ctx, func(v any) error { diff --git a/alpine/parser.go b/alpine/parser.go index 015faae33..b0d6bf400 100644 --- a/alpine/parser.go +++ b/alpine/parser.go @@ -4,8 +4,7 @@ import ( "context" "encoding/json" "io" - - "github.com/quay/zlog" + "log/slog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" @@ -18,8 +17,8 @@ const ( var _ driver.Parser = (*updater)(nil) func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "alpine/Updater.Parse") - zlog.Info(ctx).Msg("starting parse") + slog.InfoContext(ctx, "parse start") + defer slog.InfoContext(ctx, "parse done") defer r.Close() var db SecurityDB diff --git a/alpine/parser_test.go b/alpine/parser_test.go index 2ecc05df7..4de7777d7 100644 --- a/alpine/parser_test.go +++ b/alpine/parser_test.go @@ -8,9 +8,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" + "github.com/quay/claircore/test" ) var dist310 = stableRelease{3, 10}.Distribution() @@ -128,8 +128,8 @@ var v3_10CommunityTruncatedVulns = []*claircore.Vulnerability{ func TestParser(t *testing.T) { t.Parallel() - ctx := context.Background() - var table = []struct { + ctx := test.Logging(t) + table := []struct { release release repo string testFile string @@ -145,7 +145,8 @@ func TestParser(t *testing.T) { for _, test := range table { t.Run(test.testFile, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx, done := context.WithCancel(ctx) + t.Cleanup(done) path := fmt.Sprintf("testdata/%s", test.testFile) f, err := os.Open(path) diff --git a/alpine/purl.go b/alpine/purl.go new file mode 100644 index 000000000..527a57221 --- /dev/null +++ b/alpine/purl.go @@ -0,0 +1,82 @@ +package alpine + +import ( + "context" + "strconv" + "strings" + + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for Alpine APKs. + PURLType = "apk" + // PURLNamespace is the namespace of Alpine APKs. + PURLNamespace = "alpine" + // PURLDistroQualifier is the qualifier key for the distribution. + PURLDistroQualifier = "distro" +) + +// GeneratePURL generates a PURL for an Alpine APK package in the format: +// pkg:apk/alpine/@?arch=&distro=- +func GeneratePURL(ctx context.Context, r *claircore.IndexRecord) (packageurl.PackageURL, error) { + var distro string + if r.Distribution != nil { + distro = r.Distribution.Name + "-" + r.Distribution.Version + } + return packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: r.Package.Name, + Version: r.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": r.Package.Arch, + PURLDistroQualifier: distro, + }), + }, nil +} + +// ParsePURL parses a PURL for an Alpine APK package into a list of IndexRecords. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + d := distoToDistribution(purl.Qualifiers.Map()[PURLDistroQualifier]) + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Kind: claircore.BINARY, + Arch: purl.Qualifiers.Map()["arch"], + }, + Distribution: d, + }, + }, nil +} + +// DistributionFromPURL converts a PURL string to a *claircore.Distribution. +// distro strings are expected to be in the form "alpine-". +// The distro format is discussed here: https://github.com/package-url/purl-spec/issues/423 +func distoToDistribution(distro string) *claircore.Distribution { + // split the distro string into name and version + d := strings.Split(distro, "-") + if d[1] == edgeDist.VersionID { + return edgeDist + } + v := strings.Split(d[1], ".") + if len(v) < 2 { + // There are some cases where the version is 3 parts but the patch doesn't + // influence addressability so we can ignore it. + return nil + } + maj, err := strconv.Atoi(v[0]) + if err != nil { + return nil + } + min, err := strconv.Atoi(v[1]) + if err != nil { + return nil + } + dist := stableRelease{maj, min}.Distribution() + return dist +} diff --git a/alpine/purl_test.go b/alpine/purl_test.go new file mode 100644 index 000000000..c3d5cbcef --- /dev/null +++ b/alpine/purl_test.go @@ -0,0 +1,86 @@ +package alpine + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordAlpine(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "busybox", + Version: "1.36.1-r0", + Arch: "x86_64", + Kind: claircore.BINARY, + PackageDB: "apk:/busybox", + Filepath: "/bin/busybox", + }, + Distribution: &claircore.Distribution{ + Name: "Alpine Linux", + PrettyName: "Alpine Linux v3.18", + Version: "3.18", + DID: "alpine", + }, + }, + }, + { + name: "edge", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "busybox", + Version: "1.36.1-r0", + Arch: "x86_64", + Kind: claircore.BINARY, + PackageDB: "apk:/busybox", + Filepath: "/bin/busybox", + }, + Distribution: &claircore.Distribution{ + Name: "Alpine Linux", + PrettyName: "Alpine Linux edge", + Version: "edge", + DID: "alpine", + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParsePURL: %v", err) + } + if diff := cmp.Diff([]*claircore.IndexRecord{tc.ir}, got, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-want +got):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore PackageDB and Filepath as they are not currently used in the matching. + cmpopts.IgnoreFields(claircore.Package{}, "PackageDB", "Filepath"), + // The version is what we save when indexing, the versionID is what we parse + // from the vulnerability database. Neither are used in the matching, the DID, + // DistributionName, and DistributionPrettyName are used. + cmpopts.IgnoreFields(claircore.Distribution{}, "VersionID", "Version"), +} diff --git a/alpine/secdb_test.go b/alpine/secdb_test.go index f0e3cb749..51ba73c95 100644 --- a/alpine/secdb_test.go +++ b/alpine/secdb_test.go @@ -48,7 +48,7 @@ var v3_10CommunityTruncatedSecDB = SecurityDB{ } func TestSecDBParse(t *testing.T) { - var table = []struct { + table := []struct { testFile string expected SecurityDB }{ diff --git a/alpine/updater.go b/alpine/updater.go index d8c7c4371..8e444da7d 100644 --- a/alpine/updater.go +++ b/alpine/updater.go @@ -4,14 +4,13 @@ import ( "bytes" "context" "fmt" + "log/slog" "net/http" "net/url" "path" "strings" "sync" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" ) @@ -60,11 +59,10 @@ func NewFactory(_ context.Context) (*Factory, error) { // UpdaterSet implements driver.UpdaterSetFactory. func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { - ctx = zlog.ContextWithValues(ctx, "component", "alpine/Factory.UpdaterSet") s := driver.NewUpdaterSet() if f.c == nil { - zlog.Info(ctx). - Msg("unconfigured") + slog.InfoContext(ctx, + "unconfigured") return s, nil } @@ -81,9 +79,9 @@ func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { if f.etag != "" { req.Header.Set(`if-none-match`, f.etag) } - zlog.Debug(ctx). - Stringer("url", u). - Msg("making request") + slog.DebugContext(ctx, + "making request", + "url", u) res, err := f.c.Do(req) if err != nil { return s, fmt.Errorf("alpine: error requesting %q: %w", u.String(), err) @@ -91,9 +89,9 @@ func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { defer res.Body.Close() switch res.StatusCode { case http.StatusNotModified: - zlog.Debug(ctx). - Stringer("url", u). - Msg("not modified") + slog.DebugContext(ctx, + "not modified", + "url", u) return f.cur, nil case http.StatusOK: default: @@ -127,12 +125,12 @@ Major: if err != nil { return s, fmt.Errorf("alpine: unable to construct request: %w", err) } - ctx := zlog.ContextWithValues(ctx, "url", u.String(), "release", r.String()) + l := slog.With("url", u, "release", r) req, err := http.NewRequestWithContext(ctx, http.MethodHead, u.String(), nil) if err != nil { return s, fmt.Errorf("alpine: unable to construct request: %w", err) } - zlog.Debug(ctx).Msg("checking release") + l.DebugContext(ctx, "checking release") res, err := f.c.Do(req) if err != nil { return s, fmt.Errorf("alpine: error requesting %q: %w", u.String(), err) @@ -143,13 +141,13 @@ Major: foundLower = true todo = append(todo, r) case http.StatusNotFound: - zlog.Debug(ctx).Msg("not found") + l.DebugContext(ctx, "not found") if foundLower { break Minor } break Major default: - zlog.Info(ctx).Str("status", res.Status).Msg("unexpected status reported") + l.InfoContext(ctx, "unexpected status reported", "status", res.Status) } } } @@ -159,12 +157,12 @@ Major: if err != nil { return s, fmt.Errorf("alpine: unable to construct request: %w", err) } - ctx := zlog.ContextWithValues(ctx, "url", u.String(), "release", r.String(), "repo", n) + l := slog.With("url", u, "release", r, "repo", n) req, err := http.NewRequestWithContext(ctx, http.MethodHead, u.String(), nil) if err != nil { return s, fmt.Errorf("alpine: unable to construct request: %w", err) } - zlog.Debug(ctx).Msg("checking repository") + l.DebugContext(ctx, "checking repository") res, err := f.c.Do(req) if err != nil { return s, fmt.Errorf("alpine: error requesting %q: %w", u.String(), err) @@ -172,12 +170,12 @@ Major: res.Body.Close() switch res.StatusCode { case http.StatusOK: - zlog.Debug(ctx).Msg("found") + l.DebugContext(ctx, "found") case http.StatusNotFound: - zlog.Debug(ctx).Msg("not found") + l.DebugContext(ctx, "not found") continue default: - zlog.Info(ctx).Str("status", res.Status).Msg("unexpected status reported") + l.InfoContext(ctx, "unexpected status reported", "status", res.Status) continue } s.Add(&updater{ @@ -247,10 +245,9 @@ func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c * } if cfg.URL != "" { u.url = cfg.URL - zlog.Info(ctx). - Str("component", "alpine/Updater.Configure"). - Str("updater", u.Name()). - Msg("configured url") + slog.InfoContext(ctx, + "configured url", + "updater", u.Name()) } u.client = c return nil diff --git a/apk/scanner.go b/apk/scanner.go index 519e2000d..3ad6e2d01 100644 --- a/apk/scanner.go +++ b/apk/scanner.go @@ -6,10 +6,9 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -51,13 +50,12 @@ func (*Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore. } defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "apk/Scanner.Scan", + log := slog.With( "version", version, "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + log.DebugContext(ctx, "start") + defer log.DebugContext(ctx, "done") sys, err := layer.FS() if err != nil { @@ -71,7 +69,7 @@ func (*Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore. default: return nil, err } - zlog.Debug(ctx).Msg("found database") + log.DebugContext(ctx, "found database") pkgs := []*claircore.Package{} srcs := make(map[string]*claircore.Package) @@ -118,7 +116,7 @@ func (*Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore. } pkgs = append(pkgs, &p) } - zlog.Debug(ctx).Int("count", len(pkgs)).Msg("found packages") + log.DebugContext(ctx, "found packages", "count", len(pkgs)) return pkgs, nil } diff --git a/apk/scanner_test.go b/apk/scanner_test.go index 21009b995..7fc0c8df8 100644 --- a/apk/scanner_test.go +++ b/apk/scanner_test.go @@ -1,11 +1,9 @@ package apk import ( - "context" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" @@ -13,7 +11,7 @@ import ( func TestScan(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) // TODO(hank) Turn into a fixture. want := []*claircore.Package{ { diff --git a/aws/client.go b/aws/client.go index 7e1de3575..511c33464 100644 --- a/aws/client.go +++ b/aws/client.go @@ -7,14 +7,13 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "path" "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore/aws/internal/alas" "github.com/quay/claircore/internal/xmlutil" "github.com/quay/claircore/pkg/tmp" @@ -29,16 +28,17 @@ const ( // Client is an http for accessing ALAS mirrors. type Client struct { c *http.Client + log *slog.Logger mirrors []*url.URL } func NewClient(ctx context.Context, hc *http.Client, release Release) (*Client, error) { - ctx = zlog.ContextWithValues(ctx, "release", string(release)) if hc == nil { return nil, errors.New("http.Client not provided") } client := &Client{ c: hc, + log: slog.With("release", string(release)), mirrors: []*url.URL{}, } tctx, cancel := context.WithTimeout(ctx, defaultOpTimeout) @@ -49,22 +49,22 @@ func NewClient(ctx context.Context, hc *http.Client, release Release) (*Client, // RepoMD returns a alas.RepoMD containing sha256 information of a repositories contents func (c *Client) RepoMD(ctx context.Context) (alas.RepoMD, error) { - ctx = zlog.ContextWithValues(ctx, "component", "aws/Client.RepoMD") + log := c.log for _, mirror := range c.mirrors { m := *mirror m.Path = path.Join(m.Path, repoDataPath) - ctx := zlog.ContextWithValues(ctx, "mirror", m.String()) + log := log.With("mirror", m.String()) req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.String(), nil) if err != nil { - zlog.Error(ctx).Err(err).Msg("failed to make request object") + log.ErrorContext(ctx, "failed to make request object", "reason", err) continue } - zlog.Debug(ctx).Msg("attempting repomd download") + log.DebugContext(ctx, "attempting repomd download") resp, err := c.c.Do(req) if err != nil { - zlog.Error(ctx).Err(err).Msg("failed to retrieve repomd") + log.ErrorContext(ctx, "failed to retrieve repomd", "reason", err) continue } defer resp.Body.Close() @@ -73,10 +73,9 @@ func (c *Client) RepoMD(ctx context.Context) (alas.RepoMD, error) { case http.StatusOK: // break default: - zlog.Error(ctx). - Int("code", resp.StatusCode). - Str("status", resp.Status). - Msg("unexpected HTTP response") + log.ErrorContext(ctx, "unexpected HTTP response", + "code", resp.StatusCode, + "status", resp.Status) continue } @@ -84,51 +83,49 @@ func (c *Client) RepoMD(ctx context.Context) (alas.RepoMD, error) { dec := xml.NewDecoder(resp.Body) dec.CharsetReader = xmlutil.CharsetReader if err := dec.Decode(&repoMD); err != nil { - zlog.Error(ctx). - Err(err). - Msg("failed xml unmarshal") + log.ErrorContext(ctx, "failed xml unmarshal", "reason", err) continue } - zlog.Debug(ctx).Msg("success") + log.DebugContext(ctx, "success") return repoMD, nil } - zlog.Error(ctx).Msg("exhausted all mirrors") + log.ErrorContext(ctx, "exhausted all mirrors") return alas.RepoMD{}, fmt.Errorf("all mirrors failed to retrieve repo metadata") } // Updates returns the *http.Response of the first mirror to establish a connection func (c *Client) Updates(ctx context.Context) (io.ReadCloser, error) { - ctx = zlog.ContextWithValues(ctx, "component", "aws/Client.Updates") + log := c.log for _, mirror := range c.mirrors { m := *mirror m.Path = path.Join(m.Path, updatesPath) - ctx := zlog.ContextWithValues(ctx, "mirror", m.String()) + log := log.With("mirror", m.String()) req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.String(), nil) if err != nil { - zlog.Error(ctx).Err(err).Msg("failed to make request object") + log.ErrorContext(ctx, "failed to make request object", "reason", err) continue } tf, err := tmp.NewFile("", "") if err != nil { - zlog.Error(ctx).Err(err).Msg("failed to open temp file") + log.ErrorContext(ctx, "failed to open temp file", "reason", err) continue } var success bool defer func() { if !success { if err := tf.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + log.WarnContext(ctx, "unable to close spool", "reason", err) } } }() resp, err := c.c.Do(req) if err != nil { - zlog.Error(ctx).Err(err).Msg("failed to retrieve updates") + log.ErrorContext(ctx, "failed to retrieve updates", "reason", err) continue } defer resp.Body.Close() @@ -137,10 +134,9 @@ func (c *Client) Updates(ctx context.Context) (io.ReadCloser, error) { case http.StatusOK: // break default: - zlog.Error(ctx). - Int("code", resp.StatusCode). - Str("status", resp.Status). - Msg("unexpected HTTP response") + log.ErrorContext(ctx, "unexpected HTTP response", + "code", resp.StatusCode, + "status", resp.Status) continue } @@ -155,7 +151,7 @@ func (c *Client) Updates(ctx context.Context) (io.ReadCloser, error) { return nil, fmt.Errorf("failed to create gzip reader: %v", err) } - zlog.Debug(ctx).Msg("success") + log.DebugContext(ctx, "success") success = true return &gzippedFile{ Reader: gz, @@ -163,7 +159,7 @@ func (c *Client) Updates(ctx context.Context) (io.ReadCloser, error) { }, nil } - zlog.Error(ctx).Msg("exhausted all mirrors") + log.ErrorContext(ctx, "exhausted all mirrors") return nil, fmt.Errorf("all update_info mirrors failed to return a response") } @@ -176,7 +172,7 @@ type gzippedFile struct { } func (c *Client) getMirrors(ctx context.Context, list string) error { - ctx = zlog.ContextWithValues(ctx, "component", "aws/Client.getMirrors") + log := c.log req, err := http.NewRequestWithContext(ctx, http.MethodGet, list, nil) if err != nil { @@ -215,7 +211,6 @@ func (c *Client) getMirrors(ctx context.Context, list string) error { c.mirrors = append(c.mirrors, uu) } - zlog.Debug(ctx). - Msg("successfully got list of mirrors") + log.DebugContext(ctx, "successfully got list of mirrors") return nil } diff --git a/aws/client_test.go b/aws/client_test.go index b5f83a4a4..d1106ca31 100644 --- a/aws/client_test.go +++ b/aws/client_test.go @@ -2,13 +2,14 @@ package aws import ( "context" + "log/slog" "net/http" "net/http/httptest" "net/url" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) type clientTestcase struct { @@ -31,11 +32,12 @@ func (tc *clientTestcase) Run(ctx context.Context) func(*testing.T) { })) client := Client{ c: srv.Client(), + log: slog.Default(), mirrors: make([]*url.URL, 0), } return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) t.Cleanup(srv.Close) err := client.getMirrors(ctx, srv.URL) @@ -48,8 +50,9 @@ func (tc *clientTestcase) Run(ctx context.Context) func(*testing.T) { } } } + func TestClientGetMirrors(t *testing.T) { - ctx := context.Background() + ctx := test.Logging(t) tests := []clientTestcase{ { Release: AmazonLinux1, diff --git a/aws/distributionscanner.go b/aws/distributionscanner.go index ca4ac7fef..b237cfd16 100644 --- a/aws/distributionscanner.go +++ b/aws/distributionscanner.go @@ -3,11 +3,10 @@ package aws import ( "bytes" "context" + "log/slog" "regexp" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -71,21 +70,16 @@ func (*DistributionScanner) Kind() string { return scannerKind } // If the files are found but all regexp fail to match an empty slice is returned. func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "aws_dist_scanner", - "name", ds.Name(), - "version", ds.Version(), - "kind", ds.Kind(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + log := slog.With("layer", l.Hash.String(), "version", ds.Version()) + log.DebugContext(ctx, "start") + defer log.DebugContext(ctx, "done") files, err := l.Files(osReleasePath) if err != nil { - zlog.Debug(ctx).Msg("didn't find an os-release") + log.DebugContext(ctx, "didn't find an os-release") return nil, nil } for _, buff := range files { - dist := ds.parse(buff) + dist := parse(buff) if dist != nil { return []*claircore.Distribution{dist}, nil } @@ -93,11 +87,11 @@ func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([] return []*claircore.Distribution{}, nil } -// parse attempts to match all AWS release regexp and returns the associated +// Parse attempts to match all AWS release regexp and returns the associated // distribution if it exists. // // separated into its own method to aid testing. -func (ds *DistributionScanner) parse(buff *bytes.Buffer) *claircore.Distribution { +func parse(buff *bytes.Buffer) *claircore.Distribution { for _, ur := range awsRegexes { if ur.regexp.Match(buff.Bytes()) { dist := releaseToDist(ur.release) diff --git a/aws/distributionscanner_test.go b/aws/distributionscanner_test.go index ec251ba75..161238ae2 100644 --- a/aws/distributionscanner_test.go +++ b/aws/distributionscanner_test.go @@ -72,52 +72,51 @@ SUPPORT_END="2028-03-01"`) func TestDistributionScanner(t *testing.T) { table := []struct { - name string - release Release - osRelease []byte + name string + release Release + osRelease []byte prettyDistName string }{ { - name: "AL1", - release: AmazonLinux1, - osRelease: AL1v201609OSRelease, + name: "AL1", + release: AmazonLinux1, + osRelease: AL1v201609OSRelease, prettyDistName: "Amazon Linux AMI 2018.03", }, { - name: "AL1", - release: AmazonLinux1, - osRelease: AL1v201703OSRelease, + name: "AL1", + release: AmazonLinux1, + osRelease: AL1v201703OSRelease, prettyDistName: "Amazon Linux AMI 2018.03", }, { - name: "AL1", - release: AmazonLinux1, - osRelease: AL1v201709OSRelease, + name: "AL1", + release: AmazonLinux1, + osRelease: AL1v201709OSRelease, prettyDistName: "Amazon Linux AMI 2018.03", }, { - name: "AL1", - release: AmazonLinux1, - osRelease: AL1v201803OSRelease, + name: "AL1", + release: AmazonLinux1, + osRelease: AL1v201803OSRelease, prettyDistName: "Amazon Linux AMI 2018.03", }, { - name: "AL2", - release: AmazonLinux2, - osRelease: AL2OSRelease, + name: "AL2", + release: AmazonLinux2, + osRelease: AL2OSRelease, prettyDistName: "Amazon Linux 2", }, { - name: "AL2023", - release: AmazonLinux2023, - osRelease: AL2023OSRelease, + name: "AL2023", + release: AmazonLinux2023, + osRelease: AL2023OSRelease, prettyDistName: "Amazon Linux 2023", }, } for _, tt := range table { t.Run(tt.name, func(t *testing.T) { - scanner := DistributionScanner{} - dist := scanner.parse(bytes.NewBuffer(tt.osRelease)) + dist := parse(bytes.NewBuffer(tt.osRelease)) cmpDist := releaseToDist(tt.release) if !cmp.Equal(dist, cmpDist) { t.Fatalf("%v", cmp.Diff(dist, cmpDist)) diff --git a/aws/purl.go b/aws/purl.go new file mode 100644 index 000000000..b8abc8644 --- /dev/null +++ b/aws/purl.go @@ -0,0 +1,89 @@ +package aws + +import ( + "context" + "fmt" + "strings" + + "github.com/package-url/packageurl-go" + "github.com/quay/claircore" + "github.com/quay/claircore/toolkit/types/cpe" +) + +const ( + // PURLType is the type of package URL for RPM packages. + PURLType = "rpm" + // PURLNamespace is the namespace of AWS RPMs. + PURLNamespace = "aws" +) + +// GeneratePURL generates an RPM PURL for a given [claircore.IndexRecord]. +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + p := packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: ir.Package.Name, + Version: ir.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": ir.Package.Arch, + }), + } + if ir.Distribution != nil { + // We don't persist the CPE in the Distribution but try it first in case it's available. + if c := ir.Distribution.CPE.String(); c != "" { + p.Qualifiers = append(p.Qualifiers, packageurl.Qualifier{ + Key: "distro_cpe", + Value: c, + }) + } + + if ir.Distribution.DID != "" && ir.Distribution.VersionID != "" { + p.Qualifiers = append(p.Qualifiers, packageurl.Qualifier{ + Key: "distro", + Value: "amzn-" + ir.Distribution.VersionID, + }) + } + } + return p, nil +} + +// ParsePURL parses an RPM PURL into a list of [claircore.IndexRecord]s. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + ir := &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Arch: purl.Qualifiers.Map()["arch"], + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{}, + } + // Prefer a distro CPE if provided. + if dc := purl.Qualifiers.Map()["distro_cpe"]; dc != "" { + if wf, err := cpe.Unbind(dc); err == nil { + ir.Distribution = cpeToDistribution(wf) + return []*claircore.IndexRecord{ir}, nil + } + } + + // Fallback to legacy distro qualifier parsing: "Name-VersionID". + distroQualifier := purl.Qualifiers.Map()["distro"] + distroParts := strings.SplitN(distroQualifier, "-", 2) + if len(distroParts) != 2 { + return nil, fmt.Errorf("invalid distro PURL: %s", distroQualifier) + } + ver := distroParts[1] + if ver == AL1Dist.Version { + ir.Distribution = AL1Dist + } else { + ir.Distribution = &claircore.Distribution{ + Name: "Amazon Linux", + DID: ID, + Version: ver, + VersionID: ver, + PrettyName: "Amazon Linux " + ver, + CPE: cpe.MustUnbind("cpe:o:amazon:amazon_linux:" + ver), + } + } + return []*claircore.IndexRecord{ir}, nil +} diff --git a/aws/purl_test.go b/aws/purl_test.go new file mode 100644 index 000000000..6e3bf8818 --- /dev/null +++ b/aws/purl_test.go @@ -0,0 +1,129 @@ +package aws + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" + "github.com/quay/claircore/toolkit/types/cpe" +) + +func TestRoundTripIndexRecordAWS(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "amzn2", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "curl", + Version: "7.79.1-2.amzn2.0.2", + Arch: "x86_64", + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{ + Name: "Amazon Linux AMI", + VersionID: "2018.03", + DID: "amzn", + Version: "2018.03", + PrettyName: "Amazon Linux AMI 2018.03", + CPE: cpe.MustUnbind("cpe:/o:amazon:linux:2018.03:ga"), + }, + }, + }, + { + name: "amzn2023", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1.16-6.amzn2023.0.4", + Arch: "aarch64", + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{ + Name: "Amazon Linux", + VersionID: "2023", + DID: "amzn", + Version: "2023", + PrettyName: "Amazon Linux 2023", + CPE: cpe.MustUnbind("cpe:2.3:o:amazon:amazon_linux:2023:*:*:*:*:*:*:*"), + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GenerateRPMPURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParseRPMPURL: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "amzn2-basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "curl", + Version: "7.79.1-2.amzn2.0.2", + Arch: "x86_64", + }, + Distribution: &claircore.Distribution{ + Name: "Amazon Linux", + VersionID: "2023", + Version: "2023", + DID: "amzn", + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: "curl", + Version: "7.79.1-2.amzn2.0.2", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "x86_64"}, + {Key: "distro", Value: "amzn-2023"}, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GenerateRPMPURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} diff --git a/aws/releases.go b/aws/releases.go index 055105584..456d12241 100644 --- a/aws/releases.go +++ b/aws/releases.go @@ -4,14 +4,14 @@ import ( "fmt" "github.com/quay/claircore" - "github.com/quay/claircore/pkg/cpe" + "github.com/quay/claircore/toolkit/types/cpe" ) type Release string const ( - AmazonLinux1 Release = "AL1" - AmazonLinux2 Release = "AL2" + AmazonLinux1 Release = "AL1" + AmazonLinux2 Release = "AL2" AmazonLinux2023 Release = "AL2023" // os-release name ID field consistently available on official amazon linux images ID = "amzn" @@ -20,8 +20,8 @@ const ( func (r Release) mirrorlist() string { //doc:url updater const ( - al1 = "http://repo.us-west-2.amazonaws.com/2018.03/updates/x86_64/mirror.list" - al2 = "https://cdn.amazonlinux.com/2/core/latest/x86_64/mirror.list" + al1 = "http://repo.us-west-2.amazonaws.com/2018.03/updates/x86_64/mirror.list" + al2 = "https://cdn.amazonlinux.com/2/core/latest/x86_64/mirror.list" al2023 = "https://cdn.amazonlinux.com/al2023/core/mirrors/latest/x86_64/mirror.list" ) switch r { @@ -62,7 +62,6 @@ var AL2023Dist = &claircore.Distribution{ CPE: cpe.MustUnbind("cpe:2.3:o:amazon:amazon_linux:2023"), } - func releaseToDist(release Release) *claircore.Distribution { switch release { case AmazonLinux1: @@ -76,3 +75,20 @@ func releaseToDist(release Release) *claircore.Distribution { return &claircore.Distribution{} } } + +// cpeToDistribution constructs a Distribution from a [cpe.WFN]. +func cpeToDistribution(wf cpe.WFN) *claircore.Distribution { + ver := wf.Attr[cpe.Version].String() + if ver == AL1Dist.Version { + return AL1Dist + } else { + return &claircore.Distribution{ + Name: "Amazon Linux", + DID: ID, + Version: ver, + VersionID: ver, + PrettyName: "Amazon Linux " + ver, + CPE: wf, + } + } +} diff --git a/aws/updater.go b/aws/updater.go index 16a550ab1..7a3c895a9 100644 --- a/aws/updater.go +++ b/aws/updater.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/aws/internal/alas" "github.com/quay/claircore/internal/xmlutil" @@ -43,7 +41,6 @@ func (u *Updater) Configure(ctx context.Context, _ driver.ConfigUnmarshaler, c * } func (u *Updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "aws/Updater.Fetch") client, err := NewClient(ctx, u.c, u.release) if err != nil { return nil, "", fmt.Errorf("failed to create client: %v", err) diff --git a/datastore/postgres/affected_manifests_e2e_test.go b/datastore/postgres/affected_manifests_e2e_test.go index f56aa4180..72c347721 100644 --- a/datastore/postgres/affected_manifests_e2e_test.go +++ b/datastore/postgres/affected_manifests_e2e_test.go @@ -8,11 +8,11 @@ import ( "testing" "github.com/jackc/pgx/v5/pgxpool" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/pkg/omnimatcher" + "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" pgtest "github.com/quay/claircore/test/postgres" ) @@ -27,7 +27,7 @@ type affectedE2E struct { func TestAffectedE2E(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) pool := pgtest.TestIndexerDB(ctx, t) store := NewIndexerStore(pool) @@ -161,7 +161,7 @@ func (e *affectedE2E) Run(t *testing.T) { // this is required so foreign key constraints do not // fail in later tests. func (e *affectedE2E) IndexArtifacts(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) const ( insertManifest = ` INSERT INTO manifest @@ -255,7 +255,7 @@ func (e *affectedE2E) IndexArtifacts(t *testing.T) { // IndexManifest confirms the contents of a manifest // can be written to the manifest index table. func (e *affectedE2E) IndexManifest(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) err := e.store.IndexManifest(ctx, &e.ir) if err != nil { t.Fatalf("failed to index manifest: %v", err) @@ -266,7 +266,7 @@ func (e *affectedE2E) IndexManifest(t *testing.T) { // in the vulnereability report reports the associated // manifest is affected. func (e *affectedE2E) AffectedManifests(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) om := omnimatcher.New(nil) for _, vuln := range e.vr.Vulnerabilities { hashes, err := e.store.AffectedManifests(ctx, *vuln, om.Vulnerable) diff --git a/datastore/postgres/affectedmanifest.go b/datastore/postgres/affectedmanifest.go index 16448f63b..2bc51152e 100644 --- a/datastore/postgres/affectedmanifest.go +++ b/datastore/postgres/affectedmanifest.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log/slog" "strconv" "time" @@ -12,7 +13,6 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore" ) @@ -107,7 +107,6 @@ WHERE ); ` ) - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/affectedManifests") // confirm the incoming vuln can be // resolved into a prototype index record @@ -160,7 +159,7 @@ WHERE pkg.ID = idStr pkgsToFilter = append(pkgsToFilter, pkg) } - zlog.Debug(ctx).Int("count", len(pkgsToFilter)).Msg("packages to filter") + slog.DebugContext(ctx, "packages to filter", "count", len(pkgsToFilter)) if err := rows.Err(); err != nil { return nil, fmt.Errorf("error scanning packages: %w", err) } @@ -183,7 +182,7 @@ WHERE }) } } - zlog.Debug(ctx).Int("count", len(filteredRecords)).Msg("vulnerable index records") + slog.DebugContext(ctx, "vulnerable index records", "count", len(filteredRecords)) // Query the manifest index for manifests containing the vulnerable // IndexRecords and create a set containing each unique manifest. @@ -232,7 +231,7 @@ WHERE return nil, err } } - zlog.Debug(ctx).Int("count", len(out)).Msg("affected manifests") + slog.DebugContext(ctx, "affected manifests", "count", len(out)) return out, nil } @@ -262,7 +261,6 @@ func protoRecord(ctx context.Context, pool *pgxpool.Pool, v claircore.Vulnerabil ` timeout = 5 * time.Second ) - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/protoRecord") protoRecord := claircore.IndexRecord{} // fill dist into prototype index record if exists @@ -302,7 +300,7 @@ func protoRecord(ctx context.Context, pool *pgxpool.Pool, v claircore.Vulnerabil VersionCodeName: v.Dist.VersionCodeName, VersionID: v.Dist.VersionID, } - zlog.Debug(ctx).Str("id", id).Msg("discovered distribution id") + slog.DebugContext(ctx, "discovered distribution id", "id", id) } } @@ -332,7 +330,7 @@ func protoRecord(ctx context.Context, pool *pgxpool.Pool, v claircore.Vulnerabil Name: v.Repo.Name, URI: v.Repo.URI, } - zlog.Debug(ctx).Str("id", id).Msg("discovered repo id") + slog.DebugContext(ctx, "discovered repo id", "id", id) } } diff --git a/datastore/postgres/connect.go b/datastore/postgres/connect.go index e9f0938c5..5a586664d 100644 --- a/datastore/postgres/connect.go +++ b/datastore/postgres/connect.go @@ -3,10 +3,10 @@ package postgres import ( "context" "fmt" + "log/slog" "github.com/jackc/pgx/v5/pgxpool" "github.com/prometheus/client_golang/prometheus" - "github.com/quay/zlog" "github.com/quay/claircore/datastore/postgres/types" "github.com/quay/claircore/pkg/poolstats" @@ -33,7 +33,7 @@ func Connect(ctx context.Context, connString string, applicationName string) (*p } if err := prometheus.Register(poolstats.NewCollector(pool, applicationName)); err != nil { - zlog.Info(ctx).Msg("pool metrics already registered") + slog.InfoContext(ctx, "pool metrics already registered") } return pool, nil diff --git a/datastore/postgres/deletemanifests.go b/datastore/postgres/deletemanifests.go index fd10616a5..1545f7b29 100644 --- a/datastore/postgres/deletemanifests.go +++ b/datastore/postgres/deletemanifests.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" + "log/slog" "github.com/jackc/pgx/v5" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/internal/dblock" @@ -43,7 +43,6 @@ var errLockFail = errors.New("lock failed") // The returned slice indicates the successfully deleted manifests. An error is // reported if none were able to be deleted. func (s *IndexerStore) DeleteManifests(ctx context.Context, ds ...claircore.Digest) ([]claircore.Digest, error) { - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/DeleteManifests") const ( getManifestID = `SELECT id FROM manifest WHERE hash = $1` getLayers = `SELECT layer_id FROM manifest_layer WHERE manifest_id = $1;` @@ -135,10 +134,9 @@ func (s *IndexerStore) DeleteManifests(ctx context.Context, ds ...claircore.Dige return fmt.Errorf("unable check layer usage: %w", err) } ra := tag.RowsAffected() - zlog.Debug(ctx). - Int64("count", ra). - Str("manifest", d.String()). - Msg("deleted layers for manifest") + slog.DebugContext(ctx, "deleted layers for manifest", + "count", ra, + "manifest", d) } deletedManifests = append(deletedManifests, d) return nil @@ -146,25 +144,20 @@ func (s *IndexerStore) DeleteManifests(ctx context.Context, ds ...claircore.Dige switch { case attemptErr == nil: case errors.Is(attemptErr, errLockFail): - zlog.Debug(ctx). - Stringer("manifest", d). - Msg("unable to obtain lock") + slog.DebugContext(ctx, "unable to obtain lock", "manifest", d) default: errs = append(errs, fmt.Errorf("%s: %w", d, attemptErr)) } } - zlog.Debug(ctx). - Int("count", len(deletedManifests)). - Int("nonexistant", len(ds)-len(deletedManifests)). - Msg("deleted manifests") + slog.DebugContext(ctx, "deleted manifests", + "count", len(deletedManifests), + "nonexistant", len(ds)-len(deletedManifests)) if len(deletedManifests) == 0 { err = errors.Join(errs...) return nil, err } if len(errs) > 0 { - zlog.Warn(ctx). - Errs("reason", errs). - Msg("unexpected errors") + slog.WarnContext(ctx, "unexpected errors", "reason", errs) } return deletedManifests, nil } diff --git a/datastore/postgres/deletemanifests_test.go b/datastore/postgres/deletemanifests_test.go index 51d29be92..f01a7083f 100644 --- a/datastore/postgres/deletemanifests_test.go +++ b/datastore/postgres/deletemanifests_test.go @@ -1,11 +1,9 @@ package postgres import ( - "context" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -17,13 +15,13 @@ import ( func TestDeleteManifests(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) pool := pgtest.TestIndexerDB(ctx, t) store := NewIndexerStore(pool) defer store.Close(ctx) t.Run("Nonexistent", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) in := []claircore.Digest{ test.RandomSHA256Digest(t), } @@ -36,7 +34,7 @@ func TestDeleteManifests(t *testing.T) { } }) t.Run("NonexistentMulti", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) in := []claircore.Digest{ test.RandomSHA256Digest(t), test.RandomSHA256Digest(t), @@ -54,7 +52,7 @@ func TestDeleteManifests(t *testing.T) { }) const insertManifest = `INSERT INTO manifest (hash) SELECT unnest($1::TEXT[]);` t.Run("One", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) want := []claircore.Digest{ test.RandomSHA256Digest(t), } @@ -70,7 +68,7 @@ func TestDeleteManifests(t *testing.T) { } }) t.Run("Locked", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) want := []claircore.Digest{ test.RandomSHA256Digest(t), test.RandomSHA256Digest(t), @@ -107,7 +105,7 @@ func TestDeleteManifests(t *testing.T) { } }) t.Run("Subset", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) in := make([]claircore.Digest, 8) for i := range in { in[i] = test.RandomSHA256Digest(t) @@ -139,7 +137,7 @@ INSERT INTO manifest_layer (i, manifest_id, layer_id) nManifests = 8 layersPer = 4 ) - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) ms := make([]claircore.Digest, nManifests) for i := range ms { ms[i] = test.RandomSHA256Digest(t) @@ -207,7 +205,7 @@ INSERT INTO manifest_layer (i, manifest_id, layer_id) nManifests = 8 nonBaseLayersPer = 3 ) - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) ms := make([]claircore.Digest, nManifests) for i := range ms { ms[i] = test.RandomSHA256Digest(t) @@ -289,7 +287,7 @@ INSERT INTO manifest_layer (i, manifest_id, layer_id) packageN = 10 ) s := NewIndexerStore(pool) - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) toDelete := make([]claircore.Digest, manifestsN) for i := range manifestsN { ir := &claircore.IndexReport{} diff --git a/datastore/postgres/enrichment.go b/datastore/postgres/enrichment.go index b3b6dd7d7..0d7edb6ef 100644 --- a/datastore/postgres/enrichment.go +++ b/datastore/postgres/enrichment.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log/slog" "sort" "strconv" "time" @@ -15,7 +16,6 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore/datastore" "github.com/quay/claircore/libvuln/driver" @@ -70,7 +70,6 @@ var ( ) func (s *MatcherStore) UpdateEnrichmentsIter(ctx context.Context, updater string, fp driver.Fingerprint, it datastore.EnrichmentIter) (uuid.UUID, error) { - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/MatcherStore.UpdateEnrichmentsIter") return s.updateEnrichments(ctx, updater, fp, it) } @@ -78,7 +77,6 @@ func (s *MatcherStore) UpdateEnrichmentsIter(ctx context.Context, updater string // EnrichmentRecord(s), and ensures enrichments from previous updates are not // queried by clients. func (s *MatcherStore) UpdateEnrichments(ctx context.Context, updater string, fp driver.Fingerprint, es []driver.EnrichmentRecord) (uuid.UUID, error) { - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/MatcherStore.UpdateEnrichments") enIter := func(yield func(record *driver.EnrichmentRecord, err error) bool) { for i := range es { if !yield(&es[i], nil) { @@ -133,7 +131,6 @@ DO NOTHING;` refreshView = `REFRESH MATERIALIZED VIEW CONCURRENTLY latest_update_operations;` ) - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/UpdateEnrichments") var id uint64 var ref uuid.UUID @@ -146,9 +143,7 @@ DO } updateEnrichmentsCounter.WithLabelValues("create").Add(1) updateEnrichmentsDuration.WithLabelValues("create").Observe(time.Since(start).Seconds()) - zlog.Debug(ctx). - Str("ref", ref.String()). - Msg("update_operation created") + slog.DebugContext(ctx, "update_operation created", "ref", ref) return nil }) if err != nil { @@ -180,7 +175,9 @@ DO // Periodically refresh planner statistics during bulk inserts to avoid stalls if enCt%analyzeEveryNRecords == 0 { if _, analyzeErr := s.pool.Exec(ctx, "ANALYZE enrichment"); analyzeErr != nil { - zlog.Warn(ctx).Err(analyzeErr).Int("record_count", enCt).Msg("failed to analyze enrichment table during processing") + slog.WarnContext(ctx, "failed to analyze enrichment table during processing", + "record_count", enCt, + "reason", analyzeErr) } } @@ -213,10 +210,9 @@ DO if _, err = s.pool.Exec(ctx, refreshView); err != nil { return uuid.Nil, fmt.Errorf("could not refresh latest_update_operations: %w", err) } - zlog.Debug(ctx). - Stringer("ref", ref). - Int("inserted", enCt). - Msg("update_operation committed") + slog.DebugContext(ctx, "update_operation committed", + "ref", ref, + "inserted", enCt) return ref, nil } @@ -256,7 +252,6 @@ where and l.kind = 'enrichment' limit 1;` - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/GetEnrichment") timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { getEnrichmentsDuration.WithLabelValues("query", strconv.FormatBool(errors.Is(err, nil))).Observe(v) })) diff --git a/datastore/postgres/gc.go b/datastore/postgres/gc.go index 756203ffd..3bdd9f50d 100644 --- a/datastore/postgres/gc.go +++ b/datastore/postgres/gc.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log/slog" "runtime" "strings" "time" @@ -12,7 +13,6 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "golang.org/x/sync/semaphore" "github.com/quay/claircore/libvuln/driver" @@ -55,8 +55,6 @@ const ( // If a full GC is required run this method until the returned int64 value // is 0. func (s *MatcherStore) GC(ctx context.Context, keep int) (int64, error) { - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/GC") - // obtain update operations which need deletin' ops, totalOps, err := eligibleUpdateOpts(ctx, s.pool, keep) if err != nil { @@ -94,7 +92,7 @@ func (s *MatcherStore) GC(ctx context.Context, keep int) (int64, error) { case driver.EnrichmentKind: cleanup = enrichmentCleanup default: - zlog.Error(ctx).Str("kind", string(kind)).Msg("unknown updater kind; skipping cleanup") + slog.ErrorContext(ctx, "unknown updater kind; skipping cleanup", "kind", string(kind)) continue } for _, u := range us { @@ -229,15 +227,13 @@ AND v1.id = v2.id; ) start := time.Now() - ctx = zlog.ContextWithValues(ctx, "updater", updater) - zlog.Debug(ctx). - Msg("starting vuln clean up") + slog.DebugContext(ctx, "starting vuln clean up") res, err := pool.Exec(ctx, deleteOrphanedVulns, updater) if err != nil { gcCounter.WithLabelValues("deleteVulns", "false").Inc() return fmt.Errorf("failed while exec'ing vuln delete: %w", err) } - zlog.Debug(ctx).Int64("rows affected", res.RowsAffected()).Msg("vulns deleted") + slog.DebugContext(ctx, "vulns deleted", "rows affected", res.RowsAffected()) gcCounter.WithLabelValues("deleteVulns", "true").Inc() gcDuration.WithLabelValues("deleteVulns").Observe(time.Since(start).Seconds()) @@ -258,15 +254,13 @@ DELETE FROM enrichment e1 USING ) start := time.Now() - ctx = zlog.ContextWithValues(ctx, "updater", updater) - zlog.Debug(ctx). - Msg("starting enrichment clean up") + slog.DebugContext(ctx, "starting enrichment clean up") res, err := pool.Exec(ctx, deleteOrphanedEnrichments, updater) if err != nil { gcCounter.WithLabelValues("deleteEnrichments", "false").Inc() return fmt.Errorf("failed while exec'ing enrichment delete: %w", err) } - zlog.Debug(ctx).Int64("rows affected", res.RowsAffected()).Msg("enrichments deleted") + slog.DebugContext(ctx, "enrichments deleted", "rows affected", res.RowsAffected()) gcCounter.WithLabelValues("deleteEnrichments", "true").Inc() gcDuration.WithLabelValues("deleteEnrichments").Observe(time.Since(start).Seconds()) diff --git a/datastore/postgres/gc_test.go b/datastore/postgres/gc_test.go index b95538688..d00013cb0 100644 --- a/datastore/postgres/gc_test.go +++ b/datastore/postgres/gc_test.go @@ -9,8 +9,6 @@ import ( "net/http" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/libvuln/updates" @@ -146,7 +144,7 @@ func TestGC(t *testing.T) { for _, tt := range table { t.Run(tt.name, func(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) pool := pgtest.TestMatcherDB(ctx, t) store := NewMatcherStore(pool) locks, err := ctxlock.New(ctx, pool) diff --git a/datastore/postgres/get.go b/datastore/postgres/get.go index 0d732c10f..5ca8c7eaf 100644 --- a/datastore/postgres/get.go +++ b/datastore/postgres/get.go @@ -3,13 +3,13 @@ package postgres import ( "context" "fmt" + "log/slog" "strconv" "time" "github.com/jackc/pgx/v5" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/datastore" @@ -43,7 +43,6 @@ type recordQuery struct { // Get implements vulnstore.Vulnerability. func (s *MatcherStore) Get(ctx context.Context, records []*claircore.IndexRecord, opts datastore.GetOpts) (map[string][]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/Get") tx, err := s.pool.Begin(ctx) if err != nil { return nil, err @@ -57,10 +56,9 @@ func (s *MatcherStore) Get(ctx context.Context, records []*claircore.IndexRecord query, err := buildGetQuery(record, &opts) if err != nil { // if we cannot build a query for an individual record continue to the next - zlog.Debug(ctx). - Err(err). - Str("record", fmt.Sprintf("%+v", record)). - Msg("could not build query for record") + slog.DebugContext(ctx, "could not build query for record", + "reason", err, + "record", record) continue } rqs = append(rqs, &recordQuery{query: query, record: record}) diff --git a/datastore/postgres/getupdateoperationdiff_test.go b/datastore/postgres/getupdateoperationdiff_test.go index f377875e4..51f914461 100644 --- a/datastore/postgres/getupdateoperationdiff_test.go +++ b/datastore/postgres/getupdateoperationdiff_test.go @@ -1,15 +1,14 @@ package postgres import ( - "context" "fmt" "testing" "github.com/google/uuid" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" pgtest "github.com/quay/claircore/test/postgres" ) @@ -26,7 +25,7 @@ type diffTestCase struct { // are captured there, e.g. if there's no difference between the two operations. func TestGetUpdateDiff(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) cases := []diffTestCase{ // second op adds two new vulns diff --git a/datastore/postgres/getupdateoperations.go b/datastore/postgres/getupdateoperations.go index a808661e3..a71b9444d 100644 --- a/datastore/postgres/getupdateoperations.go +++ b/datastore/postgres/getupdateoperations.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log/slog" "time" "github.com/google/uuid" @@ -11,7 +12,6 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore/libvuln/driver" ) @@ -80,7 +80,6 @@ func (s *MatcherStore) GetLatestUpdateRef(ctx context.Context, kind driver.Updat queryEnrichment = `SELECT ref FROM update_operation WHERE kind = 'enrichment' ORDER BY id USING > LIMIT 1;` queryVulnerability = `SELECT ref FROM update_operation WHERE kind = 'vulnerability' ORDER BY id USING > LIMIT 1;` ) - ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/getLatestRef") var q string var label string @@ -156,15 +155,12 @@ func (s *MatcherStore) GetLatestUpdateRefs(ctx context.Context, kind driver.Upda } ret[uo.Updater] = ops } - zlog.Debug(ctx). - Int("count", len(ret)). - Msg("found updaters") + slog.DebugContext(ctx, "found updaters", "count", len(ret)) return ret, nil } func getLatestRefs(ctx context.Context, pool *pgxpool.Pool) (map[string][]driver.UpdateOperation, error) { const query = `SELECT DISTINCT ON (updater) updater, ref, fingerprint, date FROM update_operation ORDER BY updater, id USING >;` - ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/getLatestRefs") start := time.Now() @@ -195,9 +191,7 @@ func getLatestRefs(ctx context.Context, pool *pgxpool.Pool) (map[string][]driver } ret[uo.Updater] = ops } - zlog.Debug(ctx). - Int("count", len(ret)). - Msg("found updaters") + slog.DebugContext(ctx, "found updaters", "count", len(ret)) return ret, nil } @@ -208,7 +202,6 @@ func (s *MatcherStore) GetUpdateOperations(ctx context.Context, kind driver.Upda queryEnrichment = `SELECT ref, updater, fingerprint, date FROM update_operation WHERE updater = ANY($1) AND kind = 'enrichment' ORDER BY id DESC;` getUpdaters = `SELECT DISTINCT(updater) FROM update_operation;` ) - ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/getUpdateOperations") tx, err := s.pool.Begin(ctx) if err != nil { diff --git a/datastore/postgres/index_e2e_test.go b/datastore/postgres/index_e2e_test.go index 62e267805..d85f2cc6c 100644 --- a/datastore/postgres/index_e2e_test.go +++ b/datastore/postgres/index_e2e_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -26,9 +25,11 @@ type mockScnr struct { func (m mockScnr) Name() string { return m.name } + func (m mockScnr) Kind() string { return m.kind } + func (m mockScnr) Version() string { return m.version } @@ -220,7 +221,7 @@ func (e *indexE2e) Run(t *testing.T) { // Manifest and Layer identities so layer code // foreign key references do not fail. func (e *indexE2e) PersistManifest(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) err := e.store.PersistManifest(ctx, e.manifest) if err != nil { t.Fatalf("failed to persist manifest: %v", err) @@ -230,7 +231,7 @@ func (e *indexE2e) PersistManifest(t *testing.T) { // RegisterScanner confirms a scanner can be registered // and provides this scanner for other subtests to use func (e *indexE2e) RegisterScanner(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) err := e.store.RegisterScanners(ctx, e.scnrs) if err != nil { t.Fatalf("failed to register scnr: %v", err) @@ -241,7 +242,7 @@ func (e *indexE2e) RegisterScanner(t *testing.T) { // selecting packages associated with a layer works // correctly. func (e *indexE2e) IndexAndRetrievePackages(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) A := test.GenUniquePackages(e.packageGen) for _, scnr := range e.scnrs { @@ -265,7 +266,7 @@ func (e *indexE2e) IndexAndRetrievePackages(t *testing.T) { // selecting distributions associated with a layer works // correctly. func (e *indexE2e) IndexAndRetrieveDistributions(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) A := test.GenUniqueDistributions(e.distGen) for _, scnr := range e.scnrs { @@ -289,7 +290,7 @@ func (e *indexE2e) IndexAndRetrieveDistributions(t *testing.T) { // selecting repositories associated with a layer works // correctly. func (e *indexE2e) IndexAndRetrieveRepos(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) A := test.GenUniqueRepositories(e.repoGen) for _, scnr := range e.scnrs { @@ -312,7 +313,7 @@ func (e *indexE2e) IndexAndRetrieveRepos(t *testing.T) { // LayerScanned confirms the book keeping involved in marking a layer // scanned works correctly. func (e *indexE2e) LayerScanned(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) for _, scnr := range e.scnrs { err := e.store.SetLayerScanned(ctx, e.manifest.Layers[0].Hash, scnr) if err != nil { @@ -332,7 +333,7 @@ func (e *indexE2e) LayerScanned(t *testing.T) { // LayerScannedNotExists confirms an error is returned when attempting // to obtain if a layer was scanned by a non-existent scanner. func (e *indexE2e) LayerScannedNotExists(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) scnr := mockScnr{ name: "invalid", kind: "invalid", @@ -348,7 +349,7 @@ func (e *indexE2e) LayerScannedNotExists(t *testing.T) { // LayerScannedFalse confirms a false boolean is returned when attempting // to obtain if a non-existent layer was scanned by a valid scanner func (e *indexE2e) LayerScannedFalse(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) // create a layer that has not been persisted to the store layer := &claircore.Layer{ @@ -367,7 +368,7 @@ func (e *indexE2e) LayerScannedFalse(t *testing.T) { // IndexReport confirms the book keeping around index reports works // correctly. func (e *indexE2e) IndexReport(t *testing.T) { - ctx := zlog.Test(e.ctx, t) + ctx := test.Logging(t, e.ctx) A := &claircore.IndexReport{ Hash: e.manifest.Hash, diff --git a/datastore/postgres/indexmanifest.go b/datastore/postgres/indexmanifest.go index 014cd4c4b..cc905576a 100644 --- a/datastore/postgres/indexmanifest.go +++ b/datastore/postgres/indexmanifest.go @@ -3,13 +3,13 @@ package postgres import ( "context" "fmt" + "log/slog" "strconv" "time" "github.com/jackc/pgx/v5" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore" ) @@ -50,7 +50,6 @@ func (s *IndexerStore) IndexManifest(ctx context.Context, ir *claircore.IndexRep ON CONFLICT DO NOTHING; ` ) - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/IndexerStore.IndexManifest") if ir.Hash.String() == "" { return fmt.Errorf("received empty hash; cannot associate contents with a manifest hash") @@ -59,7 +58,7 @@ func (s *IndexerStore) IndexManifest(ctx context.Context, ir *claircore.IndexRep records := ir.IndexRecords() if len(records) == 0 { - zlog.Warn(ctx).Msg("manifest being indexed has 0 index records") + slog.WarnContext(ctx, "manifest being indexed has 0 index records") return nil } diff --git a/datastore/postgres/indexpackage.go b/datastore/postgres/indexpackage.go index eef3fe9ae..ee970fc01 100644 --- a/datastore/postgres/indexpackage.go +++ b/datastore/postgres/indexpackage.go @@ -4,12 +4,12 @@ import ( "cmp" "context" "fmt" + "log/slog" "time" "github.com/jackc/pgx/v5" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -99,8 +99,6 @@ func (s *IndexerStore) IndexPackages(ctx context.Context, pkgs []*claircore.Pack ` ) - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/IndexerStore.IndexPackages") - var batch, assocBatch pgx.Batch skipCt := 0 queueInsert := func(pkg *claircore.Package) { @@ -155,19 +153,17 @@ func (s *IndexerStore) IndexPackages(ctx context.Context, pkgs []*claircore.Pack if err != nil { return err } - zlog.Debug(ctx). - Int("skipped", skipCt). - Int("inserted", len(pkgs)-skipCt). - Msg("packages inserted") + slog.DebugContext(ctx, "packages inserted", + "skipped", skipCt, + "inserted", len(pkgs)-skipCt) start = time.Now() err = tx.SendBatch(ctx, &assocBatch).Close() indexPackageCounter.WithLabelValues("insertWith_batch").Add(1) indexPackageDuration.WithLabelValues("insertWith_batch").Observe(time.Since(start).Seconds()) - zlog.Debug(ctx). - Int("skipped", skipCt). - Int("inserted", len(pkgs)-skipCt). - Msg("scanartifacts inserted") + slog.DebugContext(ctx, "scanartifacts inserted", + "skipped", skipCt, + "inserted", len(pkgs)-skipCt) if err != nil { return err } diff --git a/datastore/postgres/indexpackage_benchmark_test.go b/datastore/postgres/indexpackage_benchmark_test.go index 19bbdd57a..8578a8619 100644 --- a/datastore/postgres/indexpackage_benchmark_test.go +++ b/datastore/postgres/indexpackage_benchmark_test.go @@ -1,11 +1,8 @@ package postgres import ( - "context" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" @@ -14,7 +11,6 @@ import ( func Benchmark_IndexPackages(b *testing.B) { integration.NeedDB(b) - ctx := context.Background() benchmarks := []struct { // the name of this benchmark name string @@ -179,7 +175,7 @@ func Benchmark_IndexPackages(b *testing.B) { for _, bench := range benchmarks { b.Run(bench.name, func(b *testing.B) { - ctx := zlog.Test(ctx, b) + ctx := test.Logging(b) pool := pgtest.TestIndexerDB(ctx, b) store := NewIndexerStore(pool) @@ -215,5 +211,4 @@ func Benchmark_IndexPackages(b *testing.B) { } }) } - } diff --git a/datastore/postgres/matcher_store.go b/datastore/postgres/matcher_store.go index 2f980cf43..218b01049 100644 --- a/datastore/postgres/matcher_store.go +++ b/datastore/postgres/matcher_store.go @@ -8,7 +8,6 @@ import ( "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" - "github.com/quay/zlog" "github.com/remind101/migrate" "github.com/quay/claircore/datastore" @@ -56,7 +55,6 @@ var ( // DeleteUpdateOperations implements vulnstore.Updater. func (s *MatcherStore) DeleteUpdateOperations(ctx context.Context, id ...uuid.UUID) (int64, error) { const query = `DELETE FROM update_operation WHERE ref = ANY($1::uuid[]);` - ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/deleteUpdateOperations") if len(id) == 0 { return 0, nil } diff --git a/datastore/postgres/packagesbylayer_benchmark_test.go b/datastore/postgres/packagesbylayer_benchmark_test.go index 2818aae30..1fa9d75de 100644 --- a/datastore/postgres/packagesbylayer_benchmark_test.go +++ b/datastore/postgres/packagesbylayer_benchmark_test.go @@ -1,11 +1,8 @@ package postgres import ( - "context" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" @@ -14,7 +11,6 @@ import ( func Benchmark_PackagesByLayer(b *testing.B) { integration.NeedDB(b) - ctx := context.Background() benchmarks := []struct { name string hash claircore.Digest @@ -91,7 +87,7 @@ func Benchmark_PackagesByLayer(b *testing.B) { for _, bench := range benchmarks { b.Run(bench.name, func(b *testing.B) { - ctx := zlog.Test(ctx, b) + ctx := test.Logging(b) pool := pgtest.TestIndexerDB(ctx, b) store := NewIndexerStore(pool) diff --git a/datastore/postgres/querybuilder_test.go b/datastore/postgres/querybuilder_test.go index fefee94f2..fd2d4036e 100644 --- a/datastore/postgres/querybuilder_test.go +++ b/datastore/postgres/querybuilder_test.go @@ -1,14 +1,12 @@ package postgres import ( - "context" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/datastore" @@ -33,7 +31,7 @@ func TestGetQueryBuilderDeterministicArgs(t *testing.T) { both = `(((("package_name" = 'package-0') AND ("package_kind" = 'binary')) OR (("package_name" = 'source-package-0') AND ("package_kind" = 'source'))) AND ` noSource = `((("package_name" = 'package-0') AND ("package_kind" = 'binary')) AND ` ) - var table = []struct { + table := []struct { // name of test name string // the expected query string returned @@ -253,7 +251,7 @@ type testCase struct { // considered when querying for vulns func TestLatestVulns(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) cases := []testCase{ { diff --git a/datastore/postgres/recordupdatetime.go b/datastore/postgres/recordupdatetime.go index fff4a298b..71e7d4aa5 100644 --- a/datastore/postgres/recordupdatetime.go +++ b/datastore/postgres/recordupdatetime.go @@ -3,10 +3,10 @@ package postgres import ( "context" "fmt" + "log/slog" "time" "github.com/jackc/pgx/v5/pgxpool" - "github.com/quay/zlog" "github.com/quay/claircore/libvuln/driver" ) @@ -57,9 +57,7 @@ func recordUpdaterStatus(ctx context.Context, pool *pgxpool.Pool, updaterName st last_error = $4 RETURNING updater_name;` ) - - ctx = zlog.ContextWithValues(ctx, - "component", "internal/vulnstore/postgres/recordUpdaterStatus") + log := slog.With("updater", updaterName) tx, err := pool.Begin(ctx) if err != nil { @@ -70,17 +68,13 @@ func recordUpdaterStatus(ctx context.Context, pool *pgxpool.Pool, updaterName st var returnedUpdaterName string if updaterError == nil { - zlog.Debug(ctx). - Str("updater", updaterName). - Msg("recording successful update") + log.DebugContext(ctx, "recording successful update") _, err := tx.Exec(ctx, upsertSuccessfulUpdate, updaterName, updateTime, fingerprint) if err != nil { return fmt.Errorf("failed to upsert successful updater status: %w", err) } } else { - zlog.Debug(ctx). - Str("updater", updaterName). - Msg("recording failed update") + log.DebugContext(ctx, "recording failed update") if err := tx.QueryRow(ctx, upsertFailedUpdate, updaterName, updateTime, fingerprint, updaterError.Error()).Scan(&returnedUpdaterName); err != nil { return fmt.Errorf("failed to upsert failed updater status: %w", err) } @@ -89,10 +83,7 @@ func recordUpdaterStatus(ctx context.Context, pool *pgxpool.Pool, updaterName st if err := tx.Commit(ctx); err != nil { return fmt.Errorf("failed to commit transaction: %w", err) } - - zlog.Debug(ctx). - Str("updater", updaterName). - Msg("updater status stored in database") + log.DebugContext(ctx, "updater status stored in database") return nil } @@ -109,9 +100,6 @@ func recordUpdaterSetStatus(ctx context.Context, pool *pgxpool.Pool, updaterSet WHERE updater_name like $2 || '%';` ) - ctx = zlog.ContextWithValues(ctx, - "component", "internal/vulnstore/postgres/recordUpdaterSetStatus") - tx, err := pool.Begin(ctx) if err != nil { return fmt.Errorf("unable to start transaction: %w", err) @@ -127,10 +115,9 @@ func recordUpdaterSetStatus(ctx context.Context, pool *pgxpool.Pool, updaterSet return fmt.Errorf("failed to commit transaction: %w", err) } - zlog.Debug(ctx). - Str("factory", updaterSet). - Int64("rowsAffected", tag.RowsAffected()). - Msg("status updated for factory updaters") + slog.DebugContext(ctx, "status updated for factory updaters", + "factory", updaterSet, + "rowsAffected", tag.RowsAffected()) return nil } diff --git a/datastore/postgres/setlayerscanned.go b/datastore/postgres/setlayerscanned.go index ec97d59ae..39f8842aa 100644 --- a/datastore/postgres/setlayerscanned.go +++ b/datastore/postgres/setlayerscanned.go @@ -7,7 +7,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -36,7 +35,6 @@ var ( ) func (s *IndexerStore) SetLayerScanned(ctx context.Context, hash claircore.Digest, vs indexer.VersionedScanner) error { - ctx = zlog.ContextWithValues(ctx, "scanner", vs.Name()) const query = ` WITH scanner diff --git a/datastore/postgres/update_e2e_test.go b/datastore/postgres/update_e2e_test.go index 0d05562b2..367afa8d9 100644 --- a/datastore/postgres/update_e2e_test.go +++ b/datastore/postgres/update_e2e_test.go @@ -14,7 +14,6 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/datastore" @@ -27,7 +26,7 @@ import ( // TestUpdateE2E performs an end to end test of update operations and diffing func TestUpdateE2E(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := t.Context() cases := []updateE2e{ { @@ -84,7 +83,7 @@ func (e *updateE2e) Run(ctx context.Context) func(*testing.T) { {"DeleteUpdateOperations", e.DeleteUpdateOperations}, } return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) pool := pgtest.TestMatcherDB(ctx, t) e.pool = pool e.s = NewMatcherStore(pool) @@ -118,7 +117,7 @@ var updateOpCmp = cmpopts.IgnoreFields(driver.UpdateOperation{}, "Date") func (e *updateE2e) Update(ctx context.Context) func(*testing.T) { fp := driver.Fingerprint(uuid.New().String()) return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) e.updateOps = make([]driver.UpdateOperation, 0, e.Updates) for _, vs := range e.vulns() { ref, err := e.s.UpdateVulnerabilities(ctx, e.updater, fp, vs) @@ -144,7 +143,7 @@ func (e *updateE2e) Update(ctx context.Context) func(*testing.T) { // operation returns the expected results. func (e *updateE2e) GetUpdateOperations(ctx context.Context) func(*testing.T) { return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) out, err := e.s.GetUpdateOperations(ctx, driver.VulnerabilityKind, e.updater) if err != nil { t.Fatalf("failed to get UpdateOperations: %v", err) @@ -179,7 +178,7 @@ type update struct { // and then an update to an whole updater set func (e *updateE2e) recordUpdaterStatus(ctx context.Context) func(*testing.T) { return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) errorText := "test error" firstUpdateDate := time.Date(2020, time.Month(1), 22, 2, 10, 30, 0, time.UTC) secondUpdateDate := time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC) @@ -256,7 +255,7 @@ func orNoIndex(a int) string { // independently calculated diffs. func (e *updateE2e) Diff(ctx context.Context) func(t *testing.T) { return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) for n := range e.vulns() { // This does a bunch of checks so that the first operation is // compared appropriately. @@ -344,7 +343,7 @@ func (e *updateE2e) DeleteUpdateOperations(ctx context.Context) func(*testing.T) assocExists = `SELECT EXISTS(SELECT 1 FROM uo_vuln JOIN update_operation uo ON (uo_vuln.uo = uo.id) WHERE uo.ref = $1::uuid);` ) var exists bool - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) for _, op := range e.updateOps { _, err := e.s.DeleteUpdateOperations(ctx, op.Ref) if err != nil { diff --git a/datastore/postgres/updatevulnerabilities.go b/datastore/postgres/updatevulnerabilities.go index 5b58f9383..de8d148b2 100644 --- a/datastore/postgres/updatevulnerabilities.go +++ b/datastore/postgres/updatevulnerabilities.go @@ -5,6 +5,7 @@ import ( "context" "crypto/md5" "fmt" + "log/slog" "strconv" "strings" "time" @@ -13,7 +14,6 @@ import ( "github.com/jackc/pgx/v5" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/datastore" @@ -48,7 +48,6 @@ var ( // UpdateVulnerabilitiesIter implements vulnstore.Updater. func (s *MatcherStore) UpdateVulnerabilitiesIter(ctx context.Context, updater string, fp driver.Fingerprint, it datastore.VulnerabilityIter) (uuid.UUID, error) { - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/MatcherStore.UpdateVulnerabilitiesIter") return s.updateVulnerabilities(ctx, updater, fp, it, nil) } @@ -58,7 +57,6 @@ func (s *MatcherStore) UpdateVulnerabilitiesIter(ctx context.Context, updater st // provided vulnerabilities and computes a diff comprising the removed // and added vulnerabilities for this UpdateOperation. func (s *MatcherStore) UpdateVulnerabilities(ctx context.Context, updater string, fp driver.Fingerprint, vulns []*claircore.Vulnerability) (uuid.UUID, error) { - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/MatcherStore.UpdateVulnerabilities") iterVulns := func(yield func(*claircore.Vulnerability, error) bool) { for i := range vulns { if !yield(vulns[i], nil) { @@ -81,7 +79,6 @@ func (s *MatcherStore) UpdateVulnerabilities(ctx context.Context, updater string // - Insert the new vulnerabilities // - Associate new vulnerabilities with new updateOperation func (s *MatcherStore) DeltaUpdateVulnerabilities(ctx context.Context, updater string, fingerprint driver.Fingerprint, vulns []*claircore.Vulnerability, deletedVulns []string) (uuid.UUID, error) { - ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/MatcherStore.DeltaUpdateVulnerabilities") iterVulns := func(yield func(*claircore.Vulnerability, error) bool) { for i := range vulns { if !yield(vulns[i], nil) { @@ -171,12 +168,11 @@ func (s *MatcherStore) updateVulnerabilities(ctx context.Context, updater string updateVulnerabilitiesCounter.WithLabelValues("create", strconv.FormatBool(delta)).Add(1) updateVulnerabilitiesDuration.WithLabelValues("create", strconv.FormatBool(delta)).Observe(time.Since(start).Seconds()) - zlog.Debug(ctx). - Str("ref", ref.String()). - Msg("update_operation created") + log := slog.With("ref", ref) + log.DebugContext(ctx, "update_operation created") if delta { - ctx = zlog.ContextWithValues(ctx, "mode", "delta") + log = log.With("mode", "delta") // Get existing vulns // The reason this still works even though the new update_operation // is already created is because the latest_update_operation view isn't updated until @@ -312,11 +308,9 @@ func (s *MatcherStore) updateVulnerabilities(ctx context.Context, updater string return uuid.Nil, fmt.Errorf("could not refresh latest_update_operations: %w", err) } - zlog.Debug(ctx). - Str("ref", ref.String()). - Int("skipped", skipCt). - Int("inserted", vulnCt-skipCt). - Msg("update_operation committed") + log.DebugContext(ctx, "update_operation committed", + "skipped", skipCt, + "inserted", vulnCt-skipCt) return ref, nil } diff --git a/datastore/postgres/updatevulnerabilities_test.go b/datastore/postgres/updatevulnerabilities_test.go index 69f218b90..09b23906d 100644 --- a/datastore/postgres/updatevulnerabilities_test.go +++ b/datastore/postgres/updatevulnerabilities_test.go @@ -1,15 +1,14 @@ package postgres import ( - "context" "testing" "github.com/google/uuid" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/datastore" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" pgtest "github.com/quay/claircore/test/postgres" ) @@ -29,7 +28,7 @@ type op struct { func TestGetLatestVulnerabilities(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) cases := []latestVulnTestCase{ { @@ -297,6 +296,7 @@ func TestGetLatestVulnerabilities(t *testing.T) { // run test cases for _, tc := range cases { t.Run(tc.TestName, func(t *testing.T) { + ctx := test.Logging(t, ctx) _, err := store.DeltaUpdateVulnerabilities(ctx, tc.Updater, driver.Fingerprint(uuid.New().String()), tc.FirstOp.vulns, tc.FirstOp.deletedVulns) if err != nil { t.Fatalf("failed to perform update for first op: %v", err) diff --git a/debian/distributionscanner.go b/debian/distributionscanner.go index 72f4bbb57..f2f643947 100644 --- a/debian/distributionscanner.go +++ b/debian/distributionscanner.go @@ -5,14 +5,13 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "regexp" "runtime/trace" "strconv" "strings" "unicode" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/osrelease" @@ -39,18 +38,15 @@ func (*DistributionScanner) Kind() string { return "distribution" } // Scan implements [indexer.DistributionScanner]. func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "debian/DistributionScanner.Scan", - "version", ds.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + log := slog.With("version", ds.Version(), "layer", l.Hash.String()) + log.DebugContext(ctx, "start") + defer log.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { return nil, fmt.Errorf("debian: unable to open layer: %w", err) } - d, err := findDist(ctx, sys) + d, err := findDist(ctx, log, sys) if err != nil { return nil, err } @@ -60,20 +56,19 @@ func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([] return []*claircore.Distribution{d}, nil } -func findDist(ctx context.Context, sys fs.FS) (*claircore.Distribution, error) { +func findDist(ctx context.Context, log *slog.Logger, sys fs.FS) (*claircore.Distribution, error) { f, err := sys.Open(osrelease.Path) switch { case errors.Is(err, nil): case errors.Is(err, fs.ErrNotExist): - zlog.Debug(ctx).Msg("no os-release file") + log.DebugContext(ctx, "no os-release file") return nil, nil default: return nil, fmt.Errorf("debian: unexpected error: %w", err) } kv, err := osrelease.Parse(ctx, f) if err != nil { - zlog.Info(ctx). - Err(err).Msg("malformed os-release file") + log.InfoContext(ctx, "malformed os-release file", "reason", err) return nil, nil } if kv[`ID`] != `debian` { @@ -89,14 +84,12 @@ func findDist(ctx context.Context, sys fs.FS) (*claircore.Distribution, error) { name = strings.TrimFunc(ver.FindString(kv[`VERSION`]), func(r rune) bool { return !unicode.IsLetter(r) }) } if name == "" || idstr == "" { - zlog.Info(ctx). - Err(err).Msg("malformed os-release file") + log.InfoContext(ctx, "malformed os-release file", "reason", err) return nil, nil } id, err := strconv.ParseInt(idstr, 10, 32) if err != nil { - zlog.Info(ctx). - Err(err).Msg("malformed os-release file") + log.InfoContext(ctx, "malformed os-release file", "reason", err) return nil, nil } return mkDist(name, int(id)), nil diff --git a/debian/distributionscanner_test.go b/debian/distributionscanner_test.go index 5a2241da4..afcdde75e 100644 --- a/debian/distributionscanner_test.go +++ b/debian/distributionscanner_test.go @@ -1,19 +1,18 @@ package debian import ( - "context" "io/fs" + "log/slog" "os" "path/filepath" "regexp" "testing" - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) func TestDistributionScanner(t *testing.T) { ver := regexp.MustCompile(`^\d+ \(\w+\)$`) - ctx := zlog.Test(context.Background(), t) ents, err := os.ReadDir(`testdata/dist`) if err != nil { t.Fatal(err) @@ -31,8 +30,9 @@ func TestDistributionScanner(t *testing.T) { for tcDir, tcEnts := range testCase { for _, e := range tcEnts { t.Run(e.Name(), func(t *testing.T) { + ctx := test.Logging(t) sys := os.DirFS(filepath.Join(tcDir, e.Name())) - d, err := findDist(ctx, sys) + d, err := findDist(ctx, slog.Default(), sys) if err != nil { t.Error(err) } diff --git a/debian/matcher.go b/debian/matcher.go index b13754086..28f590adf 100644 --- a/debian/matcher.go +++ b/debian/matcher.go @@ -40,7 +40,7 @@ func (*Matcher) Query() []driver.MatchConstraint { return []driver.MatchConstraint{ driver.DistributionDID, driver.DistributionName, - driver.DistributionVersion, + driver.DistributionVersionID, } } diff --git a/debian/matcher_integration_test.go b/debian/matcher_integration_test.go index fdcae16d1..7b157cb04 100644 --- a/debian/matcher_integration_test.go +++ b/debian/matcher_integration_test.go @@ -1,7 +1,6 @@ package debian import ( - "context" "encoding/json" "fmt" "net/http" @@ -12,14 +11,13 @@ import ( "path/filepath" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/datastore/postgres" "github.com/quay/claircore/internal/matcher" "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/libvuln/updates" "github.com/quay/claircore/pkg/ctxlock/v2" + "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" pgtest "github.com/quay/claircore/test/postgres" ) @@ -37,7 +35,7 @@ func TestMain(m *testing.M) { // CVE data func TestMatcherIntegration(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) pool := pgtest.TestMatcherDB(ctx, t) store := postgres.NewMatcherStore(pool) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/debian/parser.go b/debian/parser.go index e9f6f0952..f1e6b03db 100644 --- a/debian/parser.go +++ b/debian/parser.go @@ -5,8 +5,7 @@ import ( "encoding/json" "fmt" "io" - - "github.com/quay/zlog" + "log/slog" "github.com/quay/claircore" ) @@ -34,8 +33,7 @@ type ReleaseData struct { // Parse implements [driver.Parser]. func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "debian/Updater.Parse") - zlog.Info(ctx).Msg("starting parse") + slog.InfoContext(ctx, "starting parse") defer r.Close() var vulnsJSON JSONData diff --git a/debian/purl.go b/debian/purl.go new file mode 100644 index 000000000..9cf612fcc --- /dev/null +++ b/debian/purl.go @@ -0,0 +1,69 @@ +package debian + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for Debian packages. + PURLType = "deb" + // PURLNamespace is the namespace of Debian packages. + PURLNamespace = "debian" + // PURLDistroQualifier is the qualifier key for the distribution. + PURLDistroQualifier = "distro" +) + +// GeneratePURL generates a PURL for a Debian package in the format: +// pkg:deb/debian/@?arch=&distro=debian- +func GeneratePURL(ctx context.Context, r *claircore.IndexRecord) (packageurl.PackageURL, error) { + var distro string + if r.Distribution != nil { + // This completely ignores the version code name e.g. "debian-13". + distro = "debian-" + r.Distribution.VersionID + } + return packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: r.Package.Name, + Version: r.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": r.Package.Arch, + PURLDistroQualifier: distro, + }), + }, nil +} + +// ParsePURL parses a PURL for a Debian package into a list of IndexRecords. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + dq := purl.Qualifiers.Map()[PURLDistroQualifier] + distroParts := strings.SplitN(dq, "-", 2) + if len(distroParts) != 2 { + return nil, fmt.Errorf("invalid distro PURL: %s", dq) + } + _, err := strconv.Atoi(distroParts[1]) + if err != nil { + return nil, fmt.Errorf("invalid distro version: %s", distroParts[1]) + } + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Arch: purl.Qualifiers.Map()["arch"], + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{ + Name: "Debian GNU/Linux", + VersionID: distroParts[1], + DID: "debian", + }, + }, + }, nil +} diff --git a/debian/purl_test.go b/debian/purl_test.go new file mode 100644 index 000000000..c342a68e2 --- /dev/null +++ b/debian/purl_test.go @@ -0,0 +1,68 @@ +package debian + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordDebian(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1.8-6", + Arch: "x86_64", + Kind: claircore.BINARY, + PackageDB: "deb:/var/lib/dpkg/status", + }, + Distribution: &claircore.Distribution{ + Name: "Debian GNU/Linux", + VersionID: "11", + VersionCodeName: "bullseye", + DID: "debian", + Version: "11 (bullseye)", + PrettyName: "Debian GNU/Linux 11 (bullseye)", + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + fmt.Println(p.String()) + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParsePURL: %v", err) + } + if diff := cmp.Diff([]*claircore.IndexRecord{tc.ir}, got, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-want +got):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore PackageDB and Filepath as they are not currently used in the matching. + cmpopts.IgnoreFields(claircore.Package{}, "PackageDB", "Filepath"), + // The distribution that we can decipher from the PURL only has the fields that + // are used in the matching; DID, Name and VersionID. + cmpopts.IgnoreFields(claircore.Distribution{}, "VersionCodeName", "Version", "PrettyName"), +} diff --git a/debian/updater.go b/debian/updater.go index 7522c3515..81ea48a39 100644 --- a/debian/updater.go +++ b/debian/updater.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "net/textproto" "net/url" @@ -15,8 +16,6 @@ import ( "strconv" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/pkg/tmp" ) @@ -184,28 +183,25 @@ Listing: dist = strings.Trim(dist, "/") rf, err := dir.Parse(path.Join(dist, `Release`)) if err != nil { - zlog.Info(ctx). - Err(err). - Stringer("context", dir). - Str("target", path.Join(dist, `Release`)). - Msg("unable to construct URL") + slog.InfoContext(ctx, "unable to construct URL", + "reason", err, + "context", dir, + "target", path.Join(dist, `Release`)) continue } req, err := http.NewRequestWithContext(ctx, http.MethodGet, rf.String(), nil) if err != nil { - zlog.Info(ctx). - Err(err). - Stringer("url", rf). - Msg("unable to construct request") + slog.InfoContext(ctx, "unable to construct request", + "reason", err, + "url", rf) continue } req.Header.Set("range", "bytes=0-512") res, err := f.c.Do(req) if err != nil { - zlog.Info(ctx). - Err(err). - Stringer("url", rf). - Msg("unable to do request") + slog.InfoContext(ctx, "unable to do request", + "reason", err, + "url", rf) continue } buf.Reset() @@ -216,31 +212,31 @@ Listing: case http.StatusNotFound: // Probably extremely old, it's fine. continue default: - zlog.Info(ctx). - Str("status", res.Status). - Stringer("url", rf). - Msg("unexpected response") + slog.InfoContext(ctx, "unexpected response", + "status", res.Status, + "url", rf) continue } tp := textproto.NewReader(bufio.NewReader(io.MultiReader(&buf, bytes.NewReader([]byte("\r\n\r\n"))))) h, err := tp.ReadMIMEHeader() if err != nil { - zlog.Info(ctx).Err(err).Msg("unable to read MIME-ish headers") + slog.InfoContext(ctx, "unable to read MIME-ish headers", + "reason", err) continue } sv := h.Get("Version") if sv == "" { - zlog.Debug(ctx).Str("dist", dist).Msg("no version assigned, skipping") + slog.DebugContext(ctx, "no version assigned, skipping", "dist", dist) continue } vs := strings.Split(sv, ".") if len(vs) == 1 { - zlog.Debug(ctx).Str("dist", dist).Msg("no version assigned, skipping") + slog.DebugContext(ctx, "no version assigned, skipping", "dist", dist) continue } ver, err := strconv.ParseInt(vs[0], 10, 32) if err != nil { - zlog.Info(ctx).Err(err).Msg("unable to parse version") + slog.InfoContext(ctx, "unable to parse version", "reason", err) continue } @@ -275,7 +271,6 @@ func (u *updater) Name() string { // Configure implements [driver.Configurable]. func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, "component", "debian/Updater.Configure") u.c = c var cfg UpdaterConfig if err := f(&cfg); err != nil { @@ -283,14 +278,13 @@ func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c * } if cfg.DistsURL != "" || cfg.OVALURL != "" { - zlog.Error(ctx).Msg("configured with deprecated URLs") + slog.ErrorContext(ctx, "configured with deprecated URLs") return fmt.Errorf("debian: neither url nor dists_url should be used anymore; use json_url and dists_urls instead") } if cfg.JSONURL != "" { u.jsonURL = cfg.JSONURL - zlog.Info(ctx). - Msg("configured JSON database URL") + slog.InfoContext(ctx, "configured JSON database URL") } return nil @@ -298,9 +292,7 @@ func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c * // Fetch implements [driver.Fetcher]. func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "debian/Updater.Fetch", - "database", u.jsonURL) + log := slog.With("database", u.jsonURL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.jsonURL, nil) if err != nil { @@ -324,7 +316,7 @@ func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io switch resp.StatusCode { case http.StatusOK: if fingerprint == "" || fp != string(fingerprint) { - zlog.Info(ctx).Msg("fetching latest JSON database") + log.InfoContext(ctx, "fetching latest JSON database") break } fallthrough @@ -343,7 +335,7 @@ func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io defer func() { if !success { if err := f.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + log.WarnContext(ctx, "unable to close spool", "reason", err) } } }() @@ -353,7 +345,7 @@ func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io if _, err := f.Seek(0, io.SeekStart); err != nil { return nil, "", fmt.Errorf("failed to seek body: %w", err) } - zlog.Info(ctx).Msg("fetched latest json database successfully") + log.InfoContext(ctx, "fetched latest json database successfully") success = true return f, driver.Fingerprint(fp), err diff --git a/docs/contributor/logging.md b/docs/contributor/logging.md index d43495175..91c21e772 100644 --- a/docs/contributor/logging.md +++ b/docs/contributor/logging.md @@ -1,7 +1,5 @@ # Logging -All the logging in claircore is done with [zerolog][doc] via `context.Context` -values. The `zlog` package takes OpenTelemetry labels and attaches them to -`zerolog` events. +All the logging in claircore is done with [`log/slog`][doc]. This allows for claircore's logging to be used consistently throughout all the packages without having unintended prints to stderr. @@ -9,20 +7,28 @@ packages without having unintended prints to stderr. ## How to Log ### Adding Context -In a function, use `zlog` to add key-value pairs of any relevant context: + +Within a function, create a `*slog.Logger` to add key-value pairs of any +relevant context: ```go {{#include ../logger_test.go:kvs}} ``` -Alternatively, the `go.opentelemetry.io/otel/baggage` package can be used for -more explicit control around the baggage values. +Alternatively, the `github.com/quay/claircore/toolkit/log` package has a helper +for associating multiple `slog.Attr` values with a `context.Context`. +```go +{{#include ../logger_test.go:ctx}} +``` + +Programs using claircore should configure their `slog.Handler` to extract the +values using the key in `github.com/quay/claircore/toolkit/log` or use the +`Wrap` helper. ### Logging style #### Constant Messages -Zerolog emits lines when the `Msg` or `Msgf` methods are called. Project style -is to _not_ use `Msgf`. Any variable data should be set as key-value pairs on -the Event object. +Project style is to use string constants for all logging calls. Any variable +data should be passed as additional arguments to the `slog` methods. For example, don't do this: ```go @@ -66,14 +72,6 @@ times in the logs. Claircore attempts to have consistent leveled logging. The rules for figuring out what level to use is: -* Panic - - There's some occurrence that means the process won't work correctly. - -* Fatal - - Unused, because it prevents defers from running. - * Error Something unexpected occurred and the process can continue, but a @@ -96,4 +94,11 @@ out what level to use is: and exiting functions, stepping through a function, or specific file paths used while work is being done. -[doc]: https://pkg.go.dev/github.com/rs/zerolog@v1.26.0 +In exceptional situations, levels of `slog.LevelDebug-4` or `slog.LevelError+4` +may be used for "Trace" and "Emergency" logs. There are no predefined constants +for these. + +#### Contexts +All logging calls should use the "Context" variant whenever possible. + +[doc]: https://pkg.go.dev/log/slog diff --git a/docs/logger_test.go b/docs/logger_test.go index 47abe5f01..5dc34c3e3 100644 --- a/docs/logger_test.go +++ b/docs/logger_test.go @@ -2,26 +2,35 @@ package docs_test import ( "context" + "fmt" + "log/slog" "time" - "github.com/quay/zlog" + "github.com/quay/claircore/toolkit/log" ) // Example_logger is an example annotated for inclusion in the prose // documentation. func Example_logger() { ctx := context.Background() - // ANCHOR: kvs - ctx = zlog.ContextWithValues(ctx, - "component", "Example.Logger") - // ANCHOR_END: kvs + { + // ANCHOR: kvs + log := slog.With("contextual", "value") + log.DebugContext(ctx, "message") + // ANCHOR_END: kvs + } + + { + // ANCHOR: ctx + ctx := log.With(ctx, "contextual", "value") + slog.DebugContext(ctx, "message") + // ANCHOR_END: ctx + } // ANCHOR: bad_example - zlog.Info(ctx).Msgf("done at: %v", time.Now()) + slog.InfoContext(ctx, fmt.Sprintf("done at: %v", time.Now())) // ANCHOR_END: bad_example // ANCHOR: good_example - zlog.Info(ctx). - Time("time", time.Now()). - Msgf("done") + slog.InfoContext(ctx, "done", "time", time.Now()) // ANCHOR_END: good_example } diff --git a/dpkg/distroless_scanner.go b/dpkg/distroless_scanner.go index 3abfa4063..261692781 100644 --- a/dpkg/distroless_scanner.go +++ b/dpkg/distroless_scanner.go @@ -7,12 +7,11 @@ import ( "fmt" "io" "io/fs" + "log/slog" "net/textproto" "path/filepath" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -55,12 +54,8 @@ func (ps *DistrolessScanner) Kind() string { return distrolessKind } func (ps *DistrolessScanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "dpkg/DistrolessScanner.Scan", - "version", ps.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := layer.FS() if err != nil { @@ -73,7 +68,7 @@ func (ps *DistrolessScanner) Scan(ctx context.Context, layer *claircore.Layer) ( return err } if d.Name() == "status.d" && d.IsDir() { - zlog.Debug(ctx).Str("path", p).Msg("found potential distroless dpkg db directory") + slog.DebugContext(ctx, "found potential distroless dpkg db directory", "path", p) dbFiles, err := fs.ReadDir(sys, p) if err != nil { return fmt.Errorf("error reading DB directory: %w", err) @@ -81,8 +76,8 @@ func (ps *DistrolessScanner) Scan(ctx context.Context, layer *claircore.Layer) ( for _, f := range dbFiles { pkgCt := 0 fn := filepath.Join(p, f.Name()) - ctx = zlog.ContextWithValues(ctx, "database-file", fn) - zlog.Debug(ctx).Msg("examining package database") + log := slog.With("database-file", fn) + log.DebugContext(ctx, "examining package database") db, err := sys.Open(fn) if err != nil { return fmt.Errorf("reading database files from layer failed: %w", err) @@ -124,14 +119,12 @@ func (ps *DistrolessScanner) Scan(ctx context.Context, layer *claircore.Layer) ( case errors.Is(err, io.EOF): default: if _, ok := err.(textproto.ProtocolError); ok { - zlog.Warn(ctx).Err(err).Msg("unable to read DB entry") + log.WarnContext(ctx, "unable to read DB entry", "reason", err) goto Restart } - zlog.Warn(ctx).Err(err).Msg("error reading DB file") + log.WarnContext(ctx, "error reading DB file", "reason", err) } - zlog.Debug(ctx). - Int("count", pkgCt). - Msg("found packages") + slog.DebugContext(ctx, "found packages", "count", pkgCt) } } return nil diff --git a/dpkg/distroless_scanner_test.go b/dpkg/distroless_scanner_test.go index 37451c06c..c6ad7e00e 100644 --- a/dpkg/distroless_scanner_test.go +++ b/dpkg/distroless_scanner_test.go @@ -1,18 +1,16 @@ package dpkg import ( - "context" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" ) func TestDistrolessLayer(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) want := []*claircore.Package{ { Name: "base-files", diff --git a/dpkg/scanner.go b/dpkg/scanner.go index 505e2057b..99f5b5020 100644 --- a/dpkg/scanner.go +++ b/dpkg/scanner.go @@ -10,14 +10,13 @@ import ( "fmt" "io" "io/fs" + "log/slog" "net/textproto" "path/filepath" "runtime/trace" "slices" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -60,12 +59,8 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco // Preamble defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "dpkg/Scanner.Scan", - "version", ps.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := layer.FS() if err != nil { @@ -91,7 +86,7 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco if err := fs.WalkDir(sys, ".", walk); err != nil { return nil, err } - zlog.Debug(ctx).Msg("scanned for possible databases") + slog.DebugContext(ctx, "scanned for possible databases") // If we didn't find anything, this loop is completely skipped. var pkgs []*claircore.Package @@ -109,7 +104,7 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco switch { case err == nil: case errors.Is(err, errNotDpkgDB): - zlog.Info(ctx).AnErr("reason", err).Msg("skipping possible database") + slog.InfoContext(ctx, "skipping possible database", "reason", err) default: return nil, err } @@ -158,7 +153,7 @@ func sortpkg(a, b *claircore.Package) int { // // "Found"is used for scratch space and results are appended to the slice pointed to by "out". func loadDatabase(ctx context.Context, sys fs.FS, dir string, found *packages, out *[]*claircore.Package) error { - zlog.Debug(ctx).Msg("examining package database") + slog.DebugContext(ctx, "examining package database") // We want the "status" file. fn := filepath.Join(dir, "status") @@ -166,9 +161,7 @@ func loadDatabase(ctx context.Context, sys fs.FS, dir string, found *packages, o switch { case errors.Is(err, nil): case errors.Is(err, fs.ErrNotExist): - zlog.Debug(ctx). - Str("filename", fn). - Msg("false positive") + slog.DebugContext(ctx, "false positive", "filename", fn) return err default: return fmt.Errorf("reading status file from layer failed: %w", err) @@ -195,9 +188,7 @@ func loadDatabase(ctx context.Context, sys fs.FS, dir string, found *packages, o } p, ok := found.bin[k] if !ok { - zlog.Debug(ctx). - Str("package", k). - Msg("extra metadata found, ignoring") + slog.DebugContext(ctx, "extra metadata found, ignoring", "package", k) continue } f, err := sys.Open(n) @@ -208,17 +199,14 @@ func loadDatabase(ctx context.Context, sys fs.FS, dir string, found *packages, o _, err = io.Copy(hash, f) f.Close() if err != nil { - zlog.Warn(ctx). - Err(err). - Str("package", n). - Msg("unable to read package metadata") + slog.WarnContext(ctx, "unable to read package metadata", + "package", n, + "reason", err) continue } p.RepositoryHint = hex.EncodeToString(hash.Sum(nil)) } - zlog.Debug(ctx). - Int("count", len(found.bin)). - Msg("found packages") + slog.DebugContext(ctx, "found packages", "count", len(found.bin)) for _, pkg := range found.bin { *out = append(*out, pkg) @@ -322,7 +310,7 @@ Restart: switch { case errors.Is(err, io.EOF): default: - zlog.Warn(ctx).Err(err).Msg("unable to read entry") + slog.WarnContext(ctx, "unable to read entry", "reason", err) goto Restart } return nil diff --git a/dpkg/scanner_test.go b/dpkg/scanner_test.go index 7af0768b3..ec55e8418 100644 --- a/dpkg/scanner_test.go +++ b/dpkg/scanner_test.go @@ -3,7 +3,6 @@ package dpkg import ( "archive/tar" "bufio" - "context" "errors" "io" "net/textproto" @@ -12,7 +11,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" @@ -825,7 +823,7 @@ func TestScanner(t *testing.T) { }, } slices.SortFunc(want, sortpkg) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) l := test.RealizeLayer(ctx, t, test.LayerRef{ Registry: "docker.io", Name: "library/ubuntu", @@ -846,7 +844,7 @@ func TestExtraMetadata(t *testing.T) { t.Parallel() mod := test.Modtime(t, "scanner_test.go") layerfile := test.GenerateFixture(t, `extrametadata.layer`, mod, extraMetadataSetup) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) var l claircore.Layer var s Scanner @@ -964,7 +962,7 @@ func TestKeyringPackage(t *testing.T) { func TestParsedSource(t *testing.T) { t.Parallel() const filename = `testdata/postgresql.status` - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) db, err := os.Open(filename) if err != nil { @@ -998,7 +996,7 @@ func TestParsedSource(t *testing.T) { func TestNotDB(t *testing.T) { t.Parallel() const filename = `testdata/vcpkg.status` - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) db, err := os.Open(filename) if err != nil { diff --git a/enricher/cvss/cvss.go b/enricher/cvss/cvss.go index 5f032f1e6..d273c0d90 100644 --- a/enricher/cvss/cvss.go +++ b/enricher/cvss/cvss.go @@ -9,14 +9,13 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "sort" "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/enricher" "github.com/quay/claircore/libvuln/driver" @@ -110,8 +109,6 @@ func (*Enricher) Name() string { return name } // FetchEnrichment implements driver.EnrichmentUpdater. func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/cvss/Enricher/FetchEnrichment") - // year → sha256 prev := make(map[int]string) if err := json.Unmarshal([]byte(hint), &prev); err != nil && hint != "" { @@ -126,10 +123,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) if err != nil { return nil, hint, err } - zlog.Debug(ctx). - Int("year", y). - Stringer("url", u). - Msg("fetching meta file") + slog.DebugContext(ctx, "fetching meta file", "year", y, "url", u) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, hint, err @@ -148,20 +142,17 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) if err := mf.Parse(&buf); err != nil { return nil, hint, err } - zlog.Debug(ctx). - Int("year", y). - Stringer("url", u). - Time("mod", mf.LastModified). - Msg("parsed meta file") + slog.DebugContext(ctx, "parsed meta file", + "year", y, + "url", u, + "mod", mf.LastModified) cur[y] = strings.ToUpper(mf.SHA256) } doFetch := false for _, y := range yrs { if prev[y] != cur[y] { - zlog.Info(ctx). - Int("year", y). - Msg("change detected") + slog.InfoContext(ctx, "change detected", "year", y) doFetch = true break } @@ -178,7 +169,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) defer func() { if !success { if err := out.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + slog.WarnContext(ctx, "unable to close spool", "reason", err) } } }() @@ -195,10 +186,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) if err != nil { return nil, hint, fmt.Errorf("unable to create request: %w", err) } - zlog.Debug(ctx). - Int("year", y). - Stringer("url", u). - Msg("requesting json") + slog.DebugContext(ctx, "requesting json", "year", y, "url", u) res, err := e.c.Do(req) if err != nil { return nil, hint, fmt.Errorf("unable to do request: %w", err) @@ -232,7 +220,6 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) // ParseEnrichment implements driver.EnrichmentUpdater. func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]driver.EnrichmentRecord, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/cvss/Enricher/ParseEnrichment") // Our Fetch method actually has all the smarts w/r/t to constructing the // records, so this is just decoding in a loop. defer rc.Close() @@ -244,9 +231,7 @@ func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]dri ret = append(ret, driver.EnrichmentRecord{}) err = dec.Decode(&ret[len(ret)-1]) } - zlog.Debug(ctx). - Int("count", len(ret)). - Msg("decoded enrichments") + slog.DebugContext(ctx, "decoded enrichments", "count", len(ret)) if err != nil && !errors.Is(err, io.EOF) { return nil, err } @@ -255,8 +240,6 @@ func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]dri // Enrich implements driver.Enricher. func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *claircore.VulnerabilityReport) (string, []json.RawMessage, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/cvss/Enricher/Enrich") - // We return any CVSS blobs for CVEs mentioned in the free-form parts of the // vulnerability. m := make(map[string][]json.RawMessage) @@ -264,8 +247,7 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla erCache := make(map[string][]driver.EnrichmentRecord) for id, v := range r.Vulnerabilities { t := make(map[string]struct{}) - ctx := zlog.ContextWithValues(ctx, - "vuln", v.Name) + log := slog.With("vuln", v.Name) for _, elem := range []string{ v.Name, v.Links, @@ -281,9 +263,7 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla for m := range t { ts = append(ts, m) } - zlog.Debug(ctx). - Strs("cve", ts). - Msg("found CVEs") + log.DebugContext(ctx, "found CVEs", "cve", ts) sort.Strings(ts) cveKey := strings.Join(ts, "_") @@ -296,9 +276,7 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla } erCache[cveKey] = rec } - zlog.Debug(ctx). - Int("count", len(rec)). - Msg("found records") + log.DebugContext(ctx, "found records", "count", len(rec)) for _, r := range rec { m[id] = append(m[id], r.Enrichment) } diff --git a/enricher/cvss/cvss_test.go b/enricher/cvss/cvss_test.go index 47d2d8b10..e22bcb7fd 100644 --- a/enricher/cvss/cvss_test.go +++ b/enricher/cvss/cvss_test.go @@ -17,15 +17,15 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" ) func TestConfigure(t *testing.T) { t.Parallel() - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) tt := []configTestcase{ { Name: "None", @@ -91,7 +91,7 @@ type configTestcase struct { func (tc configTestcase) Run(ctx context.Context) func(*testing.T) { e := &Enricher{} return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) f := tc.Config if f == nil { f = noopConfig @@ -111,7 +111,7 @@ func noopConfig(_ any) error { return nil } func TestFetch(t *testing.T) { t.Parallel() - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) srv := mockServer(t) tt := []fetchTestcase{ { @@ -175,7 +175,7 @@ type fetchTestcase struct { func (tc fetchTestcase) Run(ctx context.Context, srv *httptest.Server) func(*testing.T) { e := &Enricher{} return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f := func(i any) error { cfg, ok := i.(*Config) if !ok { @@ -234,7 +234,7 @@ func mockServer(t *testing.T) *httptest.Server { func TestParse(t *testing.T) { t.Parallel() - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) srv := mockServer(t) tt := []parseTestcase{ { @@ -254,7 +254,7 @@ type parseTestcase struct { func (tc parseTestcase) Run(ctx context.Context, srv *httptest.Server) func(*testing.T) { e := &Enricher{} return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) f := func(i any) error { cfg, ok := i.(*Config) if !ok { @@ -285,7 +285,7 @@ func (tc parseTestcase) Run(ctx context.Context, srv *httptest.Server) func(*tes func TestEnrich(t *testing.T) { t.Parallel() - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) feedIn, err := os.Open("testdata/feed.json") if err != nil { t.Fatal(err) diff --git a/enricher/cvss/feed.go b/enricher/cvss/feed.go index 5f61c870a..5a959fdbf 100644 --- a/enricher/cvss/feed.go +++ b/enricher/cvss/feed.go @@ -4,10 +4,9 @@ import ( "context" "encoding/json" "io" + "log/slog" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" ) @@ -59,7 +58,6 @@ func newItemFeed(year int, r io.Reader) (*itemFeed, error) { // entire serialization in memory. func (f *itemFeed) WriteCVSS(ctx context.Context, w io.Writer) error { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/cvss/itemFeed/WriteCVSS") // Use records directly because our parse step doesn't actually parse // anything -- the Fetch step rips out the relevant JSON. var skip, wrote uint @@ -104,10 +102,9 @@ func (f *itemFeed) WriteCVSS(ctx context.Context, w io.Writer) error { } wrote++ } - zlog.Debug(ctx). - Int("year", f.year). - Uint("skip", skip). - Uint("wrote", wrote). - Msg("wrote cvss items") + slog.DebugContext(ctx, "wrote cvss items", + "year", f.year, + "skip", skip, + "wrote", wrote) return nil } diff --git a/enricher/cvss/feed_test.go b/enricher/cvss/feed_test.go index cea9bc562..824c5b971 100644 --- a/enricher/cvss/feed_test.go +++ b/enricher/cvss/feed_test.go @@ -5,11 +5,11 @@ import ( "os" "testing" - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) func TestFeedIngest(t *testing.T) { - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) in, err := os.Open("testdata/feed.json") if err != nil { t.Fatal(err) diff --git a/enricher/epss/epss.go b/enricher/epss/epss.go index 8bbabd23c..6e32c84f5 100644 --- a/enricher/epss/epss.go +++ b/enricher/epss/epss.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "path" @@ -17,8 +18,6 @@ import ( "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/enricher" "github.com/quay/claircore/internal/httputil" @@ -76,7 +75,6 @@ func NewFactory() driver.UpdaterSetFactory { } func (e *Enricher) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/epss/Enricher/Configure") var cfg Config e.c = c e.feedPath = currentFeedURL() @@ -94,7 +92,7 @@ func (e *Enricher) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c // only .gz file is supported if strings.HasSuffix(*cfg.URL, ".gz") { - //overwrite feedPath is cfg provides another baseURL path + // overwrite feedPath is cfg provides another baseURL path e.feedPath = *cfg.URL } else { return fmt.Errorf("invalid baseURL root: expected a '.gz' file, but got '%q'", *cfg.URL) @@ -106,8 +104,6 @@ func (e *Enricher) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c // FetchEnrichment implements driver.EnrichmentUpdater. func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/epss/Enricher/FetchEnrichment") - out, err := tmp.NewFile("", "epss.") if err != nil { return nil, "", err @@ -116,7 +112,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F defer func() { if !success { if err := out.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + slog.WarnContext(ctx, "unable to close spool", "reason", err) } } }() @@ -140,7 +136,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F if etag := resp.Header.Get("etag"); etag != "" { newFingerprint = driver.Fingerprint(etag) if prevFingerprint == newFingerprint { - zlog.Info(ctx).Str("fingerprint", string(newFingerprint)).Msg("file unchanged; skipping processing") + slog.InfoContext(ctx, "file unchanged; skipping processing", "fingerprint", string(newFingerprint)) return nil, prevFingerprint, nil } newFingerprint = driver.Fingerprint(etag) @@ -207,7 +203,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F r, err := newItemFeed(record, modelVersion, date) if err != nil { - zlog.Warn(ctx).Err(err).Msg("skipping invalid record") + slog.WarnContext(ctx, "skipping invalid record", "reason", err) continue } @@ -217,7 +213,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F totalCVEs++ } - zlog.Info(ctx).Int("totalCVEs", totalCVEs).Msg("processed CVEs") + slog.InfoContext(ctx, "processed CVEs", "totalCVEs", totalCVEs) if _, err := out.Seek(0, io.SeekStart); err != nil { return nil, newFingerprint, fmt.Errorf("unable to reset file pointer: %w", err) } @@ -228,8 +224,6 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F // ParseEnrichment implements driver.EnrichmentUpdater. func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]driver.EnrichmentRecord, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/epss/Enricher/ParseEnrichment") - defer rc.Close() dec := json.NewDecoder(rc) @@ -244,9 +238,7 @@ func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]dri ret = append(ret, record) } - zlog.Debug(ctx). - Int("count", len(ret)). - Msg("decoded enrichments") + slog.DebugContext(ctx, "decoded enrichments", "count", len(ret)) if !errors.Is(err, io.EOF) { return nil, fmt.Errorf("error decoding enrichment records: %w", err) @@ -274,13 +266,12 @@ func currentFeedURL() string { } func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *claircore.VulnerabilityReport) (string, []json.RawMessage, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/epss/Enricher/Enrich") m := make(map[string][]json.RawMessage) erCache := make(map[string][]driver.EnrichmentRecord) for id, v := range r.Vulnerabilities { t := make(map[string]struct{}) - ctx := zlog.ContextWithValues(ctx, "vuln", v.Name) + log := slog.With("vuln", v.Name) for _, elem := range []string{ v.Name, @@ -288,13 +279,13 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla } { // Check if the element is non-empty before running the regex if elem == "" { - zlog.Debug(ctx).Str("element", elem).Msg("skipping empty element") + log.DebugContext(ctx, "skipping empty element", "element", elem) continue } matches := enricher.CVERegexp.FindAllString(elem, -1) if len(matches) == 0 { - zlog.Debug(ctx).Str("element", elem).Msg("no CVEs found in element") + log.DebugContext(ctx, "no CVEs found in element", "element", elem) continue } for _, m := range matches { @@ -304,7 +295,7 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla // Skip if no CVEs were found if len(t) == 0 { - zlog.Debug(ctx).Msg("no CVEs found in vulnerability metadata") + log.DebugContext(ctx, "no CVEs found in vulnerability metadata") continue } @@ -326,11 +317,11 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla erCache[cveKey] = rec } - zlog.Debug(ctx).Int("count", len(rec)).Msg("found records") + log.DebugContext(ctx, "found records", "count", len(rec)) // Skip if no enrichment records are found if len(rec) == 0 { - zlog.Debug(ctx).Strs("cve", ts).Msg("no enrichment records found for CVEs") + log.DebugContext(ctx, "no enrichment records found for CVEs", "cve", ts) continue } diff --git a/enricher/epss/epss_test.go b/enricher/epss/epss_test.go index d10810e58..d5d279827 100644 --- a/enricher/epss/epss_test.go +++ b/enricher/epss/epss_test.go @@ -16,15 +16,15 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" ) func TestConfigure(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) tt := []configTestcase{ { Name: "None", // No configuration provided, should use default @@ -96,7 +96,7 @@ func TestConfigure(t *testing.T) { func (tc configTestcase) Run(ctx context.Context) func(*testing.T) { e := &Enricher{} return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) f := tc.Config if f == nil { f = noopConfig @@ -114,7 +114,6 @@ func (tc configTestcase) Run(ctx context.Context) func(*testing.T) { func TestFetch(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) srv := mockServer(t) tt := []fetchTestcase{ @@ -143,6 +142,7 @@ func TestFetch(t *testing.T) { }, } + ctx := context.Background() for _, tc := range tt { t.Run(tc.Name, tc.Run(ctx, srv)) } @@ -201,7 +201,7 @@ func mockServer(t *testing.T) *httptest.Server { func (tc fetchTestcase) Run(ctx context.Context, srv *httptest.Server) func(*testing.T) { return func(t *testing.T) { e := &Enricher{} - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) configFunc := func(i any) error { cfg, ok := i.(*Config) if !ok { @@ -233,7 +233,7 @@ func (tc fetchTestcase) Run(ctx context.Context, srv *httptest.Server) func(*tes func TestParse(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) srv := mockServer(t) tt := []parseTestcase{ { @@ -253,7 +253,7 @@ type parseTestcase struct { func (tc parseTestcase) Run(ctx context.Context, srv *httptest.Server) func(*testing.T) { e := &Enricher{} return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) f := func(i any) error { cfg, ok := i.(*Config) if !ok { @@ -302,7 +302,7 @@ func (g *fakeGetter) GetEnrichment(ctx context.Context, cves []string) ([]driver func TestEnrich(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) srv := mockServer(t) e := &Enricher{} f := func(i any) error { diff --git a/enricher/kev/kev.go b/enricher/kev/kev.go index d7d4bce74..e8682b180 100644 --- a/enricher/kev/kev.go +++ b/enricher/kev/kev.go @@ -7,13 +7,12 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "net/url" "slices" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/enricher" "github.com/quay/claircore/libvuln/driver" @@ -91,8 +90,6 @@ func (*Enricher) Name() string { return name } // FetchEnrichment implements driver.EnrichmentUpdater. func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/kev/Enricher.FetchEnrichment") - req, err := http.NewRequestWithContext(ctx, http.MethodGet, e.feed.String(), nil) if err != nil { return nil, hint, fmt.Errorf("kev: unable to create request: %w", err) @@ -116,12 +113,12 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) } fallthrough case http.StatusNotModified: - zlog.Info(ctx).Msg("database unchanged since last fetch") + slog.InfoContext(ctx, "database unchanged since last fetch") return nil, hint, driver.Unchanged default: return nil, hint, fmt.Errorf("http response error: %s %d", res.Status, res.StatusCode) } - zlog.Debug(ctx).Msg("successfully requested database") + slog.DebugContext(ctx, "successfully requested database") out, err := tmp.NewFile("", "kev.") if err != nil { @@ -131,7 +128,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) defer func() { if !success { if err := out.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + slog.WarnContext(ctx, "unable to close spool", "reason", err) } } }() @@ -149,17 +146,13 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, hint driver.Fingerprint) success = true hint = driver.Fingerprint(res.Header.Get("Last-Modified")) - zlog.Debug(ctx). - Str("hint", string(hint)). - Msg("using new hint") + slog.DebugContext(ctx, "using new hint", "hint", string(hint)) return out, hint, nil } // ParseEnrichment implements driver.EnrichmentUpdater. func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]driver.EnrichmentRecord, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/kev/Enricher.ParseEnrichment") - var root Root buf := bufio.NewReader(rc) if err := json.NewDecoder(buf).Decode(&root); err != nil { @@ -196,14 +189,12 @@ func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]dri // Enrich implements driver.Enricher. func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *claircore.VulnerabilityReport) (string, []json.RawMessage, error) { - ctx = zlog.ContextWithValues(ctx, "component", "enricher/kev/Enricher.Enrich") - m := make(map[string][]json.RawMessage) erCache := make(map[string][]driver.EnrichmentRecord) for id, v := range r.Vulnerabilities { t := make(map[string]struct{}) - ctx := zlog.ContextWithValues(ctx, "vuln", v.Name) + log := slog.With("vuln", v.Name) for _, elem := range []string{ v.Name, @@ -211,13 +202,13 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla } { // Check if the element is non-empty before running the regex if elem == "" { - zlog.Debug(ctx).Str("element", elem).Msg("skipping empty element") + log.DebugContext(ctx, "skipping empty element", "element", elem) continue } matches := enricher.CVERegexp.FindAllString(elem, -1) if len(matches) == 0 { - zlog.Debug(ctx).Str("element", elem).Msg("no CVEs found in element") + log.DebugContext(ctx, "no CVEs found in element", "element", elem) continue } for _, m := range matches { @@ -227,7 +218,7 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla // Skip if no CVEs were found if len(t) == 0 { - zlog.Debug(ctx).Msg("no CVEs found in vulnerability metadata") + log.DebugContext(ctx, "no CVEs found in vulnerability metadata") continue } @@ -249,11 +240,11 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla erCache[cveKey] = rec } - zlog.Debug(ctx).Int("count", len(rec)).Msg("found records") + log.DebugContext(ctx, "found records", "count", len(rec)) // Skip if no enrichment records are found if len(rec) == 0 { - zlog.Debug(ctx).Strs("cve", ts).Msg("no enrichment records found for CVEs") + log.DebugContext(ctx, "no enrichment records found for CVEs", "cve", ts) continue } diff --git a/enricher/kev/kev_test.go b/enricher/kev/kev_test.go index 7b3f5a5a9..9ae6e30b1 100644 --- a/enricher/kev/kev_test.go +++ b/enricher/kev/kev_test.go @@ -15,10 +15,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" ) // Define a static Last-Modified for testing purposes @@ -28,7 +28,6 @@ func noopConfig(_ any) error { return nil } func TestConfigure(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) type configTestcase struct { Config func(any) error @@ -87,7 +86,7 @@ func TestConfigure(t *testing.T) { for _, tc := range tt { t.Run(tc.Name, func(t *testing.T) { e := &Enricher{} - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f := tc.Config if f == nil { f = noopConfig @@ -156,7 +155,6 @@ func mockServer(t *testing.T) *httptest.Server { func TestFetch(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) srv := mockServer(t) type fetchTestcase struct { @@ -218,7 +216,7 @@ func TestFetch(t *testing.T) { for _, tc := range tt { t.Run(tc.Name, func(t *testing.T) { e := &Enricher{} - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) configFunc := func(i any) error { cfg, ok := i.(*Config) if !ok { @@ -251,7 +249,7 @@ func TestFetch(t *testing.T) { func TestParse(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) srv := mockServer(t) e := &Enricher{} @@ -336,7 +334,7 @@ func (g fakeGetter) GetEnrichment(_ context.Context, cves []string) ([]driver.En func TestEnrich(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) srv := mockServer(t) e := &Enricher{} diff --git a/go.mod b/go.mod index e502087ce..579493606 100644 --- a/go.mod +++ b/go.mod @@ -10,36 +10,34 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/jackc/pgconn v1.14.3 - github.com/jackc/pgx/v5 v5.7.6 + github.com/jackc/pgx/v5 v5.8.0 github.com/klauspost/compress v1.18.2 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 github.com/package-url/packageurl-go v0.1.3 github.com/prometheus/client_golang v1.23.2 - github.com/quay/claircore/toolkit v1.3.0 + github.com/quay/claircore/toolkit v1.4.0 github.com/quay/claircore/updater/driver v1.0.0 github.com/quay/goval-parser v0.8.8 - github.com/quay/zlog v1.1.9 github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 - github.com/rs/zerolog v1.32.0 - github.com/spdx/tools-golang v0.5.5 + github.com/spdx/tools-golang v0.5.6 github.com/ulikunitz/xz v0.5.15 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 go.uber.org/mock v0.6.0 - golang.org/x/crypto v0.46.0 - golang.org/x/net v0.48.0 + golang.org/x/crypto v0.47.0 + golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 - golang.org/x/sys v0.39.0 - golang.org/x/text v0.32.0 + golang.org/x/sys v0.40.0 + golang.org/x/text v0.33.0 golang.org/x/time v0.14.0 - golang.org/x/tools v0.40.0 - modernc.org/sqlite v1.40.1 + golang.org/x/tools v0.41.0 + modernc.org/sqlite v1.42.2 ) require ( - github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect + github.com/anchore/go-struct-converter v0.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -53,7 +51,6 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lib/pq v1.10.2 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect @@ -65,7 +62,7 @@ require ( go.opentelemetry.io/otel/metric v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/mod v0.31.0 // indirect + golang.org/x/mod v0.32.0 // indirect google.golang.org/protobuf v1.36.8 // indirect modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/go.sum b/go.sum index 44b319b6a..fd0689b50 100644 --- a/go.sum +++ b/go.sum @@ -2,13 +2,12 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= -github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9dedlWa0OhD30= +github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -22,11 +21,8 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= @@ -48,8 +44,8 @@ github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUO github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= -github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= @@ -69,10 +65,6 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= @@ -83,7 +75,6 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= @@ -94,37 +85,24 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/quay/claircore/toolkit v1.3.0 h1:QncygaArnuSKbkPESD2zMDz5xJLWlJxAGPZ69XCYA5o= -github.com/quay/claircore/toolkit v1.3.0/go.mod h1:REVn3WdU+9yMurWa+h9mfuiPWYzKqSaKIGWZ4Xanj5g= +github.com/quay/claircore/toolkit v1.4.0 h1:ygHG1pLAOTSk7r2Wmo/UbIz9WHJb+K3hhQnNIvLrBSQ= +github.com/quay/claircore/toolkit v1.4.0/go.mod h1:0cQXEt/BIYSxo/Wq6ItyAJOJzdvD6ty1wPZJ9xR3b6E= github.com/quay/goval-parser v0.8.8 h1:Uf+f9iF2GIR5GPUY2pGoa9il2+4cdES44ZlM0mWm4cA= github.com/quay/goval-parser v0.8.8/go.mod h1:Y0NTNfPYOC7yxsYKzJOrscTWUPq1+QbtHw4XpPXWPMc= -github.com/quay/zlog v1.1.9 h1:O9/vfCUcGGUs0ukqQ77xPCJBPqpZBJ00jt7IUMsRco8= -github.com/quay/zlog v1.1.9/go.mod h1:O7OCW9oe7igrswhjKlyOvTo2+6FPsV7L0/owWEX6JHE= github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 h1:ukjThsA2ou7AmovpwtMVkNQSuoN/v5U16+JomTz3c7o= github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319/go.mod h1:rhSvwcijY9wfmrBYrfCvapX8/xOTV46NAUjBRgUyJqc= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= -github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= -github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= -github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= +github.com/spdx/tools-golang v0.5.6 h1:HUwSJWxyAR7vBstEeZ1+guD6Jcl4TVsKyTBUwT0RjTQ= +github.com/spdx/tools-golang v0.5.6/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= @@ -145,40 +123,38 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190924052046-3ac2a5bbd98a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -211,10 +187,9 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY= -modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= +modernc.org/sqlite v1.42.2 h1:7hkZUNJvJFN2PgfUdjni9Kbvd4ef4mNLOu0B9FGxM74= +modernc.org/sqlite v1.42.2/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/gobin/exe.go b/gobin/exe.go index 2b522846f..c746712b6 100644 --- a/gobin/exe.go +++ b/gobin/exe.go @@ -6,13 +6,12 @@ import ( "errors" "fmt" "io" + "log/slog" "regexp" "strconv" "strings" _ "unsafe" // for error linkname tricks - "github.com/quay/zlog" - "github.com/quay/claircore" ) @@ -31,21 +30,17 @@ func toPackages(ctx context.Context, out *[]*claircore.Package, p string, r io.R case errors.Is(err, errNotGoExe): return nil default: - zlog.Debug(ctx). - Err(err). - Msg("unable to open executable") + slog.DebugContext(ctx, "unable to open executable", "reason", err) return nil } - ctx = zlog.ContextWithValues(ctx, "exe", p) + log := slog.With("exe", p) pkgdb := "go:" + p badVers := make(map[string]string) defer func() { if len(badVers) == 0 { return } - zlog.Warn(ctx). - Interface("module_versions", badVers). - Msg("invalid semantic versions found in binary") + log.WarnContext(ctx, "invalid semantic versions found in binary", "module_versions", badVers) }() // TODO(hank) This package could use canonical versions, but the @@ -80,9 +75,10 @@ func toPackages(ctx context.Context, out *[]*claircore.Package, p string, r io.R NormalizedVersion: runtimeVer, }) - ev := zlog.Debug(ctx) - vs := map[string]string{ - "stdlib": bi.GoVersion, + debug := log.Enabled(ctx, slog.LevelDebug) + var attrs []slog.Attr + if debug { + attrs = append(attrs, slog.String("stdlib", bi.GoVersion)) } // The go main module version is reported differently depending on the go @@ -147,8 +143,8 @@ func toPackages(ctx context.Context, out *[]*claircore.Package, p string, r io.R NormalizedVersion: mainVer, }) - if ev.Enabled() { - vs[bi.Main.Path] = bi.Main.Version + if debug && bi.Main.Path != "" { + attrs = append(attrs, slog.String(bi.Main.Path, bi.Main.Version)) } for _, d := range bi.Deps { // Replacements are only evaluated for the main module and seem to only @@ -174,18 +170,18 @@ func toPackages(ctx context.Context, out *[]*claircore.Package, p string, r io.R NormalizedVersion: nv, }) - if ev.Enabled() { - vs[d.Path] = d.Version + if debug { + attrs = append(attrs, slog.String(d.Path, d.Version)) } } - ev. - Interface("versions", vs). - Msg("analyzed exe") + log.DebugContext(ctx, "analyzed exe", "versions", slog.GroupValue(attrs...)) return nil } -var versionRegex = regexp.MustCompile(`^v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?$`) -var ErrInvalidSemVer = errors.New("invalid semantic version") +var ( + versionRegex = regexp.MustCompile(`^v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?$`) + ErrInvalidSemVer = errors.New("invalid semantic version") +) // ParseVersion will return a claircore.Version of type semver given // a valid semantic version. If the string is not a valid semver it diff --git a/gobin/exe_test.go b/gobin/exe_test.go index 6ca7e7d1b..75a53956e 100644 --- a/gobin/exe_test.go +++ b/gobin/exe_test.go @@ -1,16 +1,15 @@ package gobin import ( - "context" "os" "path/filepath" "testing" "github.com/Masterminds/semver" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" + "github.com/quay/claircore/test" ) func TestBin(t *testing.T) { @@ -21,7 +20,7 @@ func TestBin(t *testing.T) { for _, n := range ms { name := filepath.Base(n) t.Run(name, func(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) f, err := os.Open(n) if err != nil { t.Fatal(err) diff --git a/gobin/gobin.go b/gobin/gobin.go index 91aad4456..763a35fe4 100644 --- a/gobin/gobin.go +++ b/gobin/gobin.go @@ -18,12 +18,11 @@ import ( "fmt" "io" "io/fs" + "log/slog" "os" "runtime/trace" "sync" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/rpm" @@ -65,12 +64,8 @@ func (Detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pack } defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", l.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "gobin/Detector.Scan", - "version", detectorVersion, - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { @@ -90,7 +85,7 @@ func (Detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pack } walk := func(p string, d fs.DirEntry, err error) error { - ctx := zlog.ContextWithValues(ctx, "filename", d.Name()) + log := slog.With("path", p) switch { case err != nil: @@ -114,9 +109,7 @@ func (Detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pack } if set.Contains(p) { - zlog.Debug(ctx). - Str("path", p). - Msg("file path determined to be of RPM origin") + log.DebugContext(ctx, "file path determined to be of RPM origin") return nil } @@ -124,7 +117,7 @@ func (Detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pack if err != nil { // TODO(crozzy): Remove log line once controller is in a // position to log all the context when receiving an error. - zlog.Warn(ctx).Msg("unable to open file") + log.WarnContext(ctx, "unable to open file") return fmt.Errorf("gobin: unable to open %q: %w", p, err) } defer f.Close() @@ -138,7 +131,7 @@ func (Detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pack default: // TODO(crozzy): Remove log line once controller is in a // position to log all the context when receiving an error. - zlog.Warn(ctx).Msg("unable to read file") + log.WarnContext(ctx, "unable to read file") return fmt.Errorf("gobin: unable to read %q: %w", p, err) } @@ -158,9 +151,7 @@ func (Detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pack case 2: // big-endian typ = binary.BigEndian.Uint16(peek[0x10:]) default: - zlog.Warn(ctx). - Uint8("endianness", e). - Msg("martian ELF") + log.WarnContext(ctx, "martian ELF", "endianness", e) } if typ != 0x02 && typ != 0x03 { // AKA [debug/elf.ET_EXEC] and [debug/elf.ET_DYN] -- not imported in this file by convention. diff --git a/gobin/gobin_test.go b/gobin/gobin_test.go index 83cf657ee..1f671ff25 100644 --- a/gobin/gobin_test.go +++ b/gobin/gobin_test.go @@ -2,7 +2,6 @@ package gobin import ( "archive/tar" - "context" "io" "os" "os/exec" @@ -10,14 +9,12 @@ import ( "strings" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/test" ) func TestEmptyFile(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) mod := test.Modtime(t, "gobin_test.go") // Needs to be the name of this file. p := test.GenerateFixture(t, "nothing.tar", mod, func(t testing.TB, tf *os.File) { @@ -74,7 +71,7 @@ func TestEmptyFile(t *testing.T) { } func TestScanner(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) mod := test.Modtime(t, "gobin_test.go") // Needs to be the name of this file. p := test.GenerateFixture(t, t.Name()+".tar", mod, func(t testing.TB, tf *os.File) { diff --git a/indexer/controller/checkmanifest.go b/indexer/controller/checkmanifest.go index 839cd0e4a..2cd510e7b 100644 --- a/indexer/controller/checkmanifest.go +++ b/indexer/controller/checkmanifest.go @@ -3,11 +3,11 @@ package controller import ( "context" "fmt" + "log/slog" "strconv" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/quay/zlog" "github.com/quay/claircore/indexer" ) @@ -35,7 +35,7 @@ func checkManifest(ctx context.Context, s *Controller) (State, error) { // if we haven't seen this manifest, determine which scanners to use, persist it // and transition to FetchLayer state. if !ok { - zlog.Info(ctx).Msg("manifest to be scanned") + slog.InfoContext(ctx, "manifest to be scanned") // if a manifest was analyzed by a particular scanner we can // omit it from this index, as all its comprising layers were analyzed @@ -61,7 +61,7 @@ func checkManifest(ctx context.Context, s *Controller) (State, error) { // we have seen this manifest before and it's been been processed with the desired scanners // retrieve the existing one and transition to Terminal. - zlog.Info(ctx).Msg("manifest already scanned") + slog.InfoContext(ctx, "manifest already scanned") sr, ok, err := s.Store.IndexReport(ctx, s.manifest.Hash) if err != nil { return Terminal, fmt.Errorf("failed to retrieve manifest: %w", err) diff --git a/indexer/controller/checkmanifest_test.go b/indexer/controller/checkmanifest_test.go index e67547bfe..911755e10 100644 --- a/indexer/controller/checkmanifest_test.go +++ b/indexer/controller/checkmanifest_test.go @@ -1,20 +1,18 @@ package controller import ( - "context" "testing" - "github.com/quay/zlog" "go.uber.org/mock/gomock" "github.com/quay/claircore" + "github.com/quay/claircore/test" indexer "github.com/quay/claircore/test/mock/indexer" ) // confirm checkManfest statefunc acts appropriately // when manifest has been seen func TestCheckManifest(t *testing.T) { - ctx := zlog.Test(context.Background(), t) tt := []struct { // the name of this test name string @@ -49,7 +47,7 @@ func TestCheckManifest(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) // get mock m := table.mock(t) diff --git a/indexer/controller/coalesce_test.go b/indexer/controller/coalesce_test.go index 6828848f8..a71eae3cb 100644 --- a/indexer/controller/coalesce_test.go +++ b/indexer/controller/coalesce_test.go @@ -1,12 +1,10 @@ package controller import ( - "context" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore/indexer" + "github.com/quay/claircore/test" ) // TestCoalesce confirms when no error is encountered @@ -16,8 +14,7 @@ import ( // This test simply provides no Ecosystems to the index // controller and does no work. func TestCoalesce(t *testing.T) { - ctx := context.Background() - var tt = []struct { + tt := []struct { name string expectedState State }{ @@ -29,7 +26,7 @@ func TestCoalesce(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) indexer := New(&indexer.Options{}) state, err := coalesce(ctx, indexer) diff --git a/indexer/controller/controller.go b/indexer/controller/controller.go index df6504201..e8b4aa814 100644 --- a/indexer/controller/controller.go +++ b/indexer/controller/controller.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" + "log/slog" "math/rand" "time" - "github.com/quay/zlog" + "github.com/quay/claircore/toolkit/log" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -64,12 +65,10 @@ func (s *Controller) Index(ctx context.Context, manifest *claircore.Manifest) (* // set manifest info on controller s.manifest = manifest s.report.Hash = manifest.Hash - ctx = zlog.ContextWithValues(ctx, - "component", "indexer/controller/Controller.Index", - "manifest", s.manifest.Hash.String()) + ctx = log.With(ctx, "manifest", s.manifest.Hash) s.Realizer = s.FetchArena.Realizer(ctx) defer s.Realizer.Close() - zlog.Info(ctx).Msg("starting scan") + slog.InfoContext(ctx, "starting scan") return s.report, s.run(ctx) } @@ -83,7 +82,7 @@ func (s *Controller) run(ctx context.Context) (err error) { // As long as there's not an error and the current state isn't Terminal, run // the corresponding function. for err == nil && s.currentState != Terminal { - ctx := zlog.ContextWithValues(ctx, "state", s.currentState.String()) + ctx := log.With(ctx, "state", s.currentState) next, err = stateToStateFunc[s.currentState](ctx, s) switch { case errors.Is(err, nil) && !errors.Is(ctx.Err(), nil): @@ -107,16 +106,12 @@ func (s *Controller) run(ctx context.Context) (err error) { continue default: s.setState(IndexError) - zlog.Error(ctx). - Err(err). - Msg("error during scan") + slog.ErrorContext(ctx, "error during scan", "reason", err) s.report.Success = false s.report.Err = err.Error() } if setReportErr := s.Store.SetIndexReport(ctx, s.report); !errors.Is(setReportErr, nil) { - zlog.Info(ctx). - Err(setReportErr). - Msg("failed persisting index report") + slog.InfoContext(ctx, "failed persisting index report", "reason", setReportErr) s.setState(IndexError) s.report.Err = fmt.Sprintf("failed persisting index report: %s", setReportErr.Error()) err = setReportErr diff --git a/indexer/controller/controller_test.go b/indexer/controller/controller_test.go index 99499a275..60eb340dc 100644 --- a/indexer/controller/controller_test.go +++ b/indexer/controller/controller_test.go @@ -1,16 +1,15 @@ package controller import ( - "context" "errors" "fmt" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "go.uber.org/mock/gomock" "github.com/quay/claircore" + "github.com/quay/claircore/test" indexer "github.com/quay/claircore/test/mock/indexer" ) @@ -21,7 +20,6 @@ import ( // call to s.Store.ManifestScanned forcing checkManifest to return an error and // evaluate our scanner's state afterwards. func TestControllerIndexerError(t *testing.T) { - ctx := context.Background() tt := []struct { mock func(t *testing.T) (indexer.Store, indexer.FetchArena) name string @@ -69,7 +67,7 @@ func TestControllerIndexerError(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) store, fa := table.mock(t) c := New(&indexer.Options{ Store: store, @@ -96,7 +94,6 @@ func TestControllerIndexerError(t *testing.T) { // TestControllerIndexFinished tests that our state machine does the correct // thing when it reaches ScanFinished terminal state. func TestControllerIndexFinished(t *testing.T) { - ctx := context.Background() tt := []struct { mock func(t *testing.T) (indexer.Store, indexer.FetchArena) name string @@ -126,7 +123,7 @@ func TestControllerIndexFinished(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) store, fa := table.mock(t) c := New(&indexer.Options{ Store: store, diff --git a/indexer/controller/fetchlayers.go b/indexer/controller/fetchlayers.go index 24688711f..742d4c455 100644 --- a/indexer/controller/fetchlayers.go +++ b/indexer/controller/fetchlayers.go @@ -3,26 +3,21 @@ package controller import ( "context" "fmt" - - "github.com/quay/zlog" + "log/slog" "github.com/quay/claircore/internal/wart" ) func fetchLayers(ctx context.Context, s *Controller) (State, error) { - zlog.Info(ctx).Msg("layers fetch start") - defer zlog.Info(ctx).Msg("layers fetch done") + slog.InfoContext(ctx, "layers fetch start") + defer slog.InfoContext(ctx, "layers fetch done") toFetch, err := reduce(ctx, s.Store, s.Vscnrs, s.manifest.Layers) if err != nil { return Terminal, fmt.Errorf("failed to determine layers to fetch: %w", err) } - zlog.Debug(ctx). - Int("count", len(toFetch)). - Msg("fetching layers") + slog.DebugContext(ctx, "fetching layers", "count", len(toFetch)) if err := s.Realizer.Realize(ctx, toFetch); err != nil { - zlog.Warn(ctx). - Err(err). - Msg("layers fetch failure") + slog.WarnContext(ctx, "layers fetch failure", "reason", err) return Terminal, fmt.Errorf("failed to fetch layers: %w", err) } // With the addition of the LayerDescription abstraction, it's possible that @@ -30,6 +25,6 @@ func fetchLayers(ctx context.Context, s *Controller) (State, error) { // LayerDescription type is plumbed through the Indexer, this can be // removed. wart.CopyLayerPointers(s.manifest.Layers, toFetch) - zlog.Info(ctx).Msg("layers fetch success") + slog.InfoContext(ctx, "layers fetch success") return ScanLayers, nil } diff --git a/indexer/controller/indexfinished.go b/indexer/controller/indexfinished.go index bd5e47fc2..d96525f16 100644 --- a/indexer/controller/indexfinished.go +++ b/indexer/controller/indexfinished.go @@ -3,8 +3,7 @@ package controller import ( "context" "fmt" - - "github.com/quay/zlog" + "log/slog" ) // indexFinished is the terminal stateFunc. once it transitions the @@ -12,13 +11,13 @@ import ( // and return an IndexReport to the caller func indexFinished(ctx context.Context, s *Controller) (State, error) { s.report.Success = true - zlog.Info(ctx).Msg("finishing scan") + slog.InfoContext(ctx, "finishing scan") err := s.Store.SetIndexFinished(ctx, s.report, s.Vscnrs) if err != nil { return Terminal, fmt.Errorf("failed finish scan: %w", err) } - zlog.Info(ctx).Msg("manifest successfully scanned") + slog.InfoContext(ctx, "manifest successfully scanned") return Terminal, nil } diff --git a/indexer/controller/indexfinished_test.go b/indexer/controller/indexfinished_test.go index 83da52819..49c4c8b5f 100644 --- a/indexer/controller/indexfinished_test.go +++ b/indexer/controller/indexfinished_test.go @@ -1,17 +1,15 @@ package controller import ( - "context" "testing" - "github.com/quay/zlog" "go.uber.org/mock/gomock" + "github.com/quay/claircore/test" indexer "github.com/quay/claircore/test/mock/indexer" ) func TestIndexFinished(t *testing.T) { - ctx := context.Background() tt := []struct { name string expectedState State @@ -33,7 +31,7 @@ func TestIndexFinished(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) store := table.mock(t) scnr := New(&indexer.Options{ Store: store, diff --git a/indexer/controller/indexmanifest.go b/indexer/controller/indexmanifest.go index 272807c2a..25f919703 100644 --- a/indexer/controller/indexmanifest.go +++ b/indexer/controller/indexmanifest.go @@ -3,12 +3,11 @@ package controller import ( "context" "fmt" - - "github.com/quay/zlog" + "log/slog" ) func indexManifest(ctx context.Context, c *Controller) (State, error) { - zlog.Info(ctx).Msg("starting index manifest") + slog.InfoContext(ctx, "starting index manifest") if c.report == nil { return Terminal, fmt.Errorf("reached IndexManifest state with a nil report field. cannot continue") diff --git a/indexer/controller/indexmanifest_test.go b/indexer/controller/indexmanifest_test.go index 1bd40b577..25e911bca 100644 --- a/indexer/controller/indexmanifest_test.go +++ b/indexer/controller/indexmanifest_test.go @@ -1,18 +1,16 @@ package controller import ( - "context" "fmt" "testing" - "github.com/quay/zlog" "go.uber.org/mock/gomock" + "github.com/quay/claircore/test" indexer "github.com/quay/claircore/test/mock/indexer" ) func TestIndexManifest(t *testing.T) { - ctx := context.Background() tt := []struct { name string expectedState State @@ -44,7 +42,7 @@ func TestIndexManifest(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) s := table.mock(t) indexer := New(&indexer.Options{ Store: s, diff --git a/indexer/controller/reduce.go b/indexer/controller/reduce.go index d8cbb8d30..6890b44ca 100644 --- a/indexer/controller/reduce.go +++ b/indexer/controller/reduce.go @@ -2,8 +2,7 @@ package controller import ( "context" - - "github.com/quay/zlog" + "log/slog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -16,10 +15,7 @@ func reduce(ctx context.Context, store indexer.Store, scnrs indexer.VersionedSca for _, scnr := range scnrs { ok, err := store.LayerScanned(ctx, l.Hash, scnr) if err != nil { - zlog.Debug(ctx). - Stringer("layer", l.Hash). - Err(err). - Msg("unable to lookup layer") + slog.DebugContext(ctx, "unable to lookup layer", "layer", l.Hash, "reason", err) return nil, err } if !ok { diff --git a/indexer/controller/scanlayers.go b/indexer/controller/scanlayers.go index aeda09061..21c612635 100644 --- a/indexer/controller/scanlayers.go +++ b/indexer/controller/scanlayers.go @@ -3,19 +3,18 @@ package controller import ( "context" "fmt" - - "github.com/quay/zlog" + "log/slog" ) // scanLayers will run all scanner types against all layers if deemed necessary // to scan func scanLayers(ctx context.Context, c *Controller) (State, error) { - zlog.Info(ctx).Msg("layers scan start") - defer zlog.Info(ctx).Msg("layers scan done") + slog.InfoContext(ctx, "layers scan start") + defer slog.InfoContext(ctx, "layers scan done") err := c.LayerScanner.Scan(ctx, c.manifest.Hash, c.manifest.Layers) if err != nil { return Terminal, fmt.Errorf("failed to scan all layer contents: %w", err) } - zlog.Debug(ctx).Msg("layers scan ok") + slog.DebugContext(ctx, "layers scan ok") return Coalesce, nil } diff --git a/indexer/controller/scanlayers_test.go b/indexer/controller/scanlayers_test.go index a359b5365..b710bb0de 100644 --- a/indexer/controller/scanlayers_test.go +++ b/indexer/controller/scanlayers_test.go @@ -6,7 +6,6 @@ import ( "fmt" "testing" - "github.com/quay/zlog" "go.uber.org/mock/gomock" "github.com/quay/claircore" @@ -17,7 +16,6 @@ import ( ) func TestScanLayers(t *testing.T) { - ctx := context.Background() tt := []struct { mock func(t *testing.T) indexer.Store name string @@ -38,7 +36,7 @@ func TestScanLayers(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) s := table.mock(t) opts := &indexer.Options{ Store: s, @@ -62,7 +60,7 @@ func TestScanLayers(t *testing.T) { } func TestScanNoErrors(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) ctrl := gomock.NewController(t) store := indexer_mock.NewMockStore(ctrl) diff --git a/indexer/ecosystem.go b/indexer/ecosystem.go index 5f2ebc0e3..5e49267a0 100644 --- a/indexer/ecosystem.go +++ b/indexer/ecosystem.go @@ -2,11 +2,9 @@ package indexer import ( "context" - - "github.com/quay/zlog" ) -// Ecosystems group together scanners and a Coalescer which are commonly used +// An Ecosystem groups together scanners and a Coalescer which are commonly used // together. // // A typical ecosystem is "dpkg" which will use the "dpkg" package indexer, the @@ -26,7 +24,6 @@ type Ecosystem struct { // EcosystemsToScanners extracts and dedupes multiple ecosystems and returns // their discrete scanners. func EcosystemsToScanners(ctx context.Context, ecosystems []*Ecosystem) ([]PackageScanner, []DistributionScanner, []RepositoryScanner, []FileScanner, error) { - ctx = zlog.ContextWithValues(ctx, "component", "indexer/EcosystemsToScanners") ps := []PackageScanner{} ds := []DistributionScanner{} rs := []RepositoryScanner{} diff --git a/indexer/layerscanner.go b/indexer/layerscanner.go index 1f91b7e19..d93de7865 100644 --- a/indexer/layerscanner.go +++ b/indexer/layerscanner.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" + "log/slog" "net" "runtime" - "github.com/quay/zlog" + "github.com/quay/claircore/toolkit/log" "golang.org/x/sync/errgroup" "github.com/quay/claircore" @@ -30,13 +31,10 @@ type LayerScanner struct { // // The provided Context is only used for the duration of the call. func NewLayerScanner(ctx context.Context, concurrent int, opts *Options) (*LayerScanner, error) { - ctx = zlog.ContextWithValues(ctx, "component", "indexer.NewLayerScanner") - zlog.Info(ctx).Msg("NewLayerScanner: constructing a new layer-scanner") + slog.InfoContext(ctx, "constructing") switch { case concurrent < 1: - zlog.Warn(ctx). - Int("value", concurrent). - Msg("rectifying nonsense 'concurrent' argument") + slog.WarnContext(ctx, "rectifying nonsense 'concurrent' argument", "value", concurrent) fallthrough case concurrent == 0: concurrent = runtime.GOMAXPROCS(0) @@ -61,6 +59,7 @@ func configAndFilter[S VersionedScanner](ctx context.Context, opts *Options, ss i := 0 for _, s := range ss { n := s.Name() + log := slog.With("scanner", n) var cfgMap map[string]func(any) error switch k := s.Kind(); k { case "package": @@ -72,10 +71,7 @@ func configAndFilter[S VersionedScanner](ctx context.Context, opts *Options, ss case "file": cfgMap = opts.ScannerConfig.File default: - zlog.Warn(ctx). - Str("kind", k). - Str("scanner", n). - Msg("unknown scanner kind") + log.WarnContext(ctx, "unknown scanner kind", "kind", k) continue } @@ -87,25 +83,17 @@ func configAndFilter[S VersionedScanner](ctx context.Context, opts *Options, ss rs, rsOK := any(s).(RPCScanner) switch { case haveCfg && !csOK && !rsOK: - zlog.Warn(ctx). - Str("scanner", n). - Msg("configuration present for an unconfigurable scanner, skipping") + log.WarnContext(ctx, "configuration present for an unconfigurable scanner, skipping") case csOK && rsOK: fallthrough case !csOK && rsOK: if err := rs.Configure(ctx, f, opts.Client); err != nil { - zlog.Error(ctx). - Str("scanner", n). - Err(err). - Msg("configuration failed") + log.ErrorContext(ctx, "configuration failed", "reason", err) continue } case csOK && !rsOK: if err := cs.Configure(ctx, f); err != nil { - zlog.Error(ctx). - Str("scanner", n). - Err(err). - Msg("configuration failed") + log.ErrorContext(ctx, "configuration failed", "reason", err) continue } } @@ -125,9 +113,7 @@ func configAndFilter[S VersionedScanner](ctx context.Context, opts *Options, ss // The provided Context controls cancellation for all scanners. The first error // reported halts all work and is returned from Scan. func (ls *LayerScanner) Scan(ctx context.Context, manifest claircore.Digest, layers []*claircore.Layer) error { - ctx = zlog.ContextWithValues(ctx, - "component", "indexer/LayerScanner.Scan", - "manifest", manifest.String()) + ctx = log.With(ctx, "manifest", manifest) g, ctx := errgroup.WithContext(ctx) // Using the goroutine's built-in limit is worst-case the same as using an @@ -183,20 +169,16 @@ Layers: // ScanLayer (along with the result type) handles an individual (scanner, layer) // pair. func (ls *LayerScanner) scanLayer(ctx context.Context, l *claircore.Layer, s VersionedScanner) error { - ctx = zlog.ContextWithValues(ctx, - "component", "indexer/LayerScanner.scanLayer", - "scanner", s.Name(), - "kind", s.Kind(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("scan start") - defer zlog.Debug(ctx).Msg("scan done") + ctx = log.With(ctx, "scanner", s.Name(), "kind", s.Kind(), "layer", l.Hash) + slog.DebugContext(ctx, "scan start") + defer slog.DebugContext(ctx, "scan done") ok, err := ls.store.LayerScanned(ctx, l.Hash, s) if err != nil { return err } if ok { - zlog.Debug(ctx).Msg("layer already scanned") + slog.DebugContext(ctx, "layer already scanned") return nil } @@ -251,10 +233,10 @@ func (r *result) Do(ctx context.Context, s VersionedScanner, l *claircore.Layer) switch { case errors.Is(err, nil): case errors.As(err, &addrErr): - zlog.Warn(ctx).Str("scanner", s.Name()).Err(err).Msg("scanner not able to access resources") + slog.WarnContext(ctx, "scanner not able to access resources", "scanner", s.Name(), "reason", err) return nil default: - zlog.Info(ctx).Err(err).Send() + slog.InfoContext(ctx, "unexpected error", "reason", err) } return err @@ -264,25 +246,25 @@ func (r *result) Do(ctx context.Context, s VersionedScanner, l *claircore.Layer) // the result. func (r *result) Store(ctx context.Context, store Store, s VersionedScanner, l *claircore.Layer) error { if r.pkgs != nil { - zlog.Debug(ctx).Int("count", len(r.pkgs)).Msg("scan returned packages") + slog.DebugContext(ctx, "scan returned packages", "count", len(r.pkgs)) if err := store.IndexPackages(ctx, r.pkgs, l, s); err != nil { return err } } if r.dists != nil { - zlog.Debug(ctx).Int("count", len(r.dists)).Msg("scan returned dists") + slog.DebugContext(ctx, "scan returned distributions", "count", len(r.dists)) if err := store.IndexDistributions(ctx, r.dists, l, s); err != nil { return err } } if r.repos != nil { - zlog.Debug(ctx).Int("count", len(r.repos)).Msg("scan returned repos") + slog.DebugContext(ctx, "scan returned repositories", "count", len(r.repos)) if err := store.IndexRepositories(ctx, r.repos, l, s); err != nil { return err } } if r.files != nil { - zlog.Debug(ctx).Int("count", len(r.files)).Msg("scan returned files") + slog.DebugContext(ctx, "scan returned files", "count", len(r.files)) if err := store.IndexFiles(ctx, r.files, l, s); err != nil { return err } diff --git a/internal/dnf/dnf.go b/internal/dnf/dnf.go index 7c6ac4962..f6c36370e 100644 --- a/internal/dnf/dnf.go +++ b/internal/dnf/dnf.go @@ -13,12 +13,12 @@ import ( "io" "io/fs" "iter" + "log/slog" "net/url" "os" "runtime" "slices" - "github.com/quay/zlog" _ "modernc.org/sqlite" // register the sqlite driver "github.com/quay/claircore" @@ -85,7 +85,7 @@ func FindRepoids(ctx context.Context, sys fs.FS) ([]string, error) { } defer func() { if err := rows.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("error closing returned rows") + slog.WarnContext(ctx, "error closing returned rows", "reason", err) } }() @@ -145,7 +145,7 @@ Stat: } defer func() { if err := f.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close fs.FS db file") + slog.WarnContext(ctx, "unable to close fs.FS db file", "reason", err) } }() @@ -157,17 +157,17 @@ Stat: } defer func() { if err := spool.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close sqlite db file") + slog.WarnContext(ctx, "unable to close sqlite db file", "reason", err) } // If in an error return, make sure to clean up the spool file. if h == nil { if err := os.Remove(spool.Name()); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to unlink sqlite db file") + slog.WarnContext(ctx, "unable to unlink sqlite db file", "reason", err) } } }() - zlog.Debug(ctx).Str("file", spool.Name()).Msg("copying sqlite db out of tar") + slog.DebugContext(ctx, "copying sqlite db out of tar", "file", spool.Name()) if _, err := io.Copy(spool, f); err != nil { return nil, fmt.Errorf("internal/dnf: error spooling sqlite db: %w", err) } @@ -181,7 +181,7 @@ Stat: } if err := db.PingContext(ctx); err != nil { if err := db.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close sqlite db") + slog.WarnContext(ctx, "unable to close sqlite db", "reason", err) } return nil, fmt.Errorf("internal/dnf: error pinging sqlite db: %w", err) } @@ -210,7 +210,7 @@ func pickQueries(ctx context.Context, db *sql.DB) (q queries, err error) { if err != nil { return q, err } - zlog.Debug(ctx).Strs("tables", names).Msg("found tables in database") + slog.DebugContext(ctx, "found tables in database", "tables", names) switch { case !slices.Contains(names, `config`): @@ -260,17 +260,16 @@ func (h *historyDB) AddRepoid(ctx context.Context, pkg *claircore.Package) error v := fmt.Sprintf("%s-%s.%s", pkg.Name, pkg.Version, pkg.Arch) // "Version" contains the EVR. ver, err := rpmver.Parse(v) if err != nil { - zlog.Warn(ctx). - Err(err). - Str("version", v). - Msg("unable to re-parse rpm version") + slog.WarnContext(ctx, "unable to re-parse rpm version", + "version", v, + "reason", err, + ) return nil } if ver.Architecture == nil { - zlog.Debug(ctx). - Err(err). - Str("version", v). - Msg("unable to parse architecture") + slog.DebugContext(ctx, "unable to parse architecture", + "reason", err, + "version", v) return nil } diff --git a/internal/dnf/dnf_test.go b/internal/dnf/dnf_test.go index 6e8a550a4..adc9d80d3 100644 --- a/internal/dnf/dnf_test.go +++ b/internal/dnf/dnf_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" + "github.com/quay/claircore/test" ) type testCase struct { @@ -24,7 +24,7 @@ type testCase struct { func (tc *testCase) Run(ctx context.Context, t *testing.T) { t.Run(tc.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) seq := func(yield func(claircore.Package, error) bool) { yield(tc.Package, nil) } @@ -43,7 +43,7 @@ func (tc *testCase) Run(ctx context.Context, t *testing.T) { func TestWrap(t *testing.T) { t.Run("dnf4", func(t *testing.T) { - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) sys := os.DirFS(filepath.Join("testdata", path.Base(t.Name()))) tcs := []testCase{ @@ -77,7 +77,7 @@ func TestWrap(t *testing.T) { } }) t.Run("dnf5", func(t *testing.T) { - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) sys := os.DirFS(filepath.Join("testdata", path.Base(t.Name()))) tcs := []testCase{ @@ -129,7 +129,7 @@ func TestFindRepoids(t *testing.T) { for _, tc := range tcs { t.Run(tc.Name, func(t *testing.T) { - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) sys := os.DirFS(filepath.Join("testdata", tc.Name)) got, err := FindRepoids(ctx, sys) diff --git a/internal/matcher/controller.go b/internal/matcher/controller.go index c67774506..a5e17e363 100644 --- a/internal/matcher/controller.go +++ b/internal/matcher/controller.go @@ -2,10 +2,9 @@ package matcher import ( "context" + "log/slog" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/datastore" "github.com/quay/claircore/libvuln/driver" @@ -30,15 +29,12 @@ func NewController(m driver.Matcher, store datastore.Vulnerability) *Controller // Match is the entrypoint for [Controller]. func (mc *Controller) Match(ctx context.Context, records []*claircore.IndexRecord) (map[string][]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "internal/matcher/Controller.Match", - "matcher", mc.m.Name()) + log := slog.With("matcher", mc.m.Name()) // find the packages the matcher is interested in. interested := mc.findInterested(records) - zlog.Debug(ctx). - Int("interested", len(interested)). - Int("records", len(records)). - Msg("interest") + log.DebugContext(ctx, "interest", + "interested", len(interested), + "records", len(records)) // early return; do not call db at all if len(interested) == 0 { @@ -48,26 +44,23 @@ func (mc *Controller) Match(ctx context.Context, records []*claircore.IndexRecor remoteMatcher, matchedVulns, err := mc.queryRemoteMatcher(ctx, interested) if remoteMatcher { if err != nil { - zlog.Error(ctx).Err(err).Msg("remote matcher error, returning empty results") + log.ErrorContext(ctx, "remote matcher error, returning empty results", "reason", err) return map[string][]*claircore.Vulnerability{}, nil } return matchedVulns, nil } dbSide, authoritative := mc.dbFilter() - zlog.Debug(ctx). - Bool("opt-in", dbSide). - Bool("authoritative", authoritative). - Msg("version filter compatible?") + log.DebugContext(ctx, "version filter compatible?", + "opt-in", dbSide, + "authoritative", authoritative) // query the vulnstore vulns, err := mc.query(ctx, interested, dbSide) if err != nil { return nil, err } - zlog.Debug(ctx). - Int("vulnerabilities", len(vulns)). - Msg("query") + log.DebugContext(ctx, "query", "count", len(vulns)) if authoritative { return vulns, nil @@ -77,9 +70,7 @@ func (mc *Controller) Match(ctx context.Context, records []*claircore.IndexRecor if err != nil { return nil, err } - zlog.Debug(ctx). - Int("filtered", len(filteredVulns)). - Msg("filtered") + log.DebugContext(ctx, "filtered", "count", len(filteredVulns)) return filteredVulns, nil } diff --git a/internal/matcher/match.go b/internal/matcher/match.go index aa67f814a..3ba8eb372 100644 --- a/internal/matcher/match.go +++ b/internal/matcher/match.go @@ -5,11 +5,11 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "runtime" "sync" "sync/atomic" - "github.com/quay/zlog" "golang.org/x/sync/errgroup" "github.com/quay/claircore" @@ -202,15 +202,11 @@ func EnrichedMatch(ctx context.Context, ir *claircore.IndexReport, ms []driver.M for e = range eCh { kind, msg, err := e.Enrich(ectx, getter(s, e.Name()), vr) if err != nil { - zlog.Error(ctx). - Err(err). - Msg("enrichment error") + slog.ErrorContext(ctx, "enrichment error", "reason", err) continue } if len(msg) == 0 { - zlog.Debug(ctx). - Str("name", e.Name()). - Msg("enricher reported nothing, skipping") + slog.DebugContext(ctx, "enricher reported nothing, skipping", "name", e.Name()) continue } res := entry{ diff --git a/internal/rpm/bdb/bdb.go b/internal/rpm/bdb/bdb.go index caab00fb3..63242da0b 100644 --- a/internal/rpm/bdb/bdb.go +++ b/internal/rpm/bdb/bdb.go @@ -7,8 +7,7 @@ import ( "fmt" "io" "iter" - - "github.com/quay/zlog" + "log/slog" ) // PackageDB is the "pkgdb" a.k.a. "Packages", the raw package data. @@ -258,10 +257,9 @@ func (db *PackageDB) Headers(ctx context.Context) iter.Seq2[io.ReaderAt, error] } // Double-check we'll read the intended amount. if got, want := r.Size(), int64(offpg.Length); got != want { - zlog.Info(ctx). - Int64("got", got). - Int64("want", want). - Msg("bdb: expected data length botch") + slog.InfoContext(ctx, "expected data length botch", + "got", got, + "want", want) } if !yield(&r, nil) { diff --git a/internal/rpm/bdb/bdb_test.go b/internal/rpm/bdb/bdb_test.go index a45d06e50..e9713550b 100644 --- a/internal/rpm/bdb/bdb_test.go +++ b/internal/rpm/bdb/bdb_test.go @@ -2,19 +2,15 @@ package bdb import ( "bytes" - "context" "io/fs" "os" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore/internal/rpm/rpmdb" + "github.com/quay/claircore/test" ) func TestLoadPackage(t *testing.T) { - ctx := context.Background() - dir := os.DirFS("testdata") ms, err := fs.Glob(dir, "*Packages") if err != nil || len(ms) == 0 { @@ -22,7 +18,7 @@ func TestLoadPackage(t *testing.T) { } for _, n := range ms { t.Run(n, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) b, err := fs.ReadFile(dir, n) if err != nil { t.Fatal(err) diff --git a/internal/rpm/bdb/doc.go b/internal/rpm/bdb/doc.go index 093126a25..a44db4c9c 100644 --- a/internal/rpm/bdb/doc.go +++ b/internal/rpm/bdb/doc.go @@ -6,8 +6,7 @@ import ( "context" "encoding/binary" "io" - - "github.com/quay/zlog" + "log/slog" ) // CheckMagic looks at bit of the provided Reader to see if it looks like a @@ -28,7 +27,7 @@ func CheckMagic(ctx context.Context, r io.Reader) bool { b := make([]byte, 16) if _, err := io.ReadFull(r, b); err != nil { - zlog.Warn(ctx).Err(err).Msg("unexpected error checking magic") + slog.WarnContext(ctx, "unexpected error checking magic", "reason", err) return false } // Look at position 12 for a magic number. diff --git a/internal/rpm/database.go b/internal/rpm/database.go index c8e8bb4a0..49615d9f7 100644 --- a/internal/rpm/database.go +++ b/internal/rpm/database.go @@ -5,12 +5,11 @@ import ( "fmt" "io" "iter" + "log/slog" "runtime/trace" "strings" "sync" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/internal/rpm/rpmdb" "github.com/quay/claircore/internal/rpmver" @@ -45,9 +44,7 @@ func (db *Database) Packages(ctx context.Context) iter.Seq2[claircore.Package, e var ok bool ct := 0 defer func() { - zlog.Debug(ctx). - Int("packages", ct). - Msg("processed rpm db") + slog.DebugContext(ctx, "processed rpm db", "packages", ct) }() retErr := func(err error) (cont bool) { trace.WithRegion(ctx, "internal/rpm.Database.PackagesYield", func() { cont = yield(claircore.Package{}, err) }) @@ -87,14 +84,11 @@ func (db *Database) Packages(ctx context.Context) iter.Seq2[claircore.Package, e for !ok { v, err := rpmver.Parse(srcRPM) if err != nil { - zlog.Info(ctx). - Err(err). - Msg("unable to parse SOURCERPM tag, skipping") + slog.InfoContext(ctx, "unable to parse SOURCERPM tag, skipping", "reason", err) break } if v.Name == nil { - zlog.Info(ctx). - Msg("no name parse out of SOURCERPM tag, skipping") + slog.InfoContext(ctx, "no name parse out of SOURCERPM tag, skipping") break } src := claircore.Package{ @@ -135,39 +129,31 @@ var sourceVersionWarning sync.Once func printSourceVersionWarning(ctx context.Context) { sourceVersionWarning.Do(func() { - zlog.Warn(ctx). - Strs("see-also", []string{ + slog.WarnContext(ctx, "rpm source packages always record 0 epoch; this may cause incorrect matching", + "see-also", strings.Join([]string{ `https://github.com/rpm-software-management/rpm/issues/2796`, `https://github.com/rpm-software-management/rpm/discussions/3703`, `https://github.com/rpm-software-management/rpm/pull/3755`, - }). - Msg("rpm source packages always record 0 epoch; this may cause incorrect matching") + }, " ")) }) } // PopulatePathSet adds relevant paths from the RPM database to the provided // [PathSet]. -func (db *Database) populatePathSet(ctx context.Context, s *PathSet) error { +func (db *Database) populatePathSet(ctx context.Context, s *PathSet) (int, error) { ctx, task := trace.NewTask(ctx, "internal/rpm.Database.populatePathSet") defer task.End() seq := loadPackageInfo(ctx, db.headers.Headers(ctx)) ct := 0 - defer func() { - zlog.Debug(ctx). - Int("packages", ct). - Int("files", s.len()). - Msg("processed rpm db") - }() - for info, err := range seq { if err != nil { - return err + return 0, err } ct++ info.InsertIntoSet(s) } - return nil + return ct, nil } func (db *Database) Close() error { diff --git a/internal/rpm/files.go b/internal/rpm/files.go index b5998122f..6d41b8e8e 100644 --- a/internal/rpm/files.go +++ b/internal/rpm/files.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" + "log/slog" "runtime" "strings" "sync" "unique" "weak" - "github.com/quay/zlog" "golang.org/x/sync/singleflight" "github.com/quay/claircore" @@ -69,18 +69,22 @@ func (c *fileCache) GetPathSet(ctx context.Context, layer *claircore.Layer) (*Pa }() for found := range seq { - zlog.Debug(ctx). - Stringer("db", found). - Msg("found possible database") + slog.DebugContext(ctx, "found possible database", "db", found) err = func() error { db, err := OpenDB(ctx, sys, found) if err != nil { return err } defer db.Close() - ctx := zlog.ContextWithValues(ctx, "db", db.String()) - zlog.Debug(ctx).Msg("examining database") - return db.populatePathSet(ctx, set) + log := slog.With("db", db) + log.DebugContext(ctx, "examining database") + ct, err := db.populatePathSet(ctx, set) + if err == nil { + log.DebugContext(ctx, "processed rpm db", + "packages", ct, + "files", set.len()) + } + return err }() if err != nil { return nil, err diff --git a/internal/rpm/files_test.go b/internal/rpm/files_test.go index a0939314c..25b3052e8 100644 --- a/internal/rpm/files_test.go +++ b/internal/rpm/files_test.go @@ -7,8 +7,6 @@ import ( "unique" "weak" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/test" ) @@ -27,7 +25,7 @@ type FileCheckerTestcase struct { func (tc *FileCheckerTestcase) Run(ctx context.Context, t *testing.T, a *test.CachedArena) { t.Run(tc.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) a.LoadLayerFromRegistry(ctx, t, tc.Layer) r := a.Realizer(ctx).(*test.CachedRealizer) t.Cleanup(func() { @@ -56,7 +54,7 @@ func (tc *FileCheckerTestcase) Run(ctx context.Context, t *testing.T, a *test.Ca for _, tc := range tc.Testcases { t.Run(tc.Name, func(t *testing.T) { t.Parallel() - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -74,7 +72,7 @@ func (tc *FileCheckerTestcase) Run(ctx context.Context, t *testing.T, a *test.Ca } func TestIsRPMFile(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) a := test.NewCachedArena(t) t.Cleanup(func() { diff --git a/internal/rpm/find.go b/internal/rpm/find.go index 96be11e96..b31d4cbc4 100644 --- a/internal/rpm/find.go +++ b/internal/rpm/find.go @@ -7,10 +7,10 @@ import ( "io" "io/fs" "iter" + "log/slog" "os" "path" - "github.com/quay/zlog" "golang.org/x/sync/errgroup" "github.com/quay/claircore/internal/rpm/bdb" @@ -166,7 +166,7 @@ func OpenDB(ctx context.Context, sys fs.FS, found FoundDB) (*Database, error) { } defer func() { if err := f.Close(); err != nil { - zlog.Warn(ctx).Stringer("kind", found.kind).Err(err).Msg("unable to close db") + slog.WarnContext(ctx, "unable to close db", "kind", found, "reason", err) } }() @@ -180,10 +180,9 @@ func OpenDB(ctx context.Context, sys fs.FS, found FoundDB) (*Database, error) { if err != nil { return nil, fmt.Errorf("internal/rpm: error spooling db: %w", err) } - ctx = zlog.ContextWithValues(ctx, "file", spool.Name()) + log := slog.With("file", spool.Name()) cleanup.spool = spool - zlog.Debug(ctx). - Msg("copying db out of fs.FS") + log.DebugContext(ctx, "copying db out of fs.FS") // Need to have the file linked into the filesystem for the sqlite package. // @@ -206,12 +205,12 @@ func OpenDB(ctx context.Context, sys fs.FS, found FoundDB) (*Database, error) { // [this post]: https://sqlite.org/forum/info/57aaaf20cf703d301fed5aeaef59e70723f1d9454fb3a4e6383b2bfac6897e5a if _, err := io.Copy(spool, f); err != nil { if err := spool.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + log.WarnContext(ctx, "unable to close spool", "reason", err) } return nil, fmt.Errorf("internal/rpm: error spooling db: %w", err) } if err := spool.Sync(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to sync spool; results may be Weird") + log.WarnContext(ctx, "unable to sync spool; results may be Weird", "reason", err) } switch found.kind { @@ -219,10 +218,10 @@ func OpenDB(ctx context.Context, sys fs.FS, found FoundDB) (*Database, error) { sdb, err := sqlite.Open(spool.Name()) if err != nil { if err := spool.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + log.WarnContext(ctx, "unable to close spool", "reason", err) } if err := os.Remove(spool.Name()); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to remove spool") + log.WarnContext(ctx, "unable to remove spool", "reason", err) } return nil, fmt.Errorf("internal/rpm: unable to open sqlite db: %w", err) } @@ -243,9 +242,7 @@ func OpenDB(ctx context.Context, sys fs.FS, found FoundDB) (*Database, error) { default: panic("unreachable") } - zlog.Debug(ctx). - Stringer("db", found). - Msg("opened database") + log.DebugContext(ctx, "opened database", "db", found) if v, ok := db.headers.(validator); ok { if err := v.Validate(ctx); err != nil { diff --git a/internal/rpm/info.go b/internal/rpm/info.go index c05414df7..a7893b34e 100644 --- a/internal/rpm/info.go +++ b/internal/rpm/info.go @@ -223,15 +223,16 @@ var ( // FilePatterns is a regular expression for *any* file that may need to be // recorded alongside a package. // - // The tested strings are absolute paths. + // The tested strings are paths relative to the rpm root (almost always "/"). filePatterns = sync.OnceValue(func() *regexp.Regexp { pat := []string{ `^.*/[^/]+\.[ejw]ar$`, // Jar files `^.*/site-packages/[^/]+\.egg-info/PKG-INFO$`, // Python packages `^.*/package.json$`, // npm packages `^.*/[^/]+\.gemspec$`, // ruby gems - `^/usr/s?bin/[^/]+$`, // any executable - `^/usr/libexec/[^/]+/[^/]+$`, // sometimes the executables are here too + `^usr/s?bin/[^/]+$`, // any executable + `^usr/libexec/[^/]+/[^/]+$`, // sometimes the executables are here too + `^usr/lib/golang/(?:[^/]+/)+[^/]+$`, // golang executables are here for some RH layers } return regexp.MustCompile(strings.Join(pat, `|`)) }) diff --git a/internal/rpm/ndb/package_test.go b/internal/rpm/ndb/package_test.go index 3014725bd..455c94c1c 100644 --- a/internal/rpm/ndb/package_test.go +++ b/internal/rpm/ndb/package_test.go @@ -2,19 +2,14 @@ package ndb import ( "bytes" - "context" "io/fs" "os" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore/internal/rpm/rpmdb" ) func TestLoadPackage(t *testing.T) { - ctx := context.Background() - dir := os.DirFS("testdata") ms, err := fs.Glob(dir, "Packages*.db") if err != nil || len(ms) == 0 { @@ -22,7 +17,7 @@ func TestLoadPackage(t *testing.T) { } for _, n := range ms { t.Run(n, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := t.Context() b, err := fs.ReadFile(dir, n) if err != nil { t.Fatal(err) diff --git a/internal/rpm/rpmdb/header_test.go b/internal/rpm/rpmdb/header_test.go index 6f1331dd5..e4b5f5806 100644 --- a/internal/rpm/rpmdb/header_test.go +++ b/internal/rpm/rpmdb/header_test.go @@ -1,15 +1,12 @@ package rpmdb import ( - "context" "os" "testing" - - "github.com/quay/zlog" ) func TestParseHeader(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := t.Context() f, err := os.Open(`testdata/package.header`) if err != nil { t.Fatal(err) diff --git a/internal/rpm/sqlite/sqlite_test.go b/internal/rpm/sqlite/sqlite_test.go index e22ed6c75..92a994d9f 100644 --- a/internal/rpm/sqlite/sqlite_test.go +++ b/internal/rpm/sqlite/sqlite_test.go @@ -1,7 +1,6 @@ package sqlite import ( - "context" "encoding/json" "hash/crc64" "io" @@ -10,11 +9,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" ) func TestPackages(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := t.Context() h := crc64.New(crc64.MakeTable(crc64.ISO)) // Copying to a tempdir is needed if the tests are being run from a prepared @@ -87,7 +85,7 @@ func TestPackages(t *testing.T) { } func TestValidate(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := t.Context() dbfile := filepath.Join(t.TempDir(), `no_packages.sqlite`) dst, err := os.Create(dbfile) diff --git a/java/jar/jar.go b/java/jar/jar.go index cadc50596..0eba71234 100644 --- a/java/jar/jar.go +++ b/java/jar/jar.go @@ -36,13 +36,14 @@ import ( "fmt" "io" "io/fs" + "log/slog" "net/textproto" "path" "path/filepath" "regexp" "strings" - "github.com/quay/zlog" + "github.com/quay/claircore/toolkit/log" ) // MinSize is the absolute minimum size for a jar. @@ -60,10 +61,7 @@ const MinSize = 22 // The provided name is expected to be the full path within the layer to the jar // file being provided as "z". func Parse(ctx context.Context, name string, z *zip.Reader) ([]Info, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "java/jar/Parse", - "jar", name) - return parse(ctx, srcPath{name}, z) + return parse(log.With(ctx, "jar", name), srcPath{name}, z) } // SrcPath is a helper for tracking where an archive member is. @@ -90,9 +88,7 @@ func (p *srcPath) Pop() string { // Parse is the inner function that uses a srcPath to keep track of recursions. func parse(ctx context.Context, name srcPath, z *zip.Reader) ([]Info, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "java/jar/Parse", - "name", name.String()) + ctx = log.With(ctx, "name", name.String()) // This uses an admittedly non-idiomatic, C-like goto construction. We want // to attempt a few heuristics and keep the results of the first one that @@ -108,8 +104,7 @@ func parse(ctx context.Context, name srcPath, z *zip.Reader) ([]Info, error) { ret, err = extractProperties(ctx, name, z) switch { case errors.Is(err, nil): - zlog.Debug(ctx). - Msg("using discovered properties file(s)") + slog.DebugContext(ctx, "using discovered properties file(s)") goto Finish case errors.Is(err, errUnpopulated): case strings.HasPrefix(base, "javax") && errors.Is(err, ErrNotAJar): @@ -120,8 +115,7 @@ func parse(ctx context.Context, name srcPath, z *zip.Reader) ([]Info, error) { i, err = extractManifest(ctx, name, z) switch { case errors.Is(err, nil): - zlog.Debug(ctx). - Msg("using discovered manifest") + slog.DebugContext(ctx, "using discovered manifest") ret = append(ret, i) goto Finish case errors.Is(err, errUnpopulated) || errors.Is(err, errInsaneManifest): @@ -133,8 +127,7 @@ func parse(ctx context.Context, name srcPath, z *zip.Reader) ([]Info, error) { i, err = checkName(ctx, name.Cur()) switch { case errors.Is(err, nil): - zlog.Debug(ctx). - Msg("using name mangling") + slog.DebugContext(ctx, "using name mangling") ret = append(ret, i) goto Finish case errors.Is(err, errUnpopulated): @@ -154,9 +147,7 @@ Finish: return nil, archiveErr(name, err) } if ct := len(inner); ct != 0 { - zlog.Debug(ctx). - Int("count", ct). - Msg("found embedded jars") + slog.DebugContext(ctx, "found embedded jars", "count", ct) } ret = append(ret, inner...) @@ -210,14 +201,12 @@ func extractProperties(ctx context.Context, name srcPath, z *zip.Reader) ([]Info // encoded in the file names. p := normName(f.Name) if path.Base(p) == filename { - zlog.Debug(ctx). - Str("path", p). - Msg("found properties file") + slog.DebugContext(ctx, "found properties file", "path", p) pf = append(pf, p) } } if len(pf) == 0 { - zlog.Debug(ctx).Msg("properties not found") + slog.DebugContext(ctx, "properties not found") return nil, errUnpopulated } ret := make([]Info, len(pf)) @@ -244,7 +233,7 @@ func extractProperties(ctx context.Context, name srcPath, z *zip.Reader) ([]Info // ExtractInner recurses into anything that looks like a jar in "z". func extractInner(ctx context.Context, p srcPath, z *zip.Reader) ([]Info, error) { - ctx = zlog.ContextWithValues(ctx, "parent", p.String()) + ctx = log.With(ctx, "parent", p.String()) var ret []Info // Zips need random access, so allocate a buffer for any we find. var buf bytes.Buffer @@ -258,7 +247,7 @@ func extractInner(ctx context.Context, p srcPath, z *zip.Reader) ([]Info, error) fi := f.FileInfo() // Check size. if fi.Size() < MinSize { - zlog.Debug(ctx).Str("member", name).Msg("not actually a jar: too small") + slog.DebugContext(ctx, "not actually a jar: too small", "member", name) return nil } rc, err := f.Open() @@ -286,10 +275,7 @@ func extractInner(ctx context.Context, p srcPath, z *zip.Reader) ([]Info, error) // opening malformed zips. fallthrough case errors.Is(err, zip.ErrFormat): - zlog.Debug(ctx). - Str("member", name). - Err(err). - Msg("not actually a jar: invalid zip") + slog.DebugContext(ctx, "not actually a jar: invalid zip", "member", name) return nil default: return mkErr("failed opening inner zip", err) @@ -302,10 +288,7 @@ func extractInner(ctx context.Context, p srcPath, z *zip.Reader) ([]Info, error) case errors.Is(err, nil): case errors.Is(err, ErrNotAJar) || errors.Is(err, errInsaneManifest): - zlog.Debug(ctx). - Str("member", name). - Err(err). - Msg("not actually a jar") + slog.DebugContext(ctx, "not actually a jar", "reason", err, "member", name) return nil default: return mkErr("parse error", err) @@ -326,8 +309,7 @@ func extractInner(ctx context.Context, p srcPath, z *zip.Reader) ([]Info, error) } if len(ret) == 0 { - zlog.Debug(ctx). - Msg("found no bundled jars") + slog.DebugContext(ctx, "found no bundled jars") } return ret, nil } @@ -347,8 +329,7 @@ var nameRegexp = regexp.MustCompile(`([[:graph:]]+)-([[:digit:]][\-.[:alnum:]]*( func checkName(ctx context.Context, name string) (Info, error) { m := nameRegexp.FindStringSubmatch(filepath.Base(name)) if m == nil { - zlog.Debug(ctx). - Msg("name not useful") + slog.DebugContext(ctx, "name not useful") return Info{}, errUnpopulated } return Info{ @@ -414,26 +395,20 @@ func (i *Info) parseManifest(ctx context.Context, r io.Reader) error { tp := textproto.NewReader(bufio.NewReader(newMainSectionReader(r))) hdr, err := tp.ReadMIMEHeader() if err != nil { - zlog.Debug(ctx). - Err(err). - Msg("unable to read manifest") + slog.DebugContext(ctx, "unable to read manifest", "reason", err) return errInsaneManifest } // Sanity checks: switch { case len(hdr) == 0: - zlog.Debug(ctx). - Msg("no headers found") + slog.DebugContext(ctx, "no headers found") return errInsaneManifest case !manifestVer.MatchString(hdr.Get("Manifest-Version")): v := hdr.Get("Manifest-Version") - zlog.Debug(ctx). - Str("manifest_version", v). - Msg("invalid manifest version") + slog.DebugContext(ctx, "invalid manifest version", "manifest_version", v) return errInsaneManifest case hdr.Get("Name") != "": - zlog.Debug(ctx). - Msg("martian manifest") + slog.DebugContext(ctx, "martian manifest") // This shouldn't be happening in the Main section. return errInsaneManifest } @@ -494,9 +469,8 @@ func (i *Info) parseManifest(ctx context.Context, r io.Reader) error { } if name == "" || version == "" { - zlog.Debug(ctx). - Strs("attrs", []string{name, version}). - Msg("manifest not useful") + slog.DebugContext(ctx, "manifest not useful", + slog.Group("attrs", "name", name, "version", version)) return errUnpopulated } i.Name = name @@ -640,9 +614,8 @@ func (i *Info) parseProperties(ctx context.Context, r io.Reader) error { return mkErr("properties scanner", err) } if group == "" || artifact == "" || version == "" { - zlog.Debug(ctx). - Strs("attrs", []string{group, artifact, version}). - Msg("properties not useful") + slog.DebugContext(ctx, "properties not useful", + slog.Group("attrs", "group", group, "artifact", artifact, "version", version)) return errUnpopulated } diff --git a/java/jar/jar_test.go b/java/jar/jar_test.go index 89e5eb8b2..47e43ebd9 100644 --- a/java/jar/jar_test.go +++ b/java/jar/jar_test.go @@ -5,7 +5,6 @@ import ( "archive/zip" "bytes" "compress/gzip" - "context" "crypto/sha256" "encoding/binary" "encoding/hex" @@ -21,7 +20,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/quay/zlog" "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" @@ -31,7 +29,6 @@ import ( func TestParse(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) const url = `https://repo1.maven.org/maven2/com/orientechnologies/orientdb-community/3.2.37/orientdb-community-3.2.37.tar.gz` const sha = `101d93340ae17cfdc622ef37e30c8c3993874ab199fd5ff3fc76d466740fefe3` name := fetch(t, url, sha) @@ -55,7 +52,7 @@ func TestParse(t *testing.T) { } t.Log("found jar:", h.Name) t.Run(filepath.Base(h.Name), func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) buf.Reset() buf.Grow(int(h.Size)) n, err := io.Copy(&buf, tr) @@ -89,7 +86,7 @@ func TestParse(t *testing.T) { func TestWAR(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) const url = `https://get.jenkins.io/war/2.311/jenkins.war` const sha = `fe21501800c769279699ecf511fd9b495b1cb3ebd226452e01553ff06820910a` name := fetch(t, url, sha) @@ -174,7 +171,6 @@ func fetch(t testing.TB, u string, ck string) (name string) { } func TestJAR(t *testing.T) { - ctx := context.Background() td := os.DirFS("testdata/jar") ls, err := fs.ReadDir(td, ".") if err != nil { @@ -194,7 +190,7 @@ func TestJAR(t *testing.T) { continue } t.Run(n, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f, err := td.Open(ent.Name()) if err != nil { t.Error(err) @@ -232,7 +228,6 @@ func TestJAR(t *testing.T) { } func TestJARBadManifest(t *testing.T) { - ctx := context.Background() path := "testdata/malformed-manifests" d := os.DirFS(path) ls, err := fs.ReadDir(d, ".") @@ -246,7 +241,7 @@ func TestJARBadManifest(t *testing.T) { for _, n := range ls { t.Log(n) t.Run(n.Name(), func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f, err := os.Open(filepath.Join(path, n.Name())) if err != nil { t.Fatal(err) @@ -265,7 +260,6 @@ func TestJARBadManifest(t *testing.T) { // them gracefully. func TestMalformed(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) t.Run("BadOffset", func(t *testing.T) { const ( @@ -327,6 +321,7 @@ func TestMalformed(t *testing.T) { if err != nil { t.Fatal(err) } + ctx := test.Logging(t) infos, err := Parse(ctx, jarName, z) t.Logf("returned error: %v", err) switch { @@ -426,7 +421,6 @@ func TestMalformed(t *testing.T) { t.Fail() } }) - } func TestManifestSectionReader(t *testing.T) { @@ -487,7 +481,7 @@ func TestInnerJar(t *testing.T) { } }) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) got, err := Parse(ctx, name, &rc.Reader) if err != nil { t.Fatal(err) diff --git a/java/jar/manifest_test.go b/java/jar/manifest_test.go index 45affe801..c7f1446c9 100644 --- a/java/jar/manifest_test.go +++ b/java/jar/manifest_test.go @@ -2,7 +2,6 @@ package jar import ( "bytes" - "context" "errors" "io" "io/fs" @@ -11,12 +10,12 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) func TestParseManifest(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) md := os.DirFS("testdata/manifest") fs, err := fs.ReadDir(md, ".") @@ -49,7 +48,7 @@ func TestParseManifest(t *testing.T) { func TestParseManifest_JenkinsPlugins(t *testing.T) { var i Info - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) for _, tc := range jenkinsPlugins { t.Run(tc.Name, func(t *testing.T) { err := i.parseManifest(ctx, strings.NewReader(tc.Contents)) diff --git a/java/jar/name_test.go b/java/jar/name_test.go index e3ef3f504..33d1fb484 100644 --- a/java/jar/name_test.go +++ b/java/jar/name_test.go @@ -1,18 +1,17 @@ package jar import ( - "context" "errors" "io/fs" "os" "testing" - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) func TestCheckName(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) ms, err := fs.Glob(os.DirFS("testdata"), "*/*.jar") if err != nil { diff --git a/java/jar/properties_test.go b/java/jar/properties_test.go index db0ed88f7..a7b2d19e7 100644 --- a/java/jar/properties_test.go +++ b/java/jar/properties_test.go @@ -2,18 +2,17 @@ package jar import ( "bytes" - "context" "io" "io/fs" "os" "testing" - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) func TestParseProperties(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) md := os.DirFS("testdata/properties") fs, err := fs.ReadDir(md, ".") diff --git a/java/matcher.go b/java/matcher.go index fedfdf308..9b96f45c1 100644 --- a/java/matcher.go +++ b/java/matcher.go @@ -2,10 +2,9 @@ package java import ( "context" + "log/slog" "net/url" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/internal/maven" "github.com/quay/claircore/libvuln/driver" @@ -53,10 +52,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v if introduced != "" { iv, err := maven.ParseVersion(introduced) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", introduced). - Msg("unable to parse maven introduced version") + slog.WarnContext(ctx, "unable to parse maven introduced version", + "package", vuln.Package.Name, + "version", introduced) return false, err } // If the package's version is less than the introduced version, it's not vulnerable. @@ -71,10 +69,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v case fixedVersion != "": fv, err := maven.ParseVersion(fixedVersion) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", fixedVersion). - Msg("unable to parse maven fixed version") + slog.WarnContext(ctx, "unable to parse maven fixed version", + "package", vuln.Package.Name, + "version", fixedVersion) return false, err } // The package is affected if its version is less than the fixed version. @@ -82,10 +79,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v case lastAffected != "": la, err := maven.ParseVersion(lastAffected) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", lastAffected). - Msg("unable to parse maven last_affected version") + slog.WarnContext(ctx, "unable to parse maven last_affected version", + "package", vuln.Package.Name, + "version", lastAffected) return false, err } // The package is affected if its version is less than or equal to the last affected version. diff --git a/java/matcher_test.go b/java/matcher_test.go index 026138c8a..05859fb2f 100644 --- a/java/matcher_test.go +++ b/java/matcher_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" @@ -204,5 +203,5 @@ func TestVulnerable(t *testing.T) { } func TestMatcher(t *testing.T) { - test.RunMatcherTests(zlog.Test(context.Background(), t), t, "testdata/matcher", new(Matcher)) + test.RunMatcherTests(test.Logging(t), t, "testdata/matcher", new(Matcher)) } diff --git a/java/packagescanner.go b/java/packagescanner.go index 457cc1cbd..eeba37458 100644 --- a/java/packagescanner.go +++ b/java/packagescanner.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "runtime/trace" @@ -19,8 +20,6 @@ import ( "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/java/jar" @@ -82,9 +81,6 @@ func (*Scanner) Kind() string { return "package" } // Configure implements indexer.RPCScanner. func (s *Scanner) Configure(ctx context.Context, f indexer.ConfigDeserializer, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, - "component", "java/Scanner.Configure", - "version", s.Version()) var cfg ScannerConfig s.client = c if err := f(&cfg); err != nil { @@ -92,7 +88,7 @@ func (s *Scanner) Configure(ctx context.Context, f indexer.ConfigDeserializer, c } if cfg.DisableAPI { - zlog.Debug(ctx).Msg("search API disabled") + slog.DebugContext(ctx, "search API disabled") } else { api := DefaultSearchAPI if cfg.API != "" { @@ -103,10 +99,9 @@ func (s *Scanner) Configure(ctx context.Context, f indexer.ConfigDeserializer, c requestTimeout = cfg.APIRequestTimeout } s.rootRequestTimeout = requestTimeout - zlog.Debug(ctx). - Str("api", api). - Float64("requestTimeout", requestTimeout.Seconds()). - Msg("configured search API URL") + slog.DebugContext(ctx, "configured search API URL", + "api", api, + "requestTimeout", requestTimeout) u, err := url.Parse(api) if err != nil { return err @@ -124,12 +119,8 @@ func (s *Scanner) Configure(ctx context.Context, f indexer.ConfigDeserializer, c func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "java/Scanner.Scan", - "version", s.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") if err := ctx.Err(); err != nil { return nil, err } @@ -154,11 +145,9 @@ func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircor return nil, fmt.Errorf("java: unable to check RPM db: %w", err) } for _, n := range ars { - ctx := zlog.ContextWithValues(ctx, "file", n) + log := slog.With("path", n) if set.Contains(n) { - zlog.Debug(ctx). - Str("path", n). - Msg("file path determined to be of RPM origin") + log.DebugContext(ctx, "file path determined to be of RPM origin") continue } @@ -192,9 +181,7 @@ func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircor // opening malformed zips. fallthrough case errors.Is(err, zip.ErrFormat): - zlog.Info(ctx). - Err(err). - Msg("not actually a jar: invalid zip") + log.InfoContext(ctx, "not actually a jar: invalid zip", "reason", err) continue default: return nil, err @@ -205,9 +192,7 @@ func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircor case err == nil: case errors.Is(err, jar.ErrNotAJar): // Could not prove this is really a jar. Skip it and move on. - zlog.Info(ctx). - AnErr("reason", err). - Msg("skipping jar") + log.InfoContext(ctx, "skipping jar", "reason", err) continue default: return nil, err @@ -301,9 +286,7 @@ func (s *Scanner) search(ctx context.Context, i *jar.Info, ck []byte) error { defer done() req, err := http.NewRequestWithContext(tctx, http.MethodGet, s.root.String(), nil) if err != nil { - zlog.Warn(ctx). - Err(err). - Msg("unable to construct request") + slog.WarnContext(ctx, "unable to construct request", "reason", err) return errRPC } v := req.URL.Query() @@ -314,30 +297,24 @@ func (s *Scanner) search(ctx context.Context, i *jar.Info, ck []byte) error { req.URL.RawQuery = v.Encode() res, err := s.client.Do(req) if err != nil { - zlog.Warn(ctx). - Err(err). - Msg("error making request") + slog.WarnContext(ctx, "error making request", "reason", err) return errRPC } if res.StatusCode != http.StatusOK { res.Body.Close() - zlog.Warn(ctx). - Str("status", res.Status). - Msg("unexpected response status") + slog.WarnContext(ctx, "unexpected response status", "status", res.Status) return errRPC } var sr searchResponse err = json.NewDecoder(res.Body).Decode(&sr) res.Body.Close() if err != nil { - zlog.Warn(ctx). - Err(err). - Msg("error decoding json") + slog.WarnContext(ctx, "error decoding json", "reason", err) return errRPC } success = true if len(sr.Response.Doc) == 0 { - zlog.Debug(ctx).Msg("no matching artifacts found") + slog.DebugContext(ctx, "no matching artifacts found") return nil } // Sort and then take the first one, because apparently the same diff --git a/java/packagescanner_test.go b/java/packagescanner_test.go index 4456a9790..04067c315 100644 --- a/java/packagescanner_test.go +++ b/java/packagescanner_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "github.com/quay/zlog" "net/http" "net/http/httptest" "path" @@ -22,8 +21,7 @@ func TestDisableAPI(t *testing.T) { t.Fatal("external http request invoked when none was expected") })) - ctx := context.Background() - ctx = zlog.Test(ctx, t) + ctx := test.Logging(t) scanner := &java.Scanner{} var buf bytes.Buffer cfg := &java.ScannerConfig{ diff --git a/java/purl.go b/java/purl.go new file mode 100644 index 000000000..10b520a65 --- /dev/null +++ b/java/purl.go @@ -0,0 +1,47 @@ +package java + +import ( + "context" + "fmt" + "strings" + + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for Java packages. + PURLType = "maven" +) + +// GeneratePURL generates a Maven PURL for a given [claircore.IndexRecord]. +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + // The PURL examples in the spec show that the group ID is used + // as the namespace for Maven PURLs, so split the package name on the colon. + // https://github.com/package-url/purl-spec?tab=readme-ov-file#some-purl-examples + parts := strings.SplitN(ir.Package.Name, ":", 2) + if len(parts) != 2 { + return packageurl.PackageURL{}, fmt.Errorf("invalid package name: %s", ir.Package.Name) + } + return packageurl.PackageURL{ + Type: PURLType, + Namespace: parts[0], + Name: parts[1], + Version: ir.Package.Version, + }, nil +} + +// ParsePURL parses a Maven PURL into a list of [claircore.IndexRecord]s. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Namespace + ":" + purl.Name, + Version: purl.Version, + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, nil +} diff --git a/java/purl_test.go b/java/purl_test.go new file mode 100644 index 000000000..d3fb08484 --- /dev/null +++ b/java/purl_test.go @@ -0,0 +1,106 @@ +package java + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordJava(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "org.apache.commons:commons-lang3", + Version: "3.12.0", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + { + name: "different-version", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "com.fasterxml.jackson.core:jackson-databind", + Version: "2.17.1", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParsePURL: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "org.slf4j:slf4j-api", + Version: "2.0.12", + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: "org.slf4j", + Name: "slf4j-api", + Version: "2.0.12", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore fields that are not part of the PURL round-trip for Java. + cmpopts.IgnoreFields(claircore.Package{}, "PackageDB", "Filepath"), +} diff --git a/libindex/fetcher.go b/libindex/fetcher.go index d641a03f9..41ca4dc06 100644 --- a/libindex/fetcher.go +++ b/libindex/fetcher.go @@ -8,13 +8,13 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "os" "runtime" "strings" - "github.com/quay/zlog" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" @@ -154,15 +154,11 @@ func (f closeFunc) Close() error { // Because we know we're the only concurrent call that's dealing with this blob, // we can be a bit more lax. func (a *RemoteFetchArena) fetchFileForCache(ctx context.Context, desc *claircore.LayerDescription) (*os.File, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "libindex/RemoteFetchArena.fetchFileForCache", - "arena", a.root.Name(), - "layer", desc.Digest, - "uri", desc.URI) - ctx, span := tracer.Start(ctx, "RemoteFetchArena.fetchFileForCache") + log := slog.With("arena", a.root.Name(), "layer", desc.Digest, "uri", desc.URI) + ctx, span := tracer.Start(ctx, "RemoteFetchArena.fetchUnlinkedFile") defer span.End() span.SetStatus(codes.Error, "") - zlog.Debug(ctx).Msg("layer fetch start") + log.DebugContext(ctx, "layer fetch start") // Validate the layer input. if desc.URI == "" { @@ -215,9 +211,7 @@ func (a *RemoteFetchArena) fetchFileForCache(ctx context.Context, desc *claircor defer zr.Close() // Look at the content-type and optionally fix it up. ct := resp.Header.Get("content-type") - zlog.Debug(ctx). - Str("content-type", ct). - Msg("reported content-type") + log.DebugContext(ctx, "reported content-type", "content-type", ct) span.SetAttributes(attribute.String("payload.content-type", ct), attribute.Stringer("payload.compression.detected", kind)) if ct == "" || ct == "text/plain" || ct == "binary/octet-stream" || ct == "application/octet-stream" { switch kind { @@ -230,9 +224,7 @@ func (a *RemoteFetchArena) fetchFileForCache(ctx context.Context, desc *claircor default: return nil, fmt.Errorf("fetcher: disallowed compression kind: %q", kind.String()) } - zlog.Debug(ctx). - Str("content-type", ct). - Msg("fixed content-type") + log.DebugContext(ctx, "fixed content-type", "content-type", ct) span.SetAttributes(attribute.String("payload.content-type.detected", ct)) } @@ -275,18 +267,12 @@ func (a *RemoteFetchArena) fetchFileForCache(ctx context.Context, desc *claircor return } if err := f.Close(); err != nil { - zlog.Warn(ctx). - Err(err). - Msg("error closing spoolfile in error return") + log.WarnContext(ctx, "error closing spoolfile in error return", "reason", err) } }() buf := bufio.NewWriter(f) n, err := io.Copy(buf, zr) - zlog.Debug(ctx). - Int64("size", n). - Bool("big", n >= bigLayerSize). - AnErr("copy_error", err). - Msg("wrote file") + log.DebugContext(ctx, "wrote file", "size", n, "big", n >= bigLayerSize, "copy_error", err) // TODO(hank) Add a metric for "big files" and a histogram for size. if err != nil { return nil, err @@ -301,7 +287,7 @@ func (a *RemoteFetchArena) fetchFileForCache(ctx context.Context, desc *claircor return nil, err } - zlog.Debug(ctx).Msg("layer fetch ok") + log.DebugContext(ctx, "layer fetch ok") span.SetStatus(codes.Ok, "") fileOK = true return f, nil @@ -352,8 +338,6 @@ func (p *FetchProxy) Realize(ctx context.Context, ls []*claircore.Layer) error { // RealizeDescriptions returns [claircore.Layer] structs populated according to // the passed slice of [claircore.LayerDescription]. func (p *FetchProxy) RealizeDescriptions(ctx context.Context, descs []claircore.LayerDescription) ([]claircore.Layer, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "libindex/FetchProxy.RealizeDescriptions") ctx, span := tracer.Start(ctx, "RealizeDescriptions") defer span.End() g, ctx := errgroup.WithContext(ctx) diff --git a/libindex/fetcher_test.go b/libindex/fetcher_test.go index 03375ccd6..00e763ea3 100644 --- a/libindex/fetcher_test.go +++ b/libindex/fetcher_test.go @@ -16,8 +16,6 @@ import ( "sync/atomic" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/internal/wart" "github.com/quay/claircore/test" @@ -29,7 +27,7 @@ type fetchTestcase struct { func (tc fetchTestcase) Run(ctx context.Context) func(*testing.T) { return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) c, descs := test.ServeLayers(t, tc.N) for _, l := range descs { t.Logf("%+v", l) @@ -65,7 +63,6 @@ func TestFetchSimple(t *testing.T) { func TestFetchInvalid(t *testing.T) { // TODO(hank) Rewrite this into unified testcases. - ctx := context.Background() tt := []struct { name string layer []*claircore.Layer @@ -91,7 +88,7 @@ func TestFetchInvalid(t *testing.T) { tmp := t.TempDir() for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) a := NewRemoteFetchArena(http.DefaultClient, tmp) fetcher := a.Realizer(ctx) if err := fetcher.Realize(ctx, table.layer); err == nil { @@ -103,7 +100,7 @@ func TestFetchInvalid(t *testing.T) { func TestFetchConcurrent(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) descs, h := commonLayerServer(t, 25) srv := httptest.NewUnstartedServer(h) srv.Start() @@ -119,8 +116,6 @@ func TestFetchConcurrent(t *testing.T) { }) t.Run("OldInterface", func(t *testing.T) { - ctx := zlog.Test(ctx, t) - t.Run("Thread", func(t *testing.T) { run := func(a *RemoteFetchArena, ls []claircore.LayerDescription) func(*testing.T) { ps := wart.DescriptionsToLayers(ls) @@ -132,7 +127,7 @@ func TestFetchConcurrent(t *testing.T) { }) return func(t *testing.T) { t.Parallel() - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f := a.Realizer(ctx) t.Cleanup(func() { if err := f.Close(); err != nil { @@ -151,7 +146,6 @@ func TestFetchConcurrent(t *testing.T) { }) t.Run("NewInterface", func(t *testing.T) { - ctx := zlog.Test(ctx, t) t.Run("Thread", func(t *testing.T) { run := func(a *RemoteFetchArena, descs []claircore.LayerDescription) func(*testing.T) { ds := make([]claircore.LayerDescription, len(descs)) @@ -164,7 +158,7 @@ func TestFetchConcurrent(t *testing.T) { }) return func(t *testing.T) { t.Parallel() - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f := a.Realizer(ctx).(*FetchProxy) defer func() { if err := f.Close(); err != nil { @@ -239,7 +233,6 @@ func commonLayerServer(t testing.TB, ct int) ([]claircore.LayerDescription, http case total < max: t.Logf("prevented %[3]d fetches: %[1]d < %d", total, max, max-total) } - }) inner := http.FileServer(http.Dir(dir)) return descs, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/libindex/libindex.go b/libindex/libindex.go index 117d06d4f..ffd65364a 100644 --- a/libindex/libindex.go +++ b/libindex/libindex.go @@ -7,11 +7,11 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "sort" "time" - "github.com/quay/zlog" "golang.org/x/sync/errgroup" "github.com/quay/claircore" @@ -27,6 +27,7 @@ import ( "github.com/quay/claircore/rhel/rhcc" "github.com/quay/claircore/rpm" "github.com/quay/claircore/ruby" + "github.com/quay/claircore/toolkit/log" "github.com/quay/claircore/whiteout" ) @@ -70,7 +71,6 @@ type Libindex struct { // The passed http.Client will be used for fetching layers and any HTTP requests // made by scanners. func New(ctx context.Context, opts *Options, cl *http.Client) (*Libindex, error) { - ctx = zlog.ContextWithValues(ctx, "component", "libindex/New") // required if opts.Locker == nil { return nil, fmt.Errorf("field Locker cannot be nil") @@ -142,7 +142,7 @@ func New(ctx context.Context, opts *Options, cl *http.Client) (*Libindex, error) return nil, fmt.Errorf("failed to set the indexer state: %v", err) } - zlog.Info(ctx).Msg("registered configured scanners") + slog.InfoContext(ctx, "registered configured scanners") l.vscnrs = vscnrs // create indexer.Options @@ -176,13 +176,11 @@ func (l *Libindex) Close(ctx context.Context) error { // If the index operation cannot start an error will be returned. // If an error occurs during scan the error will be propagated inside the IndexReport. func (l *Libindex) Index(ctx context.Context, manifest *claircore.Manifest) (*claircore.IndexReport, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "libindex/Libindex.Index", - "manifest", manifest.Hash.String()) - zlog.Info(ctx).Msg("index request start") - defer zlog.Info(ctx).Msg("index request done") + ctx = log.With(ctx, "manifest", manifest.Hash) + slog.InfoContext(ctx, "index request start") + defer slog.InfoContext(ctx, "index request done") - zlog.Debug(ctx).Msg("locking attempt") + slog.DebugContext(ctx, "locking attempt") lc, done := l.locker.Lock(ctx, manifest.Hash.String()) defer done() // The process may have waited on the lock, so check that the context is @@ -190,7 +188,7 @@ func (l *Libindex) Index(ctx context.Context, manifest *claircore.Manifest) (*cl if err := lc.Err(); !errors.Is(err, nil) { return nil, err } - zlog.Debug(ctx).Msg("locking OK") + slog.DebugContext(ctx, "locking OK") c := l.ControllerFactory(l.indexerOptions) return c.Index(lc, manifest) } @@ -242,7 +240,6 @@ func (l *Libindex) IndexReport(ctx context.Context, hash claircore.Digest) (*cla // AffectedManifests retrieves a list of affected manifests when provided a list of vulnerabilities. func (l *Libindex) AffectedManifests(ctx context.Context, vulns []claircore.Vulnerability) (*claircore.AffectedManifests, error) { - ctx = zlog.ContextWithValues(ctx, "component", "libindex/Libindex.AffectedManifests") om := omnimatcher.New(nil) affected := claircore.NewAffectedManifests() @@ -285,6 +282,5 @@ V: // // Providing an unknown digest is not an error. func (l *Libindex) DeleteManifests(ctx context.Context, d ...claircore.Digest) ([]claircore.Digest, error) { - ctx = zlog.ContextWithValues(ctx, "component", "libindex/Libindex.DeleteManifests") return l.store.DeleteManifests(ctx, d...) } diff --git a/libindex/libindex_integration_test.go b/libindex/libindex_integration_test.go index 3441d4906..99230be17 100644 --- a/libindex/libindex_integration_test.go +++ b/libindex/libindex_integration_test.go @@ -12,7 +12,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jackc/pgx/v5/pgxpool" - "github.com/quay/zlog" "go.uber.org/mock/gomock" "github.com/quay/claircore" @@ -157,7 +156,7 @@ func (tc testcase) Run(ctx context.Context, check checkFunc) func(*testing.T) { return func(t *testing.T) { t.Parallel() integration.NeedDB(t) - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) pool := pgtest.TestIndexerDB(ctx, t) tc.RunInner(ctx, t, pool, check) } diff --git a/libindex/libindex_test.go b/libindex/libindex_test.go index 35ff67dce..b1fa99571 100644 --- a/libindex/libindex_test.go +++ b/libindex/libindex_test.go @@ -8,10 +8,10 @@ import ( "strconv" "testing" - "github.com/quay/zlog" "go.uber.org/mock/gomock" "github.com/quay/claircore" + "github.com/quay/claircore/test" indexer "github.com/quay/claircore/test/mock/indexer" ) @@ -45,7 +45,6 @@ func digest(inp string) claircore.Digest { } func TestAffectedManifests(t *testing.T) { - ctx := context.Background() tt := []struct { name string inputVulns []claircore.Vulnerability @@ -94,7 +93,7 @@ func TestAffectedManifests(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) s := table.mockStore(t) li := &Libindex{store: s} @@ -115,7 +114,7 @@ func TestAffectedManifests(t *testing.T) { t.Run("CancelledContext", func(t *testing.T) { for _, table := range tt { t.Run(table.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) ctx, cancel := context.WithCancelCause(ctx) want := errors.New("early cancel") cancel(want) @@ -138,7 +137,6 @@ func TestAffectedManifests(t *testing.T) { } func BenchmarkAffectedManifests(b *testing.B) { - ctx := context.Background() // create store ctrl := gomock.NewController(b) s := indexer.NewMockStore(ctrl) @@ -151,7 +149,7 @@ func BenchmarkAffectedManifests(b *testing.B) { nil, ).MaxTimes(100 * b.N) - ctx = zlog.Test(ctx, b) + ctx := test.Logging(b) li := &Libindex{store: s} for b.Loop() { diff --git a/libindex/tempfile_linux.go b/libindex/tempfile_linux.go index 16efa55b4..fab396bfe 100644 --- a/libindex/tempfile_linux.go +++ b/libindex/tempfile_linux.go @@ -64,7 +64,7 @@ Loop: if !tmpOK && err == nil { // This is just a best-effort action to keep files from accumulating. // The correct way is to use the kernel feature for this: the O_TMPFILE flag. - _ = os.Remove(f.Name()) + _ = dir.Remove(f.Name()) } if err != nil { diff --git a/libindex/tempfile_unix.go b/libindex/tempfile_unix.go index 4627273fd..f3b75d733 100644 --- a/libindex/tempfile_unix.go +++ b/libindex/tempfile_unix.go @@ -23,6 +23,6 @@ func openTemp(dir *os.Root) (f *os.File, err error) { if err != nil { return nil, err } - runtime.AddCleanup(f, func(n string) { os.Remove(n) }, f.Name()) + runtime.AddCleanup(f, func(n string) { dir.Remove(n) }, f.Name()) return f, nil } diff --git a/libvuln/libvuln.go b/libvuln/libvuln.go index e7119297d..65f07ffae 100644 --- a/libvuln/libvuln.go +++ b/libvuln/libvuln.go @@ -3,13 +3,12 @@ package libvuln import ( "context" "fmt" + "log/slog" "math/rand" "reflect" "time" "github.com/google/uuid" - "github.com/quay/zlog" - "github.com/rs/zerolog" "github.com/quay/claircore" "github.com/quay/claircore/datastore" @@ -33,7 +32,6 @@ type Libvuln struct { updaters *updates.Manager } -// TODO (crozzy): Find a home for this and stop redefining it. // LockSource abstracts over how locks are implemented. // // An online system needs distributed locks, offline use cases can use @@ -44,10 +42,10 @@ type LockSource interface { Close(context.Context) error } +// TODO (crozzy): Find a home for this and stop redefining it. + // New creates a new instance of the Libvuln library func New(ctx context.Context, opts *Options) (*Libvuln, error) { - ctx = zlog.ContextWithValues(ctx, "component", "libvuln/New") - // required if opts.Store == nil { return nil, fmt.Errorf("libvuln: must provide a Store") @@ -97,7 +95,7 @@ func New(ctx context.Context, opts *Options) (*Libvuln, error) { return nil, err } - zlog.Info(ctx).Array("matchers", matcherLog(l.matchers)).Msg("matchers created") + slog.InfoContext(ctx, "matchers created", "matcher", matcherLog(l.matchers)) l.updaters, err = updates.NewManager(ctx, l.store, @@ -119,7 +117,7 @@ func New(ctx context.Context, opts *Options) (*Libvuln, error) { go l.updaters.Start(ctx) } - zlog.Info(ctx).Msg("libvuln initialized") + slog.InfoContext(ctx, "libvuln initialized") return l, nil } @@ -224,11 +222,12 @@ func (l *Libvuln) Initialized(ctx context.Context) (bool, error) { // generated documentation URL. type matcherLog []driver.Matcher -func (l matcherLog) MarshalZerologArray(a *zerolog.Array) { - for _, m := range l { +func (l matcherLog) LogValue() slog.Value { + as := make([]slog.Attr, len(l)) + for i, m := range l { t := reflect.ValueOf(m).Elem().Type() - a.Dict(zerolog.Dict(). - Str("name", m.Name()). - Str("docs", `https://pkg.go.dev/`+t.PkgPath())) + as[i] = slog.String(m.Name(), `https://pkg.go.dev/`+t.PkgPath()) } + + return slog.GroupValue(as...) } diff --git a/libvuln/libvuln_test.go b/libvuln/libvuln_test.go deleted file mode 100644 index a19cde3c9..000000000 --- a/libvuln/libvuln_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package libvuln - -import ( - "bytes" - "context" - "testing" - - "github.com/rs/zerolog" - - "github.com/quay/claircore" - "github.com/quay/claircore/libvuln/driver" -) - -func TestMatcherLog(t *testing.T) { - const want = `{"matchers":[{"name":"test-matcher","docs":"https://pkg.go.dev/github.com/quay/claircore/libvuln"}]}` + "\n" - var buf bytes.Buffer - log := zerolog.New(&buf) - log.Log().Array("matchers", matcherLog([]driver.Matcher{&TestMatcher{}})).Send() - - got := buf.String() - t.Logf("got: %+#q", got) - if got != want { - t.Errorf("want: %+#q", want) - } -} - -// Helper for above test -type TestMatcher struct{} - -var _ driver.Matcher = (*TestMatcher)(nil) - -func (*TestMatcher) Name() string { - return "test-matcher" -} - -func (*TestMatcher) Filter(*claircore.IndexRecord) bool { - return false -} - -func (*TestMatcher) Query() []driver.MatchConstraint { - return nil -} - -func (*TestMatcher) Vulnerable(context.Context, *claircore.IndexRecord, *claircore.Vulnerability) (bool, error) { - return false, nil -} diff --git a/libvuln/updates.go b/libvuln/updates.go index 57e5a0c3c..94c94533e 100644 --- a/libvuln/updates.go +++ b/libvuln/updates.go @@ -4,10 +4,10 @@ import ( "context" "fmt" "io" + "log/slog" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" - "github.com/quay/zlog" "github.com/quay/claircore/datastore/postgres" "github.com/quay/claircore/libvuln/driver" @@ -23,8 +23,6 @@ func OfflineImport(ctx context.Context, pool *pgxpool.Pool, in io.Reader) error // BUG(hank) The OfflineImport function is a wart, needed to work around // some package namespacing issues. It should get refactored if claircore // gets merged into clair. - ctx = zlog.ContextWithValues(ctx, "component", "libvuln/OfflineImporter") - s := postgres.NewMatcherStore(pool) l, err := jsonblob.Load(ctx, in) if err != nil { @@ -39,13 +37,12 @@ func OfflineImport(ctx context.Context, pool *pgxpool.Pool, in io.Reader) error Update: for l.Next() { e := l.Entry() + log := slog.With("updater", e.Updater) for _, op := range ops[e.Updater] { // This only helps if updaters don't keep something that // changes in the fingerprint. if op.Fingerprint == e.Fingerprint { - zlog.Info(ctx). - Str("updater", e.Updater). - Msg("fingerprint match, skipping") + log.InfoContext(ctx, "fingerprint match, skipping") continue Update } } @@ -60,12 +57,10 @@ Update: return fmt.Errorf("updating vulnerabilities: %w", err) } } - zlog.Info(ctx). - Str("updater", e.Updater). - Str("ref", ref.String()). - Int("vuln_count", len(e.Vuln)). - Int("enrichment_count", len(e.Enrichment)). - Msg("update imported") + log.InfoContext(ctx, "update imported", + "ref", ref, + "vuln_count", len(e.Vuln), + "enrichment_count", len(e.Enrichment)) } if err := l.Err(); err != nil { return err diff --git a/libvuln/updates/manager.go b/libvuln/updates/manager.go index e54ad2edc..770077180 100644 --- a/libvuln/updates/manager.go +++ b/libvuln/updates/manager.go @@ -5,13 +5,13 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "runtime" "strings" "time" "github.com/google/uuid" - "github.com/quay/zlog" "golang.org/x/sync/semaphore" "github.com/quay/claircore" @@ -62,8 +62,6 @@ type Manager struct { // NewManager will return a manager ready to have its Start or Run methods called. func NewManager(ctx context.Context, store datastore.Updater, locks LockSource, client *http.Client, opts ...ManagerOption) (*Manager, error) { - ctx = zlog.ContextWithValues(ctx, "component", "libvuln/updates/NewManager") - // the default Manager m := &Manager{ store: store, @@ -98,21 +96,19 @@ func NewManager(ctx context.Context, store datastore.Updater, locks LockSource, // // Start must only be called once between context cancellations. func (m *Manager) Start(ctx context.Context) error { - ctx = zlog.ContextWithValues(ctx, "component", "libvuln/updates/Manager.Start") - if m.interval == 0 { return fmt.Errorf("manager must be configured with an interval to start") } // perform the initial run - zlog.Info(ctx).Msg("starting initial updates") + slog.InfoContext(ctx, "starting initial updates") err := m.Run(ctx) if err != nil { - zlog.Error(ctx).Err(err).Msg("errors encountered during updater run") + slog.ErrorContext(ctx, "errors encountered during updater run", "reason", err) } // perform run on every tick - zlog.Info(ctx).Str("interval", m.interval.String()).Msg("starting background updates") + slog.InfoContext(ctx, "starting background updates", "interval", m.interval) t := time.NewTicker(m.interval) defer t.Stop() for { @@ -122,7 +118,7 @@ func (m *Manager) Start(ctx context.Context) error { case <-t.C: err := m.Run(ctx) if err != nil { - zlog.Error(ctx).Err(err).Msg("errors encountered during updater run") + slog.ErrorContext(ctx, "errors encountered during updater run", "reason", err) } } } @@ -134,8 +130,6 @@ func (m *Manager) Start(ctx context.Context) error { // Run is safe to call at anytime, regardless of whether background updaters // are running. func (m *Manager) Run(ctx context.Context) error { - ctx = zlog.ContextWithValues(ctx, "component", "libvuln/updates/Manager.Run") - updaters := []driver.Updater{} // Constructing updater sets may require network access // depending on the factory. @@ -145,23 +139,21 @@ func (m *Manager) Run(ctx context.Context) error { updateTime := time.Now() set, err := factory.UpdaterSet(ctx) if err != nil { - zlog.Error(ctx).Err(err).Msg("failed constructing factory, excluding from run") + slog.ErrorContext(ctx, "failed constructing factory, excluding from run", "reason", err) continue } if stubUpdaterInSet(set) { updaterSetName, err := getFactoryNameFromStubUpdater(set) if err != nil { - zlog.Error(ctx). - Err(err). - Msg("error getting updater set name") + slog.ErrorContext(ctx, "error getting updater set name", + "reason", err) } err = m.store.RecordUpdaterSetStatus(ctx, updaterSetName, updateTime) if err != nil { - zlog.Error(ctx). - Err(err). - Str("updaterSetName", updaterSetName). - Time("updateTime", updateTime). - Msg("error while recording update success for all updaters in updater set") + slog.ErrorContext(ctx, "error while recording update success for all updaters in updater set", + "updaterSetName", updaterSetName, + "updateTime", updateTime, + "reason", err) } continue } @@ -178,29 +170,25 @@ func (m *Manager) Run(ctx context.Context) error { cfg = noopConfig } if err := f.Configure(ctx, cfg, m.client); err != nil { - zlog.Warn(ctx). - Err(err). - Str("updater", name). - Msg("failed configuring updater, excluding from current run") + slog.WarnContext(ctx, "failed configuring updater, excluding from current run", + "updater", name, + "reason", err) continue } } toRun = append(toRun, u) } - zlog.Info(ctx). - Int("total", len(toRun)). - Int("batchSize", m.batchSize). - Msg("running updaters") + slog.InfoContext(ctx, "running updaters", + "total", len(toRun), + "batchSize", m.batchSize) sem := semaphore.NewWeighted(int64(m.batchSize)) errChan := make(chan error, len(toRun)+1) // +1 for a potential ctx error for i := range toRun { err := sem.Acquire(ctx, 1) if err != nil { - zlog.Error(ctx). - Err(err). - Msg("sem acquire failed, ending updater run") + slog.ErrorContext(ctx, "sem acquire failed, ending updater run", "reason", err) break } @@ -210,10 +198,9 @@ func (m *Manager) Run(ctx context.Context) error { ctx, done := m.locks.TryLock(ctx, u.Name()) defer done() if err := ctx.Err(); err != nil { - zlog.Debug(ctx). - Err(err). - Str("updater", u.Name()). - Msg("lock context canceled, excluding from run") + slog.DebugContext(ctx, "lock context canceled, excluding from run", + "reason", err, + "updater", u.Name()) return } @@ -232,19 +219,16 @@ func (m *Manager) Run(ctx context.Context) error { if m.updateRetention != 0 { ctx, done := m.locks.TryLock(ctx, "garbage-collection") if err := ctx.Err(); err != nil { - zlog.Debug(ctx). - Err(err). - Msg("lock context canceled, garbage collection already running") + slog.DebugContext(ctx, "lock context canceled, garbage collection already running", "reason", err) } else { - zlog.Info(ctx).Int("retention", m.updateRetention).Msg("GC started") + slog.InfoContext(ctx, "GC started", "retention", m.updateRetention) i, err := m.store.GC(ctx, m.updateRetention) if err != nil { - zlog.Error(ctx).Err(err).Msg("error while performing GC") + slog.ErrorContext(ctx, "error while performing GC", "reason", err) } else { - zlog.Info(ctx). - Int64("remaining_ops", i). - Int("retention", m.updateRetention). - Msg("GC completed") + slog.InfoContext(ctx, "GC completed", + "remaining_ops", i, + "retention", m.updateRetention) } } done() @@ -286,36 +270,30 @@ func getFactoryNameFromStubUpdater(set driver.UpdaterSet) (string, error) { func (m *Manager) driveUpdater(ctx context.Context, u driver.Updater) (err error) { var newFP driver.Fingerprint updateTime := time.Now() + name := u.Name() + log := slog.With("updater", name) defer func() { - deferErr := m.store.RecordUpdaterStatus(ctx, u.Name(), updateTime, newFP, err) + deferErr := m.store.RecordUpdaterStatus(ctx, name, updateTime, newFP, err) if deferErr != nil { - zlog.Error(ctx). - Err(deferErr). - Str("updater", u.Name()). - Time("updateTime", updateTime). - Msg("error while recording updater status") + log.ErrorContext(ctx, "error while recording updater status", + "reason", deferErr, + "updateTime", updateTime) } }() - name := u.Name() - ctx = zlog.ContextWithValues(ctx, - "component", "libvuln/updates/Manager.driveUpdater", - "updater", name) - zlog.Info(ctx).Msg("starting update") - defer zlog.Info(ctx).Msg("finished update") + log.InfoContext(ctx, "starting update") + defer log.InfoContext(ctx, "finished update") uoKind := driver.VulnerabilityKind // Do some assertions eu, euOK := u.(driver.EnrichmentUpdater) if euOK { - zlog.Info(ctx). - Msg("found EnrichmentUpdater") + log.InfoContext(ctx, "found EnrichmentUpdater") uoKind = driver.EnrichmentKind } du, duOK := u.(driver.DeltaUpdater) if duOK { - zlog.Info(ctx). - Msg("found DeltaUpdater") + slog.InfoContext(ctx, "found DeltaUpdater") } var prevFP driver.Fingerprint @@ -341,7 +319,7 @@ func (m *Manager) driveUpdater(ctx context.Context, u driver.Updater) (err error switch { case err == nil: case errors.Is(err, driver.Unchanged): - zlog.Info(ctx).Msg("vulnerability database unchanged") + log.InfoContext(ctx, "vulnerability database unchanged") err = nil return default: @@ -386,9 +364,7 @@ func (m *Manager) driveUpdater(ctx context.Context, u driver.Updater) (err error err = fmt.Errorf("failed to update: %v", err) return } - zlog.Info(ctx). - Str("ref", ref.String()). - Msg("successful update") + log.InfoContext(ctx, "successful update", "ref", ref) return nil } diff --git a/libvuln/updates/manager_test.go b/libvuln/updates/manager_test.go index 70e1aa5c5..502b30139 100644 --- a/libvuln/updates/manager_test.go +++ b/libvuln/updates/manager_test.go @@ -12,20 +12,20 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/jackc/pgx/v5/pgxpool" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/datastore/postgres" "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/pkg/ctxlock/v2" + "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" pgtest "github.com/quay/claircore/test/postgres" ) func TestDeltaUpdates(t *testing.T) { integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) facs := map[string]driver.UpdaterSetFactory{ "delta": &Factory{ vulnGetter: &vulnGetter{ @@ -81,8 +81,10 @@ func TestDeltaUpdates(t *testing.T) { } } -var _ driver.DeltaUpdater = (*testUpdater)(nil) -var _ driver.Updater = (*testUpdater)(nil) +var ( + _ driver.DeltaUpdater = (*testUpdater)(nil) + _ driver.Updater = (*testUpdater)(nil) +) type testUpdater struct { vulnGetter *vulnGetter diff --git a/linux/coalescer_test.go b/linux/coalescer_test.go index 4a9d84fce..fda4f45e9 100644 --- a/linux/coalescer_test.go +++ b/linux/coalescer_test.go @@ -1,19 +1,16 @@ package linux import ( - "context" "strconv" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/test" ) func TestCoalescer(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) coalescer := &Coalescer{ ir: &claircore.IndexReport{ Environments: map[string][]*claircore.Environment{}, diff --git a/matchers/matchers.go b/matchers/matchers.go index 14e9724d7..333c644e7 100644 --- a/matchers/matchers.go +++ b/matchers/matchers.go @@ -4,10 +4,9 @@ import ( "context" "errors" "fmt" + "log/slog" "net/http" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" _ "github.com/quay/claircore/matchers/defaults" "github.com/quay/claircore/matchers/registry" @@ -30,7 +29,6 @@ type MatchersOption func(m *Matchers) // NewMatchers will return a slice of Matcher created based on the provided // MatchersOption. func NewMatchers(ctx context.Context, client *http.Client, opts ...MatchersOption) ([]driver.Matcher, error) { - ctx = zlog.ContextWithValues(ctx, "component", "libvuln/matchers/NewMatchers") if client == nil { return nil, errors.New("invalid *http.Client") } @@ -57,7 +55,7 @@ func NewMatchers(ctx context.Context, client *http.Client, opts ...MatchersOptio for _, factory := range m.factories { matcher, err := factory.Matcher(ctx) if err != nil { - zlog.Error(ctx).Err(err).Msg("failed constructing factory, excluding from run") + slog.ErrorContext(ctx, "failed constructing factory, excluding from run", "reason", err) continue } matchers = append(matchers, matcher...) diff --git a/nodejs/coalescer_test.go b/nodejs/coalescer_test.go index dba365f03..d8a55a364 100644 --- a/nodejs/coalescer_test.go +++ b/nodejs/coalescer_test.go @@ -1,12 +1,9 @@ package nodejs import ( - "context" "strconv" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/test" @@ -14,7 +11,7 @@ import ( func TestCoalescer(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) coalescer := &coalescer{} pkgs := test.GenUniquePackages(6) repo := []*claircore.Repository{&Repository} diff --git a/nodejs/matcher_test.go b/nodejs/matcher_test.go index 329360d3a..0cd1bd753 100644 --- a/nodejs/matcher_test.go +++ b/nodejs/matcher_test.go @@ -1,15 +1,13 @@ package nodejs import ( - "context" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore/test" ) func TestMatcher(t *testing.T) { t.Parallel() - test.RunMatcherTests(zlog.Test(context.Background(), t), t, "testdata/matcher", new(Matcher)) + ctx := test.Logging(t) + test.RunMatcherTests(ctx, t, "testdata/matcher", new(Matcher)) } diff --git a/nodejs/packagescanner.go b/nodejs/packagescanner.go index 9c56be593..1e0e5af90 100644 --- a/nodejs/packagescanner.go +++ b/nodejs/packagescanner.go @@ -8,12 +8,12 @@ import ( "encoding/json" "fmt" "io/fs" + "log/slog" "path/filepath" "runtime/trace" "strings" "github.com/Masterminds/semver" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -67,12 +67,8 @@ type packageJSON struct { func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "nodejs/Scanner.Scan", - "version", s.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") if err := ctx.Err(); err != nil { return nil, err } @@ -99,9 +95,7 @@ func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircor for _, p := range pkgs { if set.Contains(p) { - zlog.Debug(ctx). - Str("path", p). - Msg("file path determined to be of RPM origin") + slog.DebugContext(ctx, "file path determined to be of RPM origin", "path", p) continue } @@ -128,17 +122,16 @@ func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircor if sv, err := semver.NewVersion(pkgJSON.Version); err == nil { pkg.NormalizedVersion = claircore.FromSemver(sv) } else { - zlog.Info(ctx). - Str("package", pkg.Name). - Str("version", pkg.Version). - Msg("invalid semantic version") + slog.InfoContext(ctx, "invalid semantic version", + "package", pkg.Name, + "version", pkg.Version) } ret = append(ret, pkg) } if len(invalidPkgs) > 0 { - zlog.Debug(ctx).Strs("paths", invalidPkgs).Msg("unable to decode package.json, skipping") + slog.DebugContext(ctx, "unable to decode package.json, skipping", "paths", invalidPkgs) } return ret, nil @@ -146,14 +139,7 @@ func (s *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircor func packages(ctx context.Context, sys fs.FS) (out []string, err error) { return out, fs.WalkDir(sys, ".", func(p string, d fs.DirEntry, err error) error { - ev := zlog.Debug(ctx). - Str("file", p) - var success bool - defer func() { - if !success { - ev.Discard().Send() - } - }() + attrs := []slog.Attr{slog.String("file", p)} switch { case err != nil: return err @@ -168,12 +154,11 @@ func packages(ctx context.Context, sys fs.FS) (out []string, err error) { // for more information. return nil case strings.HasSuffix(p, "/package.json"): - ev = ev.Str("kind", "package.json") + attrs = append(attrs, slog.String("kind", "package.json")) default: return nil } - ev.Msg("found package") - success = true + slog.LogAttrs(ctx, slog.LevelDebug, "found package", attrs...) out = append(out, p) return nil }) diff --git a/nodejs/packagescanner_test.go b/nodejs/packagescanner_test.go index d8aa08ae3..54818cf61 100644 --- a/nodejs/packagescanner_test.go +++ b/nodejs/packagescanner_test.go @@ -7,14 +7,13 @@ import ( "github.com/quay/claircore" "github.com/quay/claircore/nodejs" + "github.com/quay/claircore/test" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" ) func TestScanLocal(t *testing.T) { t.Parallel() - ctx := t.Context() table := []struct { name string @@ -52,7 +51,7 @@ func TestScanLocal(t *testing.T) { } defer file.Close() - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) scanner := &nodejs.Scanner{} var l claircore.Layer err = l.Init(ctx, &claircore.LayerDescription{ diff --git a/nodejs/purl.go b/nodejs/purl.go new file mode 100644 index 000000000..9d3ad9b63 --- /dev/null +++ b/nodejs/purl.go @@ -0,0 +1,46 @@ +package nodejs + +import ( + "context" + "fmt" + + "github.com/Masterminds/semver" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for Node.js packages. + PURLType = "npm" +) + +// GeneratePURL generates a Node.js PURL for a given [claircore.IndexRecord]. +// Example: pkg:npm/express@4.18.2 +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + return packageurl.PackageURL{ + Type: PURLType, + Name: ir.Package.Name, + Version: ir.Package.Version, + }, nil +} + +// ParsePURL parses a Node.js PURL into a list of [claircore.IndexRecord]s. +// The matcher needs the NormalizedVersion to be set. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + v, err := semver.NewVersion(purl.Version) + if err != nil { + return nil, fmt.Errorf("nodejs: unable to parse version: %w", err) + } + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Name, + Kind: claircore.BINARY, + Version: v.String(), + NormalizedVersion: claircore.FromSemver(v), + }, + Repository: &Repository, + }, + }, nil +} diff --git a/nodejs/purl_test.go b/nodejs/purl_test.go new file mode 100644 index 000000000..6a250e896 --- /dev/null +++ b/nodejs/purl_test.go @@ -0,0 +1,136 @@ +package nodejs + +import ( + "context" + "testing" + + "github.com/Masterminds/semver" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordNodeJS(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + wantErr bool + }{ + { + name: "express", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "express", + Version: "4.18.2", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + { + name: "lodash", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "lodash", + Version: "4.17.21", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + { + name: "bad-version", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "left-pad", + Version: "not-a-version", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Align expected NormalizedVersion with GeneratePURL/ParsePURL behaviour. + if v, err := semver.NewVersion(tc.ir.Package.Version); err == nil { + tc.ir.Package.NormalizedVersion = claircore.FromSemver(v) + } + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + + got, err := ParsePURL(ctx, p) + if tc.wantErr { + if err == nil { + t.Fatalf("expected an error, got nil") + } + return + } + if err != nil { + t.Fatalf("ParsePURL unexpected error: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "express", + Version: "4.18.2", + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Name: "express", + Version: "4.18.2", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Ensure NormalizedVersion is set since GeneratePURL uses it. + if v, err := semver.NewVersion(tc.ir.Package.Version); err == nil { + tc.ir.Package.NormalizedVersion = claircore.FromSemver(v) + } + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore fields not relevant to PURL round-trip for Node.js. + cmpopts.IgnoreFields(claircore.Package{}, "PackageDB", "Filepath"), +} diff --git a/oracle/distributionscanner.go b/oracle/distributionscanner.go index fc5446b6b..5c002e620 100644 --- a/oracle/distributionscanner.go +++ b/oracle/distributionscanner.go @@ -3,11 +3,10 @@ package oracle import ( "bytes" "context" + "log/slog" "regexp" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -83,15 +82,11 @@ func (*DistributionScanner) Kind() string { return scannerKind } // If the files are found but all regexp fail to match an empty slice is returned. func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "oracle/DistributionScanner.Scan", - "version", ds.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") files, err := l.Files(osReleasePath, issuePath) if err != nil { - zlog.Debug(ctx).Msg("didn't find an os-release or issues file") + slog.DebugContext(ctx, "didn't find an os-release or issues file") return nil, nil } for _, buff := range files { diff --git a/oracle/parser.go b/oracle/parser.go index 386be1b33..e1b4c2e06 100644 --- a/oracle/parser.go +++ b/oracle/parser.go @@ -5,9 +5,9 @@ import ( "encoding/xml" "fmt" "io" + "log/slog" "github.com/quay/goval-parser/oval" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/internal/xmlutil" @@ -36,8 +36,7 @@ var platformToDist = map[string]*claircore.Distribution{ var _ driver.Parser = (*Updater)(nil) func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "oracle/Updater.Parse") - zlog.Info(ctx).Msg("starting parse") + slog.InfoContext(ctx, "starting parse") defer r.Close() root := oval.Root{} dec := xml.NewDecoder(r) @@ -45,7 +44,7 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln if err := dec.Decode(&root); err != nil { return nil, fmt.Errorf("oracle: unable to decode OVAL document: %w", err) } - zlog.Debug(ctx).Msg("xml decoded") + slog.DebugContext(ctx, "xml decoded") protoVulns := func(def oval.Definition) ([]*claircore.Vulnerability, error) { // In all oracle databases tested a single // and correct platform string can be found inside a definition @@ -87,10 +86,9 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln if err != nil { // Found a CPE but could not parse it. Log a warning and return // successfully. - zlog.Warn(ctx). - Str("def_title", def.Title). - Str("cpe", affected). - Msg("could not parse CPE: there may be a false positive match with a userspace_ksplice package") + slog.WarnContext(ctx, "could not parse CPE: there may be a false positive match with a userspace_ksplice package", + "def_title", def.Title, + "cpe", affected) return vs, nil } if wfn.Attr[cpe.Edition].V == "userspace_ksplice" { @@ -102,16 +100,14 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln case kspliceCPEs == 0: // Continue if there are no ksplice CPEs. case cpes == 0: - zlog.Warn(ctx). - Str("def_title", def.Title). - Msg("potential false positives: couldn't find CPEs to check for ksplice packages") + slog.WarnContext(ctx, "potential false positives: couldn't find CPEs to check for ksplice packages", + "def_title", def.Title) case diff == 0: - zlog.Debug(ctx).Msg("skipping userspace_ksplice vulnerabilities") + slog.DebugContext(ctx, "skipping userspace_ksplice vulnerabilities") return nil, nil case diff > 0: - zlog.Warn(ctx). - Str("def_title", def.Title). - Msg("potential false positives: OVAL may have a userspace_ksplice CPE which could not be skipped") + slog.WarnContext(ctx, "potential false positives: OVAL may have a userspace_ksplice CPE which could not be skipped", + "def_title", def.Title) default: panic("programmer error") } diff --git a/oracle/parser_test.go b/oracle/parser_test.go index b1d5a3e05..34ef0e756 100644 --- a/oracle/parser_test.go +++ b/oracle/parser_test.go @@ -1,16 +1,15 @@ package oracle import ( - "context" "os" "testing" - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) func TestParse(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) u, err := NewUpdater(-1) if err != nil { t.Fatal(err) diff --git a/oracle/purl.go b/oracle/purl.go new file mode 100644 index 000000000..24e3c1336 --- /dev/null +++ b/oracle/purl.go @@ -0,0 +1,60 @@ +package oracle + +import ( + "context" + "fmt" + "strings" + + "github.com/package-url/packageurl-go" + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for RPM packages. + PURLType = "rpm" + // PURLNamespace is the namespace of Oracle RPMs. + PURLNamespace = "oracle" +) + +// GeneratePURL generates an RPM PURL for a given [claircore.IndexRecord]. +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + p := packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: ir.Package.Name, + Version: ir.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": ir.Package.Arch, + }), + } + if ir.Distribution != nil { + if ir.Distribution.DID != "" && ir.Distribution.VersionID != "" { + p.Qualifiers = append(p.Qualifiers, packageurl.Qualifier{ + Key: "distro", + Value: "oracle-" + ir.Distribution.VersionID, + }) + } + } + return p, nil +} + +// ParsePURL parses an RPM PURL into a list of [claircore.IndexRecord]s. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + ir := &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Arch: purl.Qualifiers.Map()["arch"], + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{}, + } + distroQualifier := purl.Qualifiers.Map()["distro"] + distroParts := strings.SplitN(distroQualifier, "-", 2) + if len(distroParts) != 2 { + return nil, fmt.Errorf("invalid distro PURL: %s", distroQualifier) + } + release := Release(distroParts[1]) + ir.Distribution = releaseToDist(release) + return []*claircore.IndexRecord{ir}, nil +} diff --git a/oracle/purl_test.go b/oracle/purl_test.go new file mode 100644 index 000000000..bbe2735f6 --- /dev/null +++ b/oracle/purl_test.go @@ -0,0 +1,130 @@ +package oracle + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordOracle(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "ol9", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1.8-6.el9", + Arch: "x86_64", + Kind: claircore.BINARY, + }, + Distribution: nineDist, + }, + }, + { + name: "ol7", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "coreutils", + Version: "8.22-24.oe1", + Arch: "aarch64", + Kind: claircore.BINARY, + }, + Distribution: sevenDist, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParsePURL: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "basic-ol9", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1.8-6.el9", + Arch: "x86_64", + }, + Distribution: releaseToDist(Nine), + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: "bash", + Version: "5.1.8-6.el9", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "x86_64"}, + {Key: "distro", Value: "oracle-9"}, + }, + }, + }, + { + name: "basic-ol7", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "coreutils", + Version: "8.22-24.oe1", + Arch: "aarch64", + }, + Distribution: releaseToDist(Seven), + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: "coreutils", + Version: "8.22-24.oe1", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "aarch64"}, + {Key: "distro", Value: "oracle-7"}, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} diff --git a/oracle/updater_test.go b/oracle/updater_test.go index d6cccf304..62a29bc8c 100644 --- a/oracle/updater_test.go +++ b/oracle/updater_test.go @@ -1,18 +1,16 @@ package oracle import ( - "context" "net/http" "net/http/httptest" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" ) func TestFetch(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "testdata/com.oracle.elsa-2018.xml") })) diff --git a/osrelease/scanner.go b/osrelease/scanner.go index c3116511d..041d51752 100644 --- a/osrelease/scanner.go +++ b/osrelease/scanner.go @@ -7,12 +7,12 @@ import ( "context" "fmt" "io" + "log/slog" "runtime/trace" "sort" "strings" "github.com/quay/claircore/toolkit/types/cpe" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -57,12 +57,8 @@ func (*Scanner) Kind() string { return scannerKind } // present in the layer. func (s *Scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "osrelease/Scanner.Scan", - "version", s.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { @@ -75,10 +71,7 @@ func (s *Scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Di for _, n := range []string{Path, FallbackPath} { f, err := sys.Open(n) if err != nil { - zlog.Debug(ctx). - Str("name", n). - Err(err). - Msg("unable to open file") + slog.DebugContext(ctx, "unable to open file", "name", n, "reason", err) continue } defer f.Close() @@ -86,7 +79,7 @@ func (s *Scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Di break } if rd == nil { - zlog.Debug(ctx).Msg("didn't find an os-release file") + slog.DebugContext(ctx, "didn't find an os-release file") return nil, nil } d, err := toDist(ctx, rd) @@ -98,8 +91,6 @@ func (s *Scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Di // ToDist returns the distribution information from the file contents provided on r. func toDist(ctx context.Context, r io.Reader) (*claircore.Distribution, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "osrelease/parse") defer trace.StartRegion(ctx, "parse").End() m, err := Parse(ctx, r) if err != nil { @@ -115,48 +106,41 @@ func toDist(ctx context.Context, r io.Reader) (*claircore.Distribution, error) { } sort.Strings(ks) for _, key := range ks { + slog.DebugContext(ctx, "found key", "key", key) value := m[key] switch key { case "ID": - zlog.Debug(ctx).Msg("found ID") d.DID = value case "VERSION_ID": - zlog.Debug(ctx).Msg("found VERSION_ID") d.VersionID = value case "BUILD_ID": case "VARIANT_ID": case "CPE_NAME": - zlog.Debug(ctx).Msg("found CPE_NAME") wfn, err := cpe.Unbind(value) if err != nil { - zlog.Warn(ctx). - Err(err). - Str("value", value). - Msg("failed to unbind the cpe") + slog.WarnContext(ctx, "failed to unbind the cpe", "reason", err, "value", value) break } d.CPE = wfn case "NAME": - zlog.Debug(ctx).Msg("found NAME") d.Name = value case "VERSION": - zlog.Debug(ctx).Msg("found VERSION") d.Version = value case "ID_LIKE": case "VERSION_CODENAME": - zlog.Debug(ctx).Msg("found VERISON_CODENAME") d.VersionCodeName = value case "PRETTY_NAME": - zlog.Debug(ctx).Msg("found PRETTY_NAME") d.PrettyName = value case "REDHAT_BUGZILLA_PRODUCT": - zlog.Debug(ctx).Msg("using RHEL hack") + slog.DebugContext(ctx, "using RHEL hack") // This is a dirty hack because the Red Hat OVAL database and the // CPE contained in the os-release file don't agree. d.PrettyName = value + default: + continue } } - zlog.Debug(ctx).Str("name", d.Name).Msg("found dist") + slog.DebugContext(ctx, "found dist", "name", d.Name) return &d, nil } @@ -165,7 +149,6 @@ func toDist(ctx context.Context, r io.Reader) (*claircore.Distribution, error) { // // See comments in the source for edge cases. func Parse(ctx context.Context, r io.Reader) (map[string]string, error) { - ctx = zlog.ContextWithValues(ctx, "component", "osrelease/Parse") defer trace.StartRegion(ctx, "Parse").End() m := make(map[string]string) s := bufio.NewScanner(r) diff --git a/osrelease/scanner_test.go b/osrelease/scanner_test.go index 91e01a18d..5b1c26a8c 100644 --- a/osrelease/scanner_test.go +++ b/osrelease/scanner_test.go @@ -2,14 +2,12 @@ package osrelease import ( "bytes" - "context" "encoding/json" "path/filepath" "strings" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "golang.org/x/tools/txtar" "github.com/quay/claircore" @@ -18,14 +16,13 @@ import ( func TestParse(t *testing.T) { t.Parallel() - ctx := context.Background() ms, _ := filepath.Glob("testdata/*.txtar") for i := range ms { m := ms[i] name := strings.TrimSuffix(filepath.Base(m), ".txtar") t.Run(name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) ar, err := txtar.ParseFile(m) if err != nil { t.Fatal(err) @@ -99,7 +96,7 @@ type layerspec struct { func (lc layercase) Test(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) l := test.RealizeLayer(ctx, t, lc.Layer) var s Scanner diff --git a/photon/distributionscanner.go b/photon/distributionscanner.go index 3d2bb0d27..af0116611 100644 --- a/photon/distributionscanner.go +++ b/photon/distributionscanner.go @@ -3,11 +3,10 @@ package photon import ( "bytes" "context" + "log/slog" "regexp" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -47,6 +46,16 @@ var photonRegexes = []photonRegex{ // regex for /etc/os-release regexp: regexp.MustCompile(`^.*"VMware Photon OS"\sVERSION="3.0"`), }, + { + release: Photon4, + // regex for /etc/os-release + regexp: regexp.MustCompile(`^.*"VMware Photon OS"\sVERSION="4.0"`), + }, + { + release: Photon5, + // regex for /etc/os-release + regexp: regexp.MustCompile(`^.*"VMware Photon OS"\sVERSION="5.0"`), + }, } var ( @@ -74,15 +83,11 @@ func (*DistributionScanner) Kind() string { return scannerKind } // If the files are found but all regexp fail to match an empty slice is returned. func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "photon/DistributionScanner.Scan", - "version", ds.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") files, err := l.Files(osReleasePath, photonReleasePath) if err != nil { - zlog.Debug(ctx).Msg("didn't find an os-release or photon-release") + slog.DebugContext(ctx, "didn't find an os-release or photon-release") return nil, nil } for _, buff := range files { diff --git a/photon/distributionscanner_test.go b/photon/distributionscanner_test.go index 6d2fc9d67..1598439da 100644 --- a/photon/distributionscanner_test.go +++ b/photon/distributionscanner_test.go @@ -34,6 +34,24 @@ ANSI_COLOR="1;34" HOME_URL="https://vmware.github.io/photon/" BUG_REPORT_URL="https://github.com/vmware/photon/issues"`) +var photon4OSRelease []byte = []byte(`NAME="VMware Photon OS" +VERSION="4.0" +ID=photon +VERSION_ID="4.0" +PRETTY_NAME="VMware Photon OS/Linux" +ANSI_COLOR="1;34" +HOME_URL="https://vmware.github.io/photon/" +BUG_REPORT_URL="https://github.com/vmware/photon/issues"`) + +var photon5OSRelease []byte = []byte(`NAME="VMware Photon OS" +VERSION="5.0" +ID=photon +VERSION_ID="5.0" +PRETTY_NAME="VMware Photon OS/Linux" +ANSI_COLOR="1;34" +HOME_URL="https://vmware.github.io/photon/" +BUG_REPORT_URL="https://github.com/vmware/photon/issues"`) + func TestDistributionScanner(t *testing.T) { table := []struct { name string @@ -55,6 +73,16 @@ func TestDistributionScanner(t *testing.T) { release: Photon3, osRelease: photon3OSRelease, }, + { + name: "photon 4.0", + release: Photon4, + osRelease: photon4OSRelease, + }, + { + name: "photon 5.0", + release: Photon5, + osRelease: photon5OSRelease, + }, } for _, tt := range table { t.Run(tt.name, func(t *testing.T) { diff --git a/photon/parser.go b/photon/parser.go index 7467b1462..47018fb48 100644 --- a/photon/parser.go +++ b/photon/parser.go @@ -5,9 +5,9 @@ import ( "encoding/xml" "fmt" "io" + "log/slog" "github.com/quay/goval-parser/oval" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/internal/xmlutil" @@ -18,8 +18,7 @@ import ( var _ driver.Parser = (*Updater)(nil) func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "photon/Updater.Parse") - zlog.Info(ctx).Msg("starting parse") + slog.InfoContext(ctx, "starting parse") defer r.Close() root := oval.Root{} dec := xml.NewDecoder(r) @@ -27,11 +26,11 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln if err := dec.Decode(&root); err != nil { return nil, fmt.Errorf("photon: unable to decode OVAL document: %w", err) } - zlog.Debug(ctx).Msg("xml decoded") + slog.DebugContext(ctx, "xml decoded") protoVulns := func(def oval.Definition) ([]*claircore.Vulnerability, error) { return []*claircore.Vulnerability{ - &claircore.Vulnerability{ + { Updater: u.Name(), Name: def.Title, Description: def.Description, diff --git a/photon/purl.go b/photon/purl.go new file mode 100644 index 000000000..b8ecd5192 --- /dev/null +++ b/photon/purl.go @@ -0,0 +1,59 @@ +package photon + +import ( + "context" + "fmt" + "strings" + + "github.com/package-url/packageurl-go" + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for RPM packages. + PURLType = "rpm" + // PURLNamespace is the namespace of photon RPMs. + PURLNamespace = "photon" +) + +// GeneratePURL generates an RPM PURL for a given [claircore.IndexRecord]. +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + p := packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: ir.Package.Name, + Version: ir.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": ir.Package.Arch, + }), + } + if ir.Distribution != nil { + if ir.Distribution.DID != "" && ir.Distribution.VersionID != "" { + p.Qualifiers = append(p.Qualifiers, packageurl.Qualifier{ + Key: "distro", + Value: "photon-" + ir.Distribution.VersionID, + }) + } + } + return p, nil +} + +// ParsePURL parses an RPM PURL into a list of [claircore.IndexRecord]s. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + ir := &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Arch: purl.Qualifiers.Map()["arch"], + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{}, + } + distroQualifier := purl.Qualifiers.Map()["distro"] + distroParts := strings.SplitN(distroQualifier, "-", 2) + if len(distroParts) != 2 { + return nil, fmt.Errorf("invalid distro PURL: %s", distroQualifier) + } + ir.Distribution = releaseToDist(Release(distroParts[1])) + return []*claircore.IndexRecord{ir}, nil +} diff --git a/photon/purl_test.go b/photon/purl_test.go new file mode 100644 index 000000000..a193c4111 --- /dev/null +++ b/photon/purl_test.go @@ -0,0 +1,130 @@ +package photon + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordPhoton(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "photon 1.0", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1.8-6.el9", + Arch: "x86_64", + Kind: claircore.BINARY, + }, + Distribution: photon1Dist, + }, + }, + { + name: "photon 2.0", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "coreutils", + Version: "8.22-24.oe1", + Arch: "aarch64", + Kind: claircore.BINARY, + }, + Distribution: photon2Dist, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParsePURL: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "basic-ol9", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1.8-6.el9", + Arch: "x86_64", + }, + Distribution: photon1Dist, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: "bash", + Version: "5.1.8-6.el9", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "x86_64"}, + {Key: "distro", Value: "photon-1.0"}, + }, + }, + }, + { + name: "basic-ol7", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "coreutils", + Version: "8.22-24.oe1", + Arch: "aarch64", + }, + Distribution: photon2Dist, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: "coreutils", + Version: "8.22-24.oe1", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "aarch64"}, + {Key: "distro", Value: "photon-2.0"}, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} diff --git a/photon/releases.go b/photon/releases.go index 8468aa6c5..51bebf8a8 100644 --- a/photon/releases.go +++ b/photon/releases.go @@ -7,9 +7,11 @@ type Release string // These are some known Releases. const ( - Photon1 Release = `photon1` - Photon2 Release = `photon2` - Photon3 Release = `photon3` + Photon1 Release = `1.0` + Photon2 Release = `2.0` + Photon3 Release = `3.0` + Photon4 Release = `4.0` + Photon5 Release = `5.0` ) var photon1Dist = &claircore.Distribution{ @@ -36,6 +38,22 @@ var photon3Dist = &claircore.Distribution{ DID: "photon", } +var photon4Dist = &claircore.Distribution{ + Name: "VMware Photon OS", + Version: "4.0", + VersionID: "4.0", + PrettyName: "VMware Photon OS/Linux", + DID: "photon", +} + +var photon5Dist = &claircore.Distribution{ + Name: "VMware Photon OS", + Version: "5.0", + VersionID: "5.0", + PrettyName: "VMware Photon OS/Linux", + DID: "photon", +} + func releaseToDist(r Release) *claircore.Distribution { switch r { case Photon1: @@ -44,6 +62,10 @@ func releaseToDist(r Release) *claircore.Distribution { return photon2Dist case Photon3: return photon3Dist + case Photon4: + return photon4Dist + case Photon5: + return photon5Dist default: // return empty dist return &claircore.Distribution{} diff --git a/pkg/ctxlock/v2/ctxlock.go b/pkg/ctxlock/v2/ctxlock.go index 503d6116a..3b56b494e 100644 --- a/pkg/ctxlock/v2/ctxlock.go +++ b/pkg/ctxlock/v2/ctxlock.go @@ -8,16 +8,15 @@ import ( "context" "errors" "fmt" + "log/slog" "runtime" "runtime/pprof" "runtime/trace" - "strconv" "sync" "time" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" - "github.com/quay/zlog" "github.com/quay/claircore/internal/dblock" ) @@ -93,26 +92,20 @@ var ( func (l *Locker) run(ctx context.Context) { ctx = pprof.WithLabels(ctx, pprof.Labels(tracelabel, `run`)) pprof.SetGoroutineLabels(ctx) - ctx = zlog.ContextWithValues(ctx, "component", "internal/ctxlock/Locker.run") for { tctx, done := context.WithTimeout(ctx, 5*time.Second) err := l.p.AcquireFunc(tctx, l.reconnect(ctx)) done() switch { case errors.Is(err, errExiting): - zlog.Debug(ctx). - Msg("ctxlocker exiting") + slog.DebugContext(ctx, "ctxlocker exiting") return case errors.Is(err, nil): return case errors.Is(err, context.DeadlineExceeded): - zlog.Info(ctx). - Err(err). - Msg("retrying immediately") + slog.InfoContext(ctx, "retrying immediately", "reason", err) default: - zlog.Warn(ctx). - Err(err). - Msg("unexpected error; retrying immediately") + slog.WarnContext(ctx, "unexpected error; retrying immediately", "reason", err) } } } @@ -133,7 +126,6 @@ func (l *Locker) Close(_ context.Context) (_ error) { // itself until awoken. All other methods should strobe the Cond to wake up this // loop and check if the connection has died. func (l *Locker) reconnect(ctx context.Context) func(*pgxpool.Conn) error { - ctx = zlog.ContextWithValues(ctx, "component", "internal/ctxlock/Locker.reconnect") return func(c *pgxpool.Conn) error { l.rc.L.Lock() defer l.rc.L.Unlock() @@ -141,15 +133,15 @@ func (l *Locker) reconnect(ctx context.Context) func(*pgxpool.Conn) error { l.gone = make(chan struct{}) l.cur = make(map[string]struct{}, 100) // Guess at a good capacity. l.gen++ - ctx = zlog.ContextWithValues(ctx, "gen", strconv.Itoa(l.gen)) + log := slog.With(slog.Int("gen", l.gen)) defer func() { close(l.gone) l.gone = nil l.conn = nil l.cur = nil - zlog.Debug(ctx).Msg("torn down") + log.DebugContext(ctx, "torn down") }() - zlog.Debug(ctx).Msg("set up") + log.DebugContext(ctx, "set up") l.rc.Broadcast() for l.gen > 0 { @@ -157,9 +149,7 @@ func (l *Locker) reconnect(ctx context.Context) func(*pgxpool.Conn) error { err := c.Ping(ctx) done() if err != nil { - zlog.Warn(ctx). - Err(err). - Msg("liveness check failed") + log.WarnContext(ctx, "liveness check failed", "reason", err) return err } l.rc.Wait() @@ -183,15 +173,6 @@ func (l *Locker) ping(ctx context.Context) { } } -/* -The TryLock and Lock methods do not add logging baggage, because the additional -allocations around the context.Context really stack up. Ensure that any -additional information is added to the zlog calls directly. - -I've left some Debug logs commented out because zlog needs to gain a level knob -for tests. Currently, the logs always happen and throw off benchmarks. -*/ - // TryLock attempts to lock on the provided key. // // If unsuccessful, an already-canceled Context will be returned. @@ -199,7 +180,8 @@ for tests. Currently, the logs always happen and throw off benchmarks. // If successful, the returned Context will be parented to the passed-in Context // and also to the underlying connection used for the lock. func (l *Locker) TryLock(parent context.Context, key string) (context.Context, context.CancelFunc) { - // zlog.Debug(parent).Str("key", key).Msg("trying lock") + a := slog.String("key", key) + slog.DebugContext(parent, "trying lock", a) defer trace.StartRegion(parent, pkgname+".TryLock").End() child, done := context.WithCancel(parent) w, err := l.try(parent, key, done) @@ -209,14 +191,9 @@ func (l *Locker) TryLock(parent context.Context, key string) (context.Context, c case errors.Is(err, errConnGone) || errors.Is(err, errLockFail) || errors.Is(err, errDoubleLock): - zlog.Debug(parent). - Err(err). - Str("key", key). - Msg("lock failed") + slog.DebugContext(parent, "lock failed", a, "reason", err) default: - zlog.Info(parent). - Err(err). - Msg("checking lock liveness") + slog.InfoContext(parent, "checking lock liveness", a, "reason", err) l.rc.Broadcast() } done() @@ -226,7 +203,8 @@ func (l *Locker) TryLock(parent context.Context, key string) (context.Context, c // Lock attempts to obtain the named lock until it succeeds or the passed // Context is canceled. func (l *Locker) Lock(parent context.Context, key string) (context.Context, context.CancelFunc) { - // zlog.Debug(parent).Str("key", key).Msg("locking") + a := slog.String("key", key) + slog.DebugContext(parent, "locking", a) defer trace.StartRegion(parent, pkgname+".Lock").End() child, done := context.WithCancel(parent) for wait := time.Duration(500 * time.Millisecond); ; backoff(&wait) { @@ -237,14 +215,9 @@ func (l *Locker) Lock(parent context.Context, key string) (context.Context, cont case errors.Is(err, errConnGone) || errors.Is(err, errLockFail) || errors.Is(err, errDoubleLock): - zlog.Debug(parent). - Err(err). - Str("key", key). - Msg("lock failed") + slog.DebugContext(parent, "lock failed", a, "reason", err) default: - zlog.Info(parent). - Err(err). - Msg("checking lock liveness") + slog.InfoContext(parent, "checking lock liveness", a, "reason", err) l.rc.Broadcast() } @@ -343,18 +316,14 @@ func (l *Locker) unlock(ctx context.Context, key string, kb []byte, gen int, nex [][]byte{kb}, nil, []int16{1}, nil).Close() if err != nil { - zlog.Debug(ctx). - Err(err). - Msg("error during unlock") + slog.DebugContext(ctx, "error during unlock", "reason", err) // Since we're in a different call path now, we need to signal on // error here, as well. l.rc.Broadcast() return } if _, ok := l.cur[key]; !ok || tag.RowsAffected() == 0 { - zlog.Error(ctx). - Str("key", key). - Msg("lock protocol botch") + slog.ErrorContext(ctx, "lock protocol botch", "key", key) } delete(l.cur, key) } diff --git a/pkg/ctxlock/v2/ctxlock_test.go b/pkg/ctxlock/v2/ctxlock_test.go index 32ce37c03..6e1e4d8c8 100644 --- a/pkg/ctxlock/v2/ctxlock_test.go +++ b/pkg/ctxlock/v2/ctxlock_test.go @@ -1,12 +1,10 @@ package ctxlock import ( - "strconv" "sync" "testing" "github.com/google/uuid" - "github.com/quay/zlog" ) func TestUncontested(t *testing.T) { @@ -32,7 +30,6 @@ func TestUncontested(t *testing.T) { for i := range wi { go func(i int) { defer wg.Done() - ctx := zlog.ContextWithValues(ctx, "worker", strconv.Itoa(i)) <-start t.Logf("worker %d: start", i) for _, id := range wi[i] { @@ -68,7 +65,6 @@ func TestContested(t *testing.T) { for i := range w { go func(i int) { defer wg.Done() - ctx := zlog.ContextWithValues(ctx, "worker", strconv.Itoa(i)) <-start t.Logf("worker %d: start", i) for _, id := range ids { diff --git a/pkg/ctxlock/v2/main_test.go b/pkg/ctxlock/v2/main_test.go index a8f476deb..f0e846306 100644 --- a/pkg/ctxlock/v2/main_test.go +++ b/pkg/ctxlock/v2/main_test.go @@ -7,15 +7,15 @@ import ( "github.com/jackc/pgx/v5/log/testingadapter" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/tracelog" - "github.com/quay/zlog" + "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" ) func basicSetup(t testing.TB) (context.Context, *Locker) { t.Helper() integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) // Setup the Database. db, err := integration.NewDB(ctx, t) diff --git a/pkg/ovalutil/dpkg.go b/pkg/ovalutil/dpkg.go index 8475d34ec..1434066de 100644 --- a/pkg/ovalutil/dpkg.go +++ b/pkg/ovalutil/dpkg.go @@ -4,12 +4,11 @@ import ( "context" "errors" "fmt" + "log/slog" "regexp" "strings" "github.com/quay/goval-parser/oval" - "github.com/quay/zlog" - "github.com/rs/zerolog" "github.com/quay/claircore" ) @@ -23,7 +22,6 @@ type PackageExpansionFunc func(def oval.Definition, name *oval.DpkgName) []strin // // Each Criterion encountered with an EVR string will be translated into a claircore.Vulnerability func DpkgDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsFunc, expansionFunc PackageExpansionFunc) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "ovalutil/DpkgDefsToVulns") vulns := make([]*claircore.Vulnerability, 0, 10000) pkgcache := map[string]*claircore.Package{} cris := []*oval.Criterion{} @@ -36,10 +34,7 @@ func DpkgDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulns // create our prototype vulnerability protoVulns, err := protoVulns(def) if err != nil { - zlog.Debug(ctx). - Err(err). - Str("def_id", def.ID). - Msg("could not create prototype vulnerabilities") + slog.DebugContext(ctx, "could not create prototype vulnerabilities", "reason", err, "def_id", def.ID) continue } // recursively collect criterions for this definition @@ -131,19 +126,16 @@ func DpkgDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulns } } } - zlog.Debug(ctx). - Int("test", stats.Test). - Int("object", stats.Obj). - Int("state", stats.State). - Msg("ref lookup failures") - if ev := zlog.Debug(ctx); ev.Enabled() { - d := zerolog.Dict() + slog.DebugContext(ctx, "ref lookup failures", + "test", stats.Test, + "object", stats.Obj, + "state", stats.State) + if slog.Default().Enabled(ctx, slog.LevelDebug) { + var bad []slog.Attr for k, v := range badvers { - d.Str(k, v) + bad = append(bad, slog.String(k, v)) } - ev. - Dict("package-version", d). - Msg("bogus versions") + slog.DebugContext(ctx, "bogus versions", "package-version", slog.GroupValue(bad...)) } return vulns, nil } diff --git a/pkg/ovalutil/fetcher.go b/pkg/ovalutil/fetcher.go index d0a0af646..49cdf95d6 100644 --- a/pkg/ovalutil/fetcher.go +++ b/pkg/ovalutil/fetcher.go @@ -6,13 +6,12 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "mime" "net/http" "net/url" "path" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/pkg/tmp" ) @@ -66,7 +65,6 @@ type Fetcher struct { // For users that embed a Fetcher, this provides a configuration hook by // default. func (f *Fetcher) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, "component", "pkg/ovalutil/Fetcher.Configure") var cfg FetcherConfig if err := cf(&cfg); err != nil { return err @@ -77,8 +75,7 @@ func (f *Fetcher) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c return err } f.URL = uri - zlog.Info(ctx). - Msg("configured database URL") + slog.InfoContext(ctx, "configured database URL") } if cfg.Compression != "" { c, err := ParseCompressor(cfg.Compression) @@ -86,8 +83,7 @@ func (f *Fetcher) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c return err } f.Compression = c - zlog.Info(ctx). - Msg("configured database compression") + slog.InfoContext(ctx, "configured database compression") } f.Client = c @@ -112,8 +108,7 @@ type FetcherConfig struct { // // Tmp.File is used to return a ReadCloser that outlives the passed-in context. func (f *Fetcher) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "pkg/ovalutil/Fetcher.Fetch") - zlog.Info(ctx).Str("database", f.URL.String()).Msg("starting fetch") + slog.InfoContext(ctx, "starting fetch", "database", f.URL) req := http.Request{ Method: http.MethodGet, Header: http.Header{ @@ -150,7 +145,7 @@ func (f *Fetcher) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCl default: return nil, hint, fmt.Errorf("ovalutil: fetcher got unexpected HTTP response: %d (%s)", res.StatusCode, res.Status) } - zlog.Debug(ctx).Msg("request ok") + slog.DebugContext(ctx, "request ok") var r io.Reader cmp := f.Compression @@ -186,11 +181,10 @@ Compression: default: panic("unreachable") } - zlog.Debug(ctx). - Err(err). - Str("header", h). - Str("value", v). - Msg("failed to parse incoming HTTP header") + slog.DebugContext(ctx, "failed to parse incoming HTTP header", + "reason", err, + "header", h, + "value", v) } kind = mime.TypeByExtension(path.Ext(res.Request.URL.Path)) Found: @@ -227,23 +221,19 @@ Compression: default: panic(fmt.Sprintf("ovalutil: programmer error: unknown compression scheme: %v", f.Compression)) } - zlog.Debug(ctx). - Stringer("compression", cmp). - Msg("found compression scheme") + slog.DebugContext(ctx, "found compression scheme", "compression", cmp) tf, err := tmp.NewFile("", "fetcher.") if err != nil { return nil, hint, err } - zlog.Debug(ctx). - Str("path", tf.Name()). - Msg("using tempfile") + slog.DebugContext(ctx, "using tempfile", "path", tf.Name()) success := false defer func() { if !success { - zlog.Debug(ctx).Msg("unsuccessful, cleaning up tempfile") + slog.DebugContext(ctx, "unsuccessful, cleaning up tempfile") if err := tf.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("failed to close tempfile") + slog.WarnContext(ctx, "failed to close tempfile", "reason", err) } } }() @@ -254,7 +244,7 @@ Compression: if o, err := tf.Seek(0, io.SeekStart); err != nil || o != 0 { return nil, hint, err } - zlog.Debug(ctx).Msg("decompressed and buffered database") + slog.DebugContext(ctx, "decompressed and buffered database") fp.From(res.Header) hint = fp.Fingerprint() diff --git a/pkg/ovalutil/rpm.go b/pkg/ovalutil/rpm.go index f3cd9430b..ce6cc27f2 100644 --- a/pkg/ovalutil/rpm.go +++ b/pkg/ovalutil/rpm.go @@ -4,10 +4,10 @@ import ( "context" "errors" "fmt" + "log/slog" "regexp" "github.com/quay/goval-parser/oval" - "github.com/quay/zlog" "github.com/quay/claircore" ) @@ -41,17 +41,15 @@ type ProtoVulnsFunc func(def oval.Definition) ([]*claircore.Vulnerability, error // // Each Criterion encountered with an EVR string will be translated into a claircore.Vulnerability func RPMDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsFunc) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "ovalutil/RPMDefsToVulns") vulns := make([]*claircore.Vulnerability, 0, 10000) cris := []*oval.Criterion{} for _, def := range root.Definitions.Definitions { // create our prototype vulnerability protoVulns, err := protoVulns(def) if err != nil { - zlog.Debug(ctx). - Err(err). - Str("def_id", def.ID). - Msg("could not create prototype vulnerabilities") + slog.DebugContext(ctx, "could not create prototype vulnerabilities", + "reason", err, + "def_id", def.ID) continue } // recursively collect criterions for this definition @@ -77,7 +75,7 @@ func RPMDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsF case errors.Is(err, errTestSkip): continue default: - zlog.Debug(ctx).Str("test_ref", criterion.TestRef).Msg("test ref lookup failure. moving to next criterion") + slog.DebugContext(ctx, "test ref lookup failure, moving to next criterion", "test_ref", criterion.TestRef) continue } @@ -98,10 +96,9 @@ func RPMDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsF // We only handle rpminfo_objects. continue default: - zlog.Debug(ctx). - Err(err). - Str("object_ref", objRef). - Msg("failed object lookup. moving to next criterion") + slog.DebugContext(ctx, "failed object lookup, moving to next criterion", + "reason", err, + "object_ref", objRef) continue } @@ -113,10 +110,9 @@ func RPMDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsF stateRef := stateRefs[0].StateRef state, err = rpmStateLookup(root, stateRef) if err != nil { - zlog.Debug(ctx). - Err(err). - Str("state_ref", stateRef). - Msg("failed state lookup. moving to next criterion") + slog.DebugContext(ctx, "failed state lookup, moving to next criterion", + "reason", err, + "state_ref", stateRef) continue } // if we find a state, but this state does not contain an EVR, diff --git a/python/matcher.go b/python/matcher.go index 737c5004f..dec42af2f 100644 --- a/python/matcher.go +++ b/python/matcher.go @@ -2,10 +2,9 @@ package python import ( "context" + "log/slog" "net/url" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/pkg/pep440" @@ -42,10 +41,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v // Parse the package first. If it cannot be parsed, it cannot properly be analyzed for vulnerabilities. rv, err := pep440.Parse(record.Package.Version) if err != nil { - zlog.Warn(ctx). - Str("package", record.Package.Name). - Stringer("version", &rv). - Msg("unable to parse python package version") + slog.WarnContext(ctx, "unable to parse python package version", + "package", record.Package.Name, + "version", &rv) return false, err } @@ -59,10 +57,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v if introduced != "" { iv, err := pep440.Parse(introduced) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", introduced). - Msg("unable to parse python introduced version") + slog.WarnContext(ctx, "unable to parse python introduced version", + "package", vuln.Package.Name, + "version", introduced) return false, err } // If the package's version is less than the introduced version, it's not vulnerable. @@ -77,10 +74,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v case fixedVersion != "": uv, err := pep440.Parse(fixedVersion) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", fixedVersion). - Msg("unable to parse python fixed version") + slog.WarnContext(ctx, "unable to parse python fixed version", + "package", vuln.Package.Name, + "version", fixedVersion) return false, err } // The package is affected if its version is less than the fixed version. @@ -88,10 +84,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v case lastAffected != "": la, err := pep440.Parse(lastAffected) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", lastAffected). - Msg("unable to parse python last_affected version") + slog.WarnContext(ctx, "unable to parse python last_affected version", + "package", vuln.Package.Name, + "version", lastAffected) return false, err } // The package is affected if its version is less than or equal to the last affected version. diff --git a/python/matcher_test.go b/python/matcher_test.go index 6b9ee5101..186d50286 100644 --- a/python/matcher_test.go +++ b/python/matcher_test.go @@ -1,11 +1,9 @@ package python_test import ( - "context" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" @@ -23,7 +21,7 @@ type matcherTestcase struct { func (tc matcherTestcase) Run(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) got, err := tc.Matcher.Vulnerable(ctx, &tc.R, &tc.V) if err != nil { t.Error(err) @@ -238,7 +236,8 @@ func TestMatcher(t *testing.T) { for _, testcase := range testcases { t.Run(testcase.Name, func(t *testing.T) { - got, err := testcase.Matcher.Vulnerable(context.Background(), &testcase.R, &testcase.V) + ctx := test.Logging(t) + got, err := testcase.Matcher.Vulnerable(ctx, &testcase.R, &testcase.V) if err != nil { t.Fatal(err) } @@ -248,5 +247,6 @@ func TestMatcher(t *testing.T) { }) } - test.RunMatcherTests(zlog.Test(context.Background(), t), t, "testdata/matcher", new(python.Matcher)) + ctx := test.Logging(t) + test.RunMatcherTests(ctx, t, "testdata/matcher", new(python.Matcher)) } diff --git a/python/packagescanner.go b/python/packagescanner.go index 6f6eb8d96..df990411a 100644 --- a/python/packagescanner.go +++ b/python/packagescanner.go @@ -8,14 +8,13 @@ import ( "context" "fmt" "io/fs" + "log/slog" "net/textproto" "path" "path/filepath" "runtime/trace" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/pkg/pep440" @@ -59,12 +58,8 @@ func (*Scanner) Kind() string { return "package" } func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "python/Scanner.Scan", - "version", ps.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") if err := ctx.Err(); err != nil { return nil, err } @@ -85,9 +80,7 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco } for _, n := range ms { if set.Contains(n) { - zlog.Debug(ctx). - Str("path", n). - Msg("file path determined to be of RPM origin") + slog.DebugContext(ctx, "file path determined to be of RPM origin", "path", n) continue } b, err := fs.ReadFile(sys, n) @@ -99,18 +92,12 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco rd := textproto.NewReader(bufio.NewReader(bytes.NewReader(b))) hdr, err := rd.ReadMIMEHeader() if err != nil && hdr == nil { - zlog.Warn(ctx). - Err(err). - Str("path", n). - Msg("unable to read metadata, skipping") + slog.WarnContext(ctx, "unable to read metadata, skipping", "reason", err, "path", n) continue } v, err := pep440.Parse(hdr.Get("Version")) if err != nil { - zlog.Warn(ctx). - Err(err). - Str("path", n). - Msg("couldn't parse the version, skipping") + slog.WarnContext(ctx, "couldn't parse the version, skipping", "reason", err, "path", n) continue } pkgDB := filepath.Join(n, "..", "..") @@ -172,14 +159,7 @@ func findDeliciousEgg(ctx context.Context, sys fs.FS) (out []string, err error) } return out, fs.WalkDir(sys, ".", func(p string, d fs.DirEntry, err error) error { - ev := zlog.Debug(ctx). - Str("file", p) - var success bool - defer func() { - if !success { - ev.Discard().Send() - } - }() + attrs := []slog.Attr{slog.String("file", p)} switch { case err != nil: return err @@ -190,15 +170,15 @@ func findDeliciousEgg(ctx context.Context, sys fs.FS) (out []string, err error) switch { case isRPM: pat = `usr/lib*/python[23].*` - ev = ev.Bool("rpm_dir", true) + attrs = append(attrs, slog.Bool("rpm_dir", true)) case dpkg: pat = `usr/lib*/python[23]` - ev = ev.Bool("dpkg_dir", true) + attrs = append(attrs, slog.Bool("dpkg_dir", true)) default: panic("programmer error: unreachable") } if m, _ := path.Match(pat, p); m { - ev.Msg("skipping directory") + slog.LogAttrs(ctx, slog.LevelDebug, "skipping directory", attrs...) return fs.SkipDir } fallthrough @@ -208,29 +188,28 @@ func findDeliciousEgg(ctx context.Context, sys fs.FS) (out []string, err error) case strings.HasPrefix(filepath.Base(p), ".wh."): return nil case strings.HasSuffix(p, `.egg/EGG-INFO/PKG-INFO`): - ev = ev.Str("kind", ".egg") + attrs = append(attrs, slog.String("kind", ".egg")) case strings.HasSuffix(p, `.egg-info`): fallthrough case strings.HasSuffix(p, `.egg-info/PKG-INFO`): - ev = ev.Str("kind", ".egg-info") + attrs = append(attrs, slog.String("kind", ".egg-info")) case strings.HasSuffix(p, `.dist-info/METADATA`): - ev = ev.Str("kind", "wheel") + attrs = append(attrs, slog.String("kind", "wheel")) // See if we can discern the installer. var installer string ip := path.Join(path.Dir(p), `INSTALLER`) if ic, err := fs.ReadFile(sys, ip); err == nil { installer = string(bytes.TrimSpace(ic)) - ev = ev.Str("installer", installer) + attrs = append(attrs, slog.String("installer", installer)) } if _, ok := blocklist[installer]; ok { - ev.Msg("skipping package") + slog.LogAttrs(ctx, slog.LevelDebug, "skipping package", attrs...) return nil } default: return nil } - ev.Msg("found package") - success = true + slog.LogAttrs(ctx, slog.LevelDebug, "found package", attrs...) out = append(out, p) return nil }) diff --git a/python/packagescanner_test.go b/python/packagescanner_test.go index fe6d502b8..9306cf4b5 100644 --- a/python/packagescanner_test.go +++ b/python/packagescanner_test.go @@ -13,7 +13,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/python" @@ -80,7 +79,6 @@ var scanTable = []test.ScannerTestcase{ func TestScanLocal(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) type testcase struct { Name string @@ -97,7 +95,7 @@ func TestScanLocal(t *testing.T) { for _, tt := range table { t.Run(tt.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f, err := os.Open(tt.Path) if err != nil { t.Fatal(err) diff --git a/python/purl.go b/python/purl.go new file mode 100644 index 000000000..82270068f --- /dev/null +++ b/python/purl.go @@ -0,0 +1,46 @@ +package python + +import ( + "context" + "fmt" + + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" + "github.com/quay/claircore/pkg/pep440" +) + +const ( + // PURLType is the type of package URL for Python packages. + PURLType = "pypi" +) + +// GeneratePURL generates a PyPI PURL for a given [claircore.IndexRecord]. +// Example: pkg:pypi/django@1.11.1 +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + return packageurl.PackageURL{ + Type: PURLType, + Name: ir.Package.Name, + Version: ir.Package.Version, + }, nil +} + +// ParsePURL parses a PyPI PURL into a list of [claircore.IndexRecord]s. +// The matcher needs the NormalizedVersion to be set, and it to be pep440. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + v, err := pep440.Parse(purl.Version) + if err != nil { + return nil, fmt.Errorf("python: unable to parse version: %w", err) + } + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Name, + Version: v.String(), + NormalizedVersion: v.Version(), + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, nil +} diff --git a/python/purl_test.go b/python/purl_test.go new file mode 100644 index 000000000..b70eb59f6 --- /dev/null +++ b/python/purl_test.go @@ -0,0 +1,137 @@ +package python + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" + "github.com/quay/claircore/pkg/pep440" +) + +func TestRoundTripIndexRecordPython(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + wantErr bool + }{ + { + name: "urllib3", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "urllib3", + Version: "2.2.1", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + { + name: "django", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "django", + Version: "1.11.1", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + { + name: "bad-version", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "django", + Version: "something-invalid", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Align expected NormalizedVersion with GeneratePURL/ParsePURL behaviour. + // This helps the testcases stay simple. + if v, err := pep440.Parse(tc.ir.Package.Version); err == nil { + tc.ir.Package.NormalizedVersion = v.Version() + } + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + + got, err := ParsePURL(ctx, p) + if tc.wantErr { + if err == nil { + t.Fatalf("expected an error, got nil") + } + return + } + if err != nil { + t.Fatalf("ParsePURL unexpected error: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "requests", + Version: "2.31.0", + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Name: "requests", + Version: "2.31.0", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Ensure NormalizedVersion is set since GeneratePURL uses it. + if v, err := pep440.Parse(tc.ir.Package.Version); err == nil { + tc.ir.Package.NormalizedVersion = v.Version() + } + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore fields not relevant to PURL round-trip for Python. + cmpopts.IgnoreFields(claircore.Package{}, "PackageDB", "Filepath"), +} diff --git a/rhel/coalescer.go b/rhel/coalescer.go index 0d017354a..faa99b323 100644 --- a/rhel/coalescer.go +++ b/rhel/coalescer.go @@ -2,11 +2,11 @@ package rhel import ( "context" + "log/slog" "net/url" "github.com/quay/claircore" "github.com/quay/claircore/indexer" - "github.com/quay/zlog" ) // Coalescer takes individual layer artifacts and coalesces them into a full @@ -128,10 +128,7 @@ func (*Coalescer) Coalesce(ctx context.Context, artifacts []*indexer.LayerArtifa for _, repo := range layerArtifacts.Repos { uri, err := url.ParseQuery(repo.URI) if err != nil { - zlog.Warn(ctx). - Err(err). - Str("repository", repo.URI). - Msg("unable to parse repository URI") + slog.WarnContext(ctx, "unable to parse repository URI", "repository", repo.URI) continue } repoRepoid := uri.Get("repoid") diff --git a/rhel/coalescer_test.go b/rhel/coalescer_test.go index 9aec1eb61..395f6b806 100644 --- a/rhel/coalescer_test.go +++ b/rhel/coalescer_test.go @@ -9,7 +9,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/quay/claircore/toolkit/types/cpe" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -23,7 +22,7 @@ type CoalescerTestcase struct { func (tc CoalescerTestcase) Run(ctx context.Context, t *testing.T) { t.Run(tc.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t, ctx) input, want := tc.Fixture(t) got, err := new(Coalescer).Coalesce(ctx, input) if err != nil { @@ -38,7 +37,7 @@ func (tc CoalescerTestcase) Run(ctx context.Context, t *testing.T) { func TestCoalescer(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) tcs := []CoalescerTestcase{ { diff --git a/rhel/distributionscanner.go b/rhel/distributionscanner.go index c68516a66..588783d75 100644 --- a/rhel/distributionscanner.go +++ b/rhel/distributionscanner.go @@ -5,12 +5,11 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "regexp" "runtime/trace" "strconv" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -40,12 +39,8 @@ func (*DistributionScanner) Kind() string { return "distribution" } // Scan implements [indexer.DistributionScanner]. func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "rhel/DistributionScanner.Scan", - "version", ds.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { return nil, fmt.Errorf("rhel: unable to open layer: %w", err) @@ -55,7 +50,7 @@ func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([] return nil, fmt.Errorf("rhel: unexpected error reading files: %w", err) } if d == nil { - zlog.Debug(ctx).Msg("didn't find an os-release or redhat-release file") + slog.DebugContext(ctx, "didn't find an os-release or redhat-release file") return nil, nil } return []*claircore.Distribution{d}, nil diff --git a/rhel/internal/common/updater.go b/rhel/internal/common/updater.go index 41c0fad5b..6fb9828a7 100644 --- a/rhel/internal/common/updater.go +++ b/rhel/internal/common/updater.go @@ -4,13 +4,13 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/http" "reflect" "sync" "sync/atomic" "time" - "github.com/quay/zlog" "golang.org/x/time/rate" ) @@ -44,16 +44,12 @@ func NewUpdater(url string, init any) *Updater { // Get returns a pointer to the current copy of the value. The Get call may be // hijacked to update the value from the configured endpoint. func (u *Updater) Get(ctx context.Context, c *http.Client) (any, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "rhel/internal/common/Updater.Get") var err error if u.url != "" && u.reqRate.Allow() { - zlog.Debug(ctx).Msg("got unlucky, updating mapping file") + slog.DebugContext(ctx, "got unlucky, updating mapping file") err = u.Fetch(ctx, c) if err != nil { - zlog.Error(ctx). - Err(err). - Msg("error updating mapping file") + slog.ErrorContext(ctx, "error updating mapping file", "reason", err) } } @@ -64,10 +60,8 @@ func (u *Updater) Get(ctx context.Context, c *http.Client) (any, error) { // // Fetch is safe to call concurrently. func (u *Updater) Fetch(ctx context.Context, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, - "component", "rhel/internal/common/Updater.Fetch", - "url", u.url) - zlog.Debug(ctx).Msg("attempting fetch of mapping file") + log := slog.With("url", u.url) + log.DebugContext(ctx, "attempting fetch of mapping file") req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.url, nil) if err != nil { @@ -87,9 +81,7 @@ func (u *Updater) Fetch(ctx context.Context, c *http.Client) error { switch resp.StatusCode { case http.StatusOK: case http.StatusNotModified: - zlog.Debug(ctx). - Str("since", u.lastModified). - Msg("response not modified; no update necessary") + log.DebugContext(ctx, "response not modified; no update necessary", "since", u.lastModified) return nil default: return fmt.Errorf("received status code %d querying mapping url", resp.StatusCode) @@ -105,6 +97,6 @@ func (u *Updater) Fetch(ctx context.Context, c *http.Client) error { u.mu.Unlock() // atomic store of mapping file u.value.Store(v) - zlog.Debug(ctx).Msg("atomic update of local mapping file complete") + log.DebugContext(ctx, "atomic update of local mapping file complete") return nil } diff --git a/rhel/matcher.go b/rhel/matcher.go index d197973dd..e5dfd3687 100644 --- a/rhel/matcher.go +++ b/rhel/matcher.go @@ -2,10 +2,10 @@ package rhel import ( "context" + "log/slog" "strings" version "github.com/knqyf263/go-rpm-version" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" @@ -72,10 +72,7 @@ func (m *Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, // support the claircore.Vulnerability.Repo.CPE field. vuln.Repo.CPE, err = cpe.Unbind(vuln.Repo.Name) if err != nil { - zlog.Warn(ctx). - Str("vulnerability name", vuln.Name). - Err(err). - Msg("unable to unbind repo CPE") + slog.WarnContext(ctx, "unable to unbind repo CPE", "reason", err, "vulnerability name", vuln.Name) return false, nil } if !cpe.Compare(vuln.Repo.CPE, record.Repository.CPE).IsSuperset() && !IsCPESubstringMatch(record.Repository.CPE, vuln.Repo.CPE) { diff --git a/rhel/matcher_test.go b/rhel/matcher_test.go index 8caa589ef..9b701afbe 100644 --- a/rhel/matcher_test.go +++ b/rhel/matcher_test.go @@ -1,12 +1,10 @@ package rhel import ( - "context" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" + "github.com/quay/claircore/test" "github.com/quay/claircore/toolkit/types/cpe" ) @@ -155,8 +153,7 @@ func TestVulnerable(t *testing.T) { } m := &Matcher{} - ctx := context.Background() - ctx = zlog.Test(ctx, t) + ctx := test.Logging(t) for _, tc := range testCases { got, err := m.Vulnerable(ctx, tc.ir, tc.v) if err != nil { diff --git a/rhel/matcherfactory.go b/rhel/matcherfactory.go index 135eded2c..d16a9d390 100644 --- a/rhel/matcherfactory.go +++ b/rhel/matcherfactory.go @@ -2,10 +2,9 @@ package rhel import ( "context" + "log/slog" "net/http" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" ) @@ -18,7 +17,7 @@ type MatcherFactory struct { ignoreUnpatched bool } -// MatcherFactory implements [driver.MatcherFactory] +// Matcher implements [driver.MatcherFactory] func (f *MatcherFactory) Matcher(ctx context.Context) ([]driver.Matcher, error) { m := &Matcher{ ignoreUnpatched: f.ignoreUnpatched, @@ -30,16 +29,13 @@ type MatcherFactoryConfig struct { IgnoreUnpatched bool `json:"ignore_unpatched" yaml:"ignore_unpatched"` } -// MatcherFactory implements driver.MatcherConfigurable. +// Configure implements [driver.MatcherConfigurable]. func (f *MatcherFactory) Configure(ctx context.Context, cfg driver.MatcherConfigUnmarshaler, _ *http.Client) error { var fc MatcherFactoryConfig if err := cfg(&fc); err != nil { return err } f.ignoreUnpatched = fc.IgnoreUnpatched - zlog.Info(ctx). - Str("component", "rhel/MatcherFactory.Configure"). - Bool("ignore_unpatched", f.ignoreUnpatched). - Msg("configured") + slog.InfoContext(ctx, "configured", "ignore_unpatched", f.ignoreUnpatched) return nil } diff --git a/rhel/packagescanner.go b/rhel/packagescanner.go index bc0bfae24..5285803cf 100644 --- a/rhel/packagescanner.go +++ b/rhel/packagescanner.go @@ -5,14 +5,14 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/internal/dnf" "github.com/quay/claircore/internal/rpm" + "github.com/quay/claircore/toolkit/log" ) var _ indexer.PackageScanner = PackageScanner{} @@ -34,18 +34,14 @@ func (p PackageScanner) Version() string { return "1" } // // This implementation stores additional information needed to correlate with // [claircore.Repository] values in the "RepositoryHint" field. -func (p PackageScanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) { +func (p PackageScanner) Scan(ctx context.Context, layer *claircore.Layer) (out []*claircore.Package, err error) { if err := ctx.Err(); err != nil { return nil, err } defer trace.StartRegion(ctx, "PackageScanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "rhel/PackageScanner.Scan", - "version", p.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := layer.FS() if err != nil { @@ -59,15 +55,14 @@ func (p PackageScanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*cl } doDNFWrap = cm == nil || cm.FromDNFHint - var out []*claircore.Package found, errFunc := rpm.FindDBs(ctx, sys) defer func() { err = errors.Join(err, errFunc()) }() for found := range found { err = func() error { - ctx := zlog.ContextWithValues(ctx, "db", found.String()) - zlog.Debug(ctx).Msg("examining database") + ctx := log.With(ctx, "db", found) + slog.DebugContext(ctx, "examining database") db, err := rpm.OpenDB(ctx, sys, found) switch { case err == nil: @@ -99,5 +94,5 @@ func (p PackageScanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*cl } } - return out, nil + return } diff --git a/rhel/packagescanner_test.go b/rhel/packagescanner_test.go index b0d0eb32b..99141ac02 100644 --- a/rhel/packagescanner_test.go +++ b/rhel/packagescanner_test.go @@ -1,14 +1,14 @@ package rhel import ( - "context" + "errors" + "io/fs" "net/url" "os" "path/filepath" "testing" "github.com/quay/claircore" - "github.com/quay/zlog" "github.com/quay/claircore/test" "github.com/quay/claircore/test/rpmtest" @@ -21,7 +21,7 @@ import ( func TestPackageDetection(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) ms, err := filepath.Glob("testdata/package/*.txtar") if err != nil { panic("programmer error") // static glob @@ -55,7 +55,6 @@ func TestPackageDetection(t *testing.T) { // get repoid information. func TestPackageScannerDNFWrapLogic(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) tests := []struct { name string @@ -86,7 +85,7 @@ func TestPackageScannerDNFWrapLogic(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) // Create layer from tar file f, err := os.Open(tt.layerPath) @@ -143,8 +142,42 @@ func TestPackageScannerDNFWrapLogic(t *testing.T) { if !tt.expectRepoidHint && packagesWithRepoid > 0 { t.Errorf("Expected no packages to have repoid hints, but found %d", packagesWithRepoid) - } }) } } + +// TestPackageScannerPropagatesFindDBErrors ensures filesystem walk errors from FindDBs +// are surfaced to the caller instead of being swallowed. +func TestPackageScannerPropagatesFindDBErrors(t *testing.T) { + t.Parallel() + ctx := test.Logging(t) + + // Point the layer at a non-existent directory to force WalkDir to fail immediately. + missingRoot := filepath.Join(t.TempDir(), "missing-root") + + var l claircore.Layer + desc := claircore.LayerDescription{ + Digest: `sha256:` + "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", + URI: `file://` + missingRoot, + MediaType: `application/vnd.claircore.filesystem`, + Headers: make(map[string][]string), + } + if err := l.Init(ctx, &desc, nil); err != nil { + t.Fatalf("init layer: %v", err) + } + t.Cleanup(func() { + if err := l.Close(); err != nil { + t.Error(err) + } + }) + + scanner := &PackageScanner{} + _, err := scanner.Scan(ctx, &l) + if err == nil { + t.Fatalf("expected error from PackageScanner.Scan") + } + if !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("expected error to wrap fs.ErrNotExist, got %v", err) + } +} diff --git a/rhel/repositoryscanner.go b/rhel/repositoryscanner.go index a55bbefd2..b4e463c8d 100644 --- a/rhel/repositoryscanner.go +++ b/rhel/repositoryscanner.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "net/http" "net/url" "os" @@ -16,8 +17,6 @@ import ( "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/internal/dnf" @@ -108,9 +107,6 @@ func (*RepositoryScanner) Kind() string { return "repository" } // Configure implements [indexer.RPCScanner]. func (r *RepositoryScanner) Configure(ctx context.Context, f indexer.ConfigDeserializer, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, - "component", "rhel/RepositoryScanner.Configure", - "version", r.Version()) r.client = c if err := f(&r.cfg); err != nil { return err @@ -157,12 +153,8 @@ func (r *RepositoryScanner) Configure(ctx context.Context, f indexer.ConfigDeser // stored in the [claircore.Repository]'s "Name" and "CPE" fields, respectively. func (r *RepositoryScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Repository, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "rhel/RepositoryScanner.Scan", - "version", r.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { @@ -220,11 +212,10 @@ func (r *RepositoryScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*cl for repoid, cpeID := range pairs { c, err := cpe.Unbind(cpeID) if err != nil { - zlog.Warn(ctx). - Err(err). - Str("url", bugURL(cpeID, err)). - Str("cpeID", cpeID). - Msg("invalid CPE, please report a bug upstream") + slog.WarnContext(ctx, "invalid CPE, please report a bug upstream", + "reason", err, + "cpeID", cpeID, + "url", bugURL(cpeID, err)) continue } @@ -257,7 +248,7 @@ func (r *RepositoryScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*cl } // BugURL constructs a link directly to the Red Hat Jira instance. -func bugURL(id string, err error) string { +func bugURL(id string, err error) *url.URL { const desc = "A Clair instance noticed an invalid CPE:{code}%s{code}\nThe reported error was:{code}%v{code}" v := url.Values{ "pid": {"12330022"}, // ID for the Red Hat Jira "SECDATA" project. @@ -271,7 +262,7 @@ func bugURL(id string, err error) string { Path: "/secure/CreateIssueDetails!init.jspa", RawQuery: v.Encode(), } - return u.String() + return &u } // RepoidsFromContentSets returns a slice of repoids, as discovered by examining @@ -304,9 +295,7 @@ func (m *mappingFile) GetOne(ctx context.Context, repoid string) (cpes []string, if repo, ok := m.Data[repoid]; ok { return repo.CPEs, true } - zlog.Debug(ctx). - Str("repository", repoid). - Msg("repository not present in a mapping file") + slog.DebugContext(ctx, "repository not present in a mapping file", "repository", repoid) return nil, false } diff --git a/rhel/repositoryscanner_test.go b/rhel/repositoryscanner_test.go index 9692172c8..ce390145a 100644 --- a/rhel/repositoryscanner_test.go +++ b/rhel/repositoryscanner_test.go @@ -2,7 +2,6 @@ package rhel import ( "bytes" - "context" "encoding/json" "errors" "io" @@ -16,7 +15,6 @@ import ( "github.com/quay/claircore/pkg/tmp" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" @@ -25,7 +23,6 @@ import ( func TestRepositoryScanner(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) repoData := ` { @@ -320,7 +317,7 @@ func TestRepositoryScanner(t *testing.T) { for _, tt := range table { t.Run(tt.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f, err := os.Open(tt.layerPath) if err != nil { t.Fatal(err) @@ -388,7 +385,7 @@ func TestBugURL(t *testing.T) { if err == nil { t.Error("expected error") } - got := bugURL(in, err) + got := bugURL(in, err).String() t.Logf("\ngot: %s\nwant: %s", got, want) if got != want { diff --git a/rhel/rhcc/coalescer_test.go b/rhel/rhcc/coalescer_test.go index fa5a281fc..edd571d46 100644 --- a/rhel/rhcc/coalescer_test.go +++ b/rhel/rhcc/coalescer_test.go @@ -1,12 +1,9 @@ package rhcc import ( - "context" "encoding/json" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/test" @@ -14,7 +11,7 @@ import ( func TestCoalescer(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) coalescer := &coalescer{} repo := []*claircore.Repository{&GoldRepo} repo[0].ID = "1" // Assign it an ID and check it later. diff --git a/rhel/rhcc/detector.go b/rhel/rhcc/detector.go index 675e54e3f..c82868379 100644 --- a/rhel/rhcc/detector.go +++ b/rhel/rhcc/detector.go @@ -6,11 +6,10 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "strconv" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/pkg/rhctag" @@ -43,7 +42,6 @@ func (s *detector) Kind() string { return "package" } // It performs a package scan on the given layer and returns all the RHEL // container identifying metadata by using the labels.json file. func (s *detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Package, error) { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/rhcc/detector.Scan") if err := ctx.Err(); err != nil { return nil, err } @@ -62,7 +60,7 @@ func (s *detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.P // Check if required fields are present if labels.Name == "" || labels.Architecture == "" || labels.Created.IsZero() { - zlog.Warn(ctx).Msg("required labels not found in labels.json") + slog.WarnContext(ctx, "required labels not found in labels.json") return nil, nil } @@ -70,7 +68,7 @@ func (s *detector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.P rhctagVersion, err := rhctag.Parse(vr) if err != nil { // A unix timestamp should always parse, so this is unexpected. - zlog.Warn(ctx).Err(err).Str("version", vr).Msg("failed to parse rhctag version") + slog.WarnContext(ctx, "failed to parse rhctag version", "reason", err, "version", vr) return nil, nil } @@ -146,7 +144,6 @@ func (s *repoDetector) Kind() string { return "repository" } // It performs a repository scan on the given layer and returns all the RHEL // container identifying metadata by using the labels.json file. func (s *repoDetector) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Repository, error) { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/rhcc/repoDetector.Scan") if err := ctx.Err(); err != nil { return nil, err } @@ -176,5 +173,4 @@ func (s *repoDetector) Scan(ctx context.Context, l *claircore.Layer) ([]*clairco Key: RepositoryKey, }, }, nil - } diff --git a/rhel/rhcc/detector_test.go b/rhel/rhcc/detector_test.go index 1c197e352..c2010c628 100644 --- a/rhel/rhcc/detector_test.go +++ b/rhel/rhcc/detector_test.go @@ -2,14 +2,12 @@ package rhcc import ( "archive/tar" - "context" "io" "os" "strings" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" @@ -18,7 +16,7 @@ import ( func TestPackageDetector(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) type testcase struct { Name string @@ -130,7 +128,7 @@ func TestPackageDetector(t *testing.T) { }) for _, tt := range table { t.Run(tt.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) mod := test.Modtime(t, tt.LabelsFile) a.GenerateLayer(t, tt.Name, mod, genLayerFunc(tt.LabelsFile, tt.LabelsPath)) @@ -164,7 +162,7 @@ func TestPackageDetector(t *testing.T) { func TestRepositoryDetector(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) type testcase struct { Name string @@ -218,7 +216,7 @@ func TestRepositoryDetector(t *testing.T) { }) for _, tt := range table { t.Run(tt.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) mod := test.Modtime(t, tt.LabelsFile) a.GenerateLayer(t, tt.Name, mod, genLayerFunc(tt.LabelsFile, tt.LabelsPath)) diff --git a/rhel/rhcc/matcher.go b/rhel/rhcc/matcher.go index fcc7c52d5..1b156b61b 100644 --- a/rhel/rhcc/matcher.go +++ b/rhel/rhcc/matcher.go @@ -2,9 +2,9 @@ package rhcc import ( "context" + "log/slog" rpmVersion "github.com/knqyf263/go-rpm-version" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" @@ -43,10 +43,7 @@ func (*matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v // This is not a gold repo record, so we need to check if the CPE matches. vuln.Repo.CPE, err = cpe.Unbind(vuln.Repo.Name) if err != nil { - zlog.Warn(ctx). - Str("vulnerability name", vuln.Name). - Err(err). - Msg("unable to unbind repo CPE") + slog.WarnContext(ctx, "unable to unbind repo CPE", "reason", err, "vulnerability name", vuln.Name) return false, nil } if !cpe.Compare(vuln.Repo.CPE, record.Repository.CPE).IsSuperset() && !rhel.IsCPESubstringMatch(record.Repository.CPE, vuln.Repo.CPE) { @@ -54,10 +51,7 @@ func (*matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v } } pkgVer, fixedInVer := rpmVersion.NewVersion(record.Package.Version), rpmVersion.NewVersion(vuln.FixedInVersion) - zlog.Debug(ctx). - Str("record", record.Package.Version). - Str("vulnerability", vuln.FixedInVersion). - Msg("comparing versions") + slog.DebugContext(ctx, "comparing versions", "record", record.Package.Version, "vulnerability", vuln.FixedInVersion) return pkgVer.LessThan(fixedInVer), nil } diff --git a/rhel/rhcc/purl.go b/rhel/rhcc/purl.go new file mode 100644 index 000000000..b6a8ba8af --- /dev/null +++ b/rhel/rhcc/purl.go @@ -0,0 +1,80 @@ +package rhcc + +import ( + "context" + "fmt" + + "github.com/Masterminds/semver" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" + "github.com/quay/claircore/toolkit/types/cpe" +) + +const ( + // PURLType is the type of package URL for Red Hat Container Catalog packages. + PURLType = "oci" +) + +// GenerateOCIPURL generates an OCI PURL for a given [claircore.IndexRecord]. +// Example: +// pkg:oci/ubi@sha256:dbc1e98d14a022542e45b5f22e0206d3f86b5bdf237b58ee7170c9ddd1b3a283?repository_url=registry.access.redhat.com/ubi9/ubi +func GenerateOCIPURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + purl := packageurl.PackageURL{ + Type: PURLType, + Name: ir.Package.Name, + // This tends to be represented as a digest but we don't persist that information + // or use it for matching so we use the version as it is the most applicable. + Version: ir.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": ir.Package.Arch, + "tag": ir.Package.Version, + }), + } + if ir.Repository != nil && ir.Repository.Name != GoldRepo.Name { + purl.Qualifiers = append( + purl.Qualifiers, + packageurl.Qualifier{Key: "container_cpe", Value: ir.Repository.CPE.String()}, + ) + } + return purl, nil +} + +// ParseOCIPURL parses an OCI PURL into a list of [claircore.IndexRecord]s. +// The matcher needs the NormalizedVersion to be set. +func ParseOCIPURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + ir := &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Arch: purl.Qualifiers.Map()["arch"], + RepositoryHint: "rhcc", + }, + } + + ir.Repository = &GoldRepo + if containerCPE, ok := purl.Qualifiers.Map()["container_cpe"]; ok { + cpe, err := cpe.Unbind(containerCPE) + if err != nil { + return nil, err + } + ir.Repository = &claircore.Repository{ + CPE: cpe, + Name: cpe.String(), + Key: RepositoryKey, + } + } + + // Deal with the version. + ir.Package.Version = purl.Version + if tag := purl.Qualifiers.Map()["tag"]; tag != "" { + // Prefer the tag over the version if it is present. + ir.Package.Version = tag + } + sv, err := semver.NewVersion(ir.Package.Version) + if err != nil { + return nil, fmt.Errorf("error parsing version: %w", err) + } + ir.Package.NormalizedVersion = claircore.FromSemver(sv) + return []*claircore.IndexRecord{ir}, nil +} diff --git a/rhel/rhcc/purl_test.go b/rhel/rhcc/purl_test.go new file mode 100644 index 000000000..28e3126c9 --- /dev/null +++ b/rhel/rhcc/purl_test.go @@ -0,0 +1,153 @@ +package rhcc + +import ( + "context" + "testing" + + "github.com/Masterminds/semver" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" + "github.com/quay/claircore/toolkit/types/cpe" +) + +func TestRoundTripIndexRecordOCI(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "goldrepo-no-container-cpe", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "ubi", + Version: "v9.3.1", + NormalizedVersion: claircore.FromSemver(semver.MustParse("v9.3.1")), + Arch: "x86_64", + RepositoryHint: "rhcc", + }, + Repository: &GoldRepo, + }, + }, + { + name: "with-container-cpe", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "ubi-micro", + Version: "v9.3.1", + NormalizedVersion: claircore.FromSemver(semver.MustParse("v9.3.1")), + Arch: "x86_64", + RepositoryHint: "rhcc", + }, + Repository: &claircore.Repository{ + CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:9::baseos"), + Name: "cpe:2.3:o:redhat:enterprise_linux:9:*:baseos:*:*:*:*:*", + Key: RepositoryKey, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GenerateOCIPURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GenerateOCIPURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + got, err := ParseOCIPURL(ctx, p) + if err != nil { + t.Fatalf("ParseOCIPURL: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGenerateOCIPURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "basic-goldrepo", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "ubi", + Version: "v9.3.1", + Arch: "amd64", + }, + Repository: &GoldRepo, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Name: "ubi", + Version: "v9.3.1", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "amd64"}, + {Key: "tag", Value: "v9.3.1"}, + }, + }, + }, + { + name: "with-container-cpe", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "ubi", + Version: "v9.3.1", + Arch: "amd64", + }, + Repository: &claircore.Repository{ + CPE: cpe.MustUnbind("cpe:/o:redhat:enterprise_linux:9::baseos"), + Name: "cpe:2.3:o:redhat:enterprise_linux:9:*:baseos:*:*:*:*:*", + Key: RepositoryKey, + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Name: "ubi", + Version: "v9.3.1", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "amd64"}, + {Key: "tag", Value: "v9.3.1"}, + {Key: "container_cpe", Value: "cpe:2.3:o:redhat:enterprise_linux:9:*:baseos:*:*:*:*:*"}, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := GenerateOCIPURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GenerateOCIPURL: %v", err) + } + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore Distribution field as there isn't currently a serialized format + // and it is not currently used in the matching. + cmpopts.IgnoreFields(claircore.IndexRecord{}, "Distribution"), + cmpCPE, +} + +var cmpCPE = cmp.FilterPath( + func(p cmp.Path) bool { return p.Last().String() == ".CPE" }, + cmp.Comparer(func(a, b cpe.WFN) bool { return a.String() == b.String() }), +) diff --git a/rhel/rhcc/scanner.go b/rhel/rhcc/scanner.go index 39e2f27de..bb5a9b105 100644 --- a/rhel/rhcc/scanner.go +++ b/rhel/rhcc/scanner.go @@ -6,13 +6,12 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "net/http" "os" "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/internal/zreader" @@ -62,7 +61,6 @@ const DefaultName2ReposMappingURL = "https://security.access.redhat.com/data/met // Configure implements [indexer.RPCScanner]. func (s *scanner) Configure(ctx context.Context, f indexer.ConfigDeserializer, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/rhcc/scanner.Configure") s.client = c if err := f(&s.cfg); err != nil { return err @@ -117,7 +115,6 @@ func (s *scanner) Kind() string { return "package" } // Scan implements [indexer.PackageScanner]. func (s *scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Package, error) { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/rhcc/scanner.Scan") const ( compLabel = `com.redhat.component` nameLabel = `name` @@ -160,7 +157,7 @@ func (s *scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pa var ok bool (*chk.Found), ok = labels[chk.Want] if !ok { - zlog.Info(ctx).Str("label", chk.Want).Msg("expected label not found in dockerfile") + slog.InfoContext(ctx, "expected label not found in dockerfile", "label", chk.Want) return nil, nil } } @@ -189,8 +186,7 @@ func (s *scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pa } repos, ok := v.Data[name] if ok { - zlog.Debug(ctx).Str("name", name). - Msg("name present in mapping file") + slog.DebugContext(ctx, "name present in mapping file", "name", name) } else { // Didn't find external_repos in mapping, use name label as package // name. @@ -229,9 +225,7 @@ func findLabels(ctx context.Context, sys fs.FS) (map[string]string, string, erro if len(ms) == 0 { return nil, "", errNotFound } - zlog.Debug(ctx). - Strs("paths", ms). - Msg("found possible buildinfo Dockerfile(s)") + slog.DebugContext(ctx, "found possible buildinfo Dockerfile(s)", "paths", ms) var p string for _, m := range ms { if strings.Count(m, "-") > 1 { @@ -242,9 +236,7 @@ func findLabels(ctx context.Context, sys fs.FS) (map[string]string, string, erro if p == "" { return nil, "", errNotFound } - zlog.Info(ctx). - Str("path", p). - Msg("found buildinfo Dockerfile") + slog.InfoContext(ctx, "found buildinfo Dockerfile", "path", p) f, err := sys.Open(p) if err != nil { return nil, "", err @@ -287,7 +279,6 @@ func (s *reposcanner) Kind() string { return "repository" } // Scan implements [indexer.RepositoryScanner]. func (s *reposcanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Repository, error) { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/rhcc/reposcanner.Scan") sys, err := l.FS() if err != nil { return nil, fmt.Errorf("rhcc: unable to open layer: %w", err) @@ -299,7 +290,6 @@ func (s *reposcanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircor if len(ms) == 0 { return nil, nil } - zlog.Debug(ctx). - Msg("found buildinfo Dockerfile") + slog.DebugContext(ctx, "found buildinfo Dockerfile") return []*claircore.Repository{&GoldRepo}, nil } diff --git a/rhel/rhcc/scanner_test.go b/rhel/rhcc/scanner_test.go index 30e03199c..f38fd9af8 100644 --- a/rhel/rhcc/scanner_test.go +++ b/rhel/rhcc/scanner_test.go @@ -2,7 +2,6 @@ package rhcc import ( "archive/tar" - "context" "encoding/json" "io" "net/http" @@ -13,7 +12,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" @@ -21,7 +19,7 @@ import ( func TestContainerScanner(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) clairSourceContainer := &claircore.Package{ Name: "quay-clair-container", Version: "v3.5.5-4", @@ -191,7 +189,7 @@ func TestContainerScanner(t *testing.T) { }) for _, tt := range table { t.Run(tt.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) mod := test.Modtime(t, tt.Dockerfile) a.GenerateLayer(t, tt.Name, mod, func(t testing.TB, w *os.File) { dockerfile, err := os.Open(tt.Dockerfile) diff --git a/rhel/rhel.go b/rhel/rhel.go index 7ae0a0cfd..2f442e616 100644 --- a/rhel/rhel.go +++ b/rhel/rhel.go @@ -13,8 +13,7 @@ import ( "errors" "fmt" "io/fs" - - "github.com/quay/zlog" + "log/slog" ) // GetContentManifest reads and parses the content manifest file from the layer. @@ -35,9 +34,7 @@ func getContentManifest(ctx context.Context, sys fs.FS) (*contentManifest, error return nil, nil } p := ms[0] - zlog.Debug(ctx). - Str("manifest-path", p). - Msg("found content manifest file") + slog.DebugContext(ctx, "found content manifest file", "manifest-path", p) b, err := fs.ReadFile(sys, p) if err != nil { return nil, fmt.Errorf("rhel: unable to read %q: %w", p, err) @@ -48,10 +45,9 @@ func getContentManifest(ctx context.Context, sys fs.FS) (*contentManifest, error switch { case errors.Is(err, nil): case errors.As(err, &syntaxErr): - zlog.Warn(ctx). - Str("manifest-path", p). - Err(err). - Msg("could not unmarshal content_manifests file") + slog.WarnContext(ctx, "could not unmarshal content_manifests file", + "manifest-path", p, + "reason", err) return nil, nil default: return nil, err diff --git a/rhel/rhel_test.go b/rhel/rhel_test.go index f61c55127..b76b92811 100644 --- a/rhel/rhel_test.go +++ b/rhel/rhel_test.go @@ -1,7 +1,6 @@ package rhel import ( - "context" "os" "strings" "testing" @@ -9,12 +8,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/quay/claircore" "github.com/quay/claircore/test" - "github.com/quay/zlog" ) func TestGetContentManifest(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) tests := []struct { name string @@ -53,7 +50,7 @@ func TestGetContentManifest(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) // Create a layer from the test data f, err := os.Open(tt.tarPath) diff --git a/rhel/vex/fetcher.go b/rhel/vex/fetcher.go index 0ab3b5615..552ec50ac 100644 --- a/rhel/vex/fetcher.go +++ b/rhel/vex/fetcher.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "path" @@ -18,7 +19,6 @@ import ( "time" "github.com/klauspost/compress/snappy" - "github.com/quay/zlog" "github.com/quay/claircore/internal/httputil" "github.com/quay/claircore/internal/zreader" @@ -36,14 +36,13 @@ var ( // - Make a request to discover the latest archive endpoint. // - Make a HEAD request to archive endpoint to get the last-modified header. // - Save the last-modified time in the fingerprint's requestTime. -// 2. Process the changes.csv file, requesting and appending the entries that changed since the finderprint's requestTime. -// 3. Process the deletions.csv file, processing the entries that changed since the finderprint's requestTime. +// 2. Process the changes.csv file, requesting and appending the entries that changed since the fingerprint's requestTime. +// 3. Process the deletions.csv file, processing the entries that changed since the fingerprint's requestTime. // 4. If we need to process entire archive, request the archive data and append the entries that have not been changed or deleted. // // This helps to ensure that we only persist one copy of an advisory in the worst possible case. In most cases, // after the initial load, the number of processed files should be very small. func (u *Updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/vex/Updater.Fetch") fp, err := parseFingerprint(hint) if err != nil { return nil, hint, err @@ -59,16 +58,14 @@ func (u *Updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCl var success bool defer func() { if err := cw.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close snappy writer") + slog.WarnContext(ctx, "unable to close snappy writer", "reason", err) } if _, err := f.Seek(0, io.SeekStart); err != nil { - zlog.Warn(ctx). - Err(err). - Msg("unable to seek file back to start") + slog.WarnContext(ctx, "unable to seek file back to start", "reason", err) } if !success { if err := f.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + slog.WarnContext(ctx, "unable to close spool", "reason", err) } } }() @@ -84,9 +81,7 @@ func (u *Updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCl if err != nil { return nil, hint, fmt.Errorf("could not get compressed file URL: %w", err) } - zlog.Debug(ctx). - Str("url", compressedURL.String()). - Msg("got compressed URL") + slog.DebugContext(ctx, "got compressed URL", "url", compressedURL) fp.requestTime, err = u.getLastModified(ctx, compressedURL) if err != nil { @@ -178,10 +173,9 @@ func (u *Updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCl return nil, hint, fmt.Errorf("error reading tar contents: %w", err) } - zlog.Debug(ctx). - Str("updater", u.Name()). - Int("entries written", entriesWritten). - Msg("finished writing compressed data to spool") + slog.DebugContext(ctx, "finished writing compressed data to spool", + "updater", u.Name(), + "entries written", entriesWritten) } fp.version = updaterVersion @@ -353,7 +347,7 @@ func (u *Updater) processChanges(ctx context.Context, w io.Writer, fp *fingerpri if err != nil { return fmt.Errorf("error reading from buffer: %w", err) } - zlog.Debug(ctx).Str("url", advisoryURI.String()).Msg("copying body to file") + slog.DebugContext(ctx, "copying body to file", "url", advisoryURI) err = json.Compact(&bc, buf.Bytes()) if err != nil { return fmt.Errorf("error compressing JSON: %w", err) @@ -434,7 +428,7 @@ func (u *Updater) processDeletions(ctx context.Context, w io.Writer, fp *fingerp deletedJSON, err := createDeletedJSON(cvePath) if err != nil { - zlog.Warn(ctx).Err(err).Msg("error creating JSON object denoting deletion") + slog.WarnContext(ctx, "error creating JSON object denoting deletion", "reason", err) } bc.Write(deletedJSON) bc.WriteByte('\n') diff --git a/rhel/vex/fetcher_test.go b/rhel/vex/fetcher_test.go index 588a0ec70..ac5026e70 100644 --- a/rhel/vex/fetcher_test.go +++ b/rhel/vex/fetcher_test.go @@ -3,18 +3,17 @@ package vex import ( "bufio" "bytes" - "context" "testing" "github.com/klauspost/compress/snappy" - "github.com/quay/zlog" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" "github.com/quay/claircore/toolkit/types/csaf" ) func TestFactory(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) root, c := ServeSecDB(t, "testdata/server.txtar") fac := &Factory{} err := fac.Configure(ctx, func(v any) error { diff --git a/rhel/vex/parser.go b/rhel/vex/parser.go index 39a0b803c..d624728c6 100644 --- a/rhel/vex/parser.go +++ b/rhel/vex/parser.go @@ -7,13 +7,13 @@ import ( "errors" "fmt" "io" + "log/slog" "math" "strings" "unique" "github.com/klauspost/compress/snappy" "github.com/package-url/packageurl-go" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/pkg/rhctag" @@ -32,7 +32,6 @@ func (u *Updater) Parse(ctx context.Context, contents io.ReadCloser) ([]*clairco // DeltaParse implements [driver.DeltaUpdater]. func (u *Updater) DeltaParse(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, []string, error) { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/vex/Updater.DeltaParse") // This map is needed for deduplication purposes, the compressed CSAF data will include // entries that have been subsequently updated in the changes. out := map[string][]*claircore.Vulnerability{} @@ -59,7 +58,6 @@ func (u *Updater) DeltaParse(ctx context.Context, contents io.ReadCloser) ([]*cl selfLink = r.URL } } - ctx = zlog.ContextWithValues(ctx, "link", selfLink) creator := newCreator(name, selfLink, c, pc, rc) for _, v := range c.Vulnerabilities { // Create vuln here, there should always be one vulnerability @@ -252,7 +250,8 @@ func extractProductNames(prodRef, relatesToProdRef string, comps []string, c *cs func (c *creator) knownAffectedVulnerabilities(ctx context.Context, v csaf.Vulnerability, protoVulnFunc func() *claircore.Vulnerability) ([]*claircore.Vulnerability, error) { ranger := newRanger() unrelatedProductIDs := []string{} - debugEnabled := zlog.Debug(ctx).Enabled() + log := slog.With("link", c.vulnLink) + debugEnabled := log.Enabled(ctx, slog.LevelDebug) out := []*claircore.Vulnerability{} for _, pc := range v.ProductStatus["known_affected"] { pkgName, modName, repoName, err := walkRelationships(pc, c.c) @@ -272,16 +271,13 @@ func (c *creator) knownAffectedVulnerabilities(ctx context.Context, v csaf.Vulne repoProd := c.pc.Get(repoName, c.c) if repoProd == nil { - zlog.Warn(ctx). - Str("prod", repoName). - Msg("could not find product in product tree") + // Should never get here, error in data + log.WarnContext(ctx, "could not find product in product tree", "prod", repoName) continue } cpeHelper, ok := repoProd.IdentificationHelper["cpe"] if !ok { - zlog.Warn(ctx). - Str("prod", repoName). - Msg("could not find cpe helper type in product") + log.WarnContext(ctx, "could not find cpe helper type in product", "prod", repoName) continue } @@ -291,10 +287,7 @@ func (c *creator) knownAffectedVulnerabilities(ctx context.Context, v csaf.Vulne modProd := c.pc.Get(modName, c.c) modName, err = createPackageModule(modProd) if err != nil { - zlog.Warn(ctx). - Str("module", modName). - Err(err). - Msg("could not create package module") + log.WarnContext(ctx, "could not create package module", "module", modName, "reason", err) } } @@ -302,9 +295,7 @@ func (c *creator) knownAffectedVulnerabilities(ctx context.Context, v csaf.Vulne compProd := c.pc.Get(pkgName, c.c) if compProd == nil { // Should never get here, error in data - zlog.Warn(ctx). - Str("pkg", pkgName). - Msg("could not find package in product tree") + log.WarnContext(ctx, "could not find package in product tree", "pkg", pkgName) continue } vuln := protoVulnFunc() @@ -321,29 +312,22 @@ func (c *creator) knownAffectedVulnerabilities(ctx context.Context, v csaf.Vulne if ok { purl, err := packageurl.FromString(purlHelper) if err != nil { - zlog.Warn(ctx). - Str("purlHelper", purlHelper). - Err(err). - Msg("could not parse PURL") + log.WarnContext(ctx, "could not parse pURL", "reason", err, "purl", purlHelper) continue } if !checkPURL(purl) { continue } if pn, err := extractPackageName(purl); err != nil { - zlog.Warn(ctx). - Str("purl", purl.String()). - Err(err). - Msg("could not extract package name from pURL") + log.WarnContext(ctx, "error extracting package name from pURL", + "reason", err, "purl", purl) } else { pkgName = pn } if modName, err = componentPURLToModuleName(purl); err != nil { - zlog.Warn(ctx). - Str("purl", purl.String()). - Err(err). - Msg("invalid rpmmod in component pURL") + log.WarnContext(ctx, "invalid rpmmod in component pURL", + "reason", err, "purl", purl) continue } @@ -351,10 +335,8 @@ func (c *creator) knownAffectedVulnerabilities(ctx context.Context, v csaf.Vulne vuln.Repo = c.rc.Get(wfn, rhcc.RepositoryKey) vuln.Range, err = ranger.add(pkgName, vuln.FixedInVersion) if err != nil { - zlog.Warn(ctx). - Str("FixedInVersion", vuln.FixedInVersion). - Err(err). - Msg("could not parse version into range") + log.WarnContext(ctx, "could not parse version into range", + "reason", err, "FixedInVersion", vuln.FixedInVersion) continue } } @@ -387,9 +369,7 @@ func (c *creator) knownAffectedVulnerabilities(ctx context.Context, v csaf.Vulne } ranger.resetLowest() if len(unrelatedProductIDs) > 0 { - zlog.Debug(ctx). - Strs("product_ids", unrelatedProductIDs). - Msg("skipped unrelatable product_ids") + log.DebugContext(ctx, "skipped unrelatable product_ids", "product_ids", unrelatedProductIDs) } return out, nil @@ -424,7 +404,7 @@ func newRanger() *ranger { // to the fixedInVersion. Add also saves the lowest range per packageName that it's seen. // This allows resetLowest() to zero out the lowest values we saw per packageName. func (r *ranger) add(packageName, fixedInVersion string) (*claircore.Range, error) { - var rng = &claircore.Range{} + rng := &claircore.Range{} if fixedInVersion == "" { zeroVer := &rhctag.Version{} @@ -463,7 +443,8 @@ func (r *ranger) resetLowest() { func (c *creator) fixedVulnerabilities(ctx context.Context, v csaf.Vulnerability, protoVulnFunc func() *claircore.Vulnerability) ([]*claircore.Vulnerability, error) { ranger := newRanger() unrelatedProductIDs := []string{} - debugEnabled := zlog.Debug(ctx).Enabled() + log := slog.With("link", c.vulnLink) + debugEnabled := log.Enabled(ctx, slog.LevelDebug) for _, pc := range v.ProductStatus["fixed"] { pkgName, modName, repoName, err := walkRelationships(pc, c.c) if err != nil { @@ -478,16 +459,12 @@ func (c *creator) fixedVulnerabilities(ctx context.Context, v csaf.Vulnerability repoProd := c.pc.Get(repoName, c.c) if repoProd == nil { // Should never get here, error in data - zlog.Warn(ctx). - Str("prod", repoName). - Msg("could not find product in product tree") + log.WarnContext(ctx, "could not find product in product tree", "prod", repoName) continue } cpeHelper, ok := repoProd.IdentificationHelper["cpe"] if !ok { - zlog.Warn(ctx). - Str("prod", repoName). - Msg("could not find cpe helper type in product") + log.WarnContext(ctx, "could not find cpe helper type in product", "prod", repoName) continue } @@ -497,33 +474,24 @@ func (c *creator) fixedVulnerabilities(ctx context.Context, v csaf.Vulnerability modProd := c.pc.Get(modName, c.c) modName, err = createPackageModule(modProd) if err != nil { - zlog.Warn(ctx). - Str("module", modName). - Err(err). - Msg("could not create package module") + log.WarnContext(ctx, "could not create package module", "module", modName, "reason", err) } } compProd := c.pc.Get(pkgName, c.c) if compProd == nil { // Should never get here, error in data - zlog.Warn(ctx). - Str("pkg", pkgName). - Msg("could not find package in product tree") + log.WarnContext(ctx, "could not find package in product tree", "pkg", pkgName) continue } purlHelper, ok := compProd.IdentificationHelper["purl"] if !ok { - zlog.Warn(ctx). - Str("pkg", pkgName). - Msg("could not find purl helper type in product") + log.WarnContext(ctx, "could not find purl helper type in product", "pkg", pkgName) continue } purl, err := packageurl.FromString(purlHelper) if err != nil { - zlog.Warn(ctx). - Str("purl", purlHelper). - Msg("could not parse PURL") + log.WarnContext(ctx, "could not parse pURL", "reason", err, "purl", purlHelper) continue } if !checkPURL(purl) { @@ -531,25 +499,19 @@ func (c *creator) fixedVulnerabilities(ctx context.Context, v csaf.Vulnerability } fixedIn, err := extractFixedInVersion(purl) if err != nil { - zlog.Warn(ctx). - Str("purl", purl.String()). - Err(err). - Msg("error extracting fixed_in version from pURL") + log.WarnContext(ctx, "error extracting fixed_in version from pURL", + "reason", err, "purl", purl) continue } packageName, err := extractPackageName(purl) if err != nil { - zlog.Warn(ctx). - Str("purl", purl.String()). - Err(err). - Msg("error extracting package name from pURL") + log.WarnContext(ctx, "error extracting package name from pURL", + "reason", err, "purl", purl) continue } if modName, err = componentPURLToModuleName(purl); err != nil { - zlog.Warn(ctx). - Str("purl", purl.String()). - Err(err). - Msg("invalid rpmmod in component pURL") + log.WarnContext(ctx, "invalid rpmmod in component pURL", + "reason", err, "purl", purl) continue } @@ -583,10 +545,8 @@ func (c *creator) fixedVulnerabilities(ctx context.Context, v csaf.Vulnerability vuln.Repo = c.rc.Get(wfn, rhcc.RepositoryKey) vuln.Range, err = ranger.add(packageName, vuln.FixedInVersion) if err != nil { - zlog.Warn(ctx). - Str("FixedInVersion", vuln.FixedInVersion). - Err(err). - Msg("could not parse version into range") + log.WarnContext(ctx, "could not parse version into range", + "reason", err, "FixedInVersion", vuln.FixedInVersion) continue } default: @@ -617,9 +577,7 @@ func (c *creator) fixedVulnerabilities(ctx context.Context, v csaf.Vulnerability } ranger.resetLowest() if len(unrelatedProductIDs) > 0 { - zlog.Debug(ctx). - Strs("product_ids", unrelatedProductIDs). - Msg("skipped unrelatable product_ids") + log.DebugContext(ctx, "skipped unrelatable product_ids", "product_ids", unrelatedProductIDs) } out := make([]*claircore.Vulnerability, len(c.fixedVulns)) diff --git a/rhel/vex/parser_test.go b/rhel/vex/parser_test.go index af86f26c5..11311779e 100644 --- a/rhel/vex/parser_test.go +++ b/rhel/vex/parser_test.go @@ -2,7 +2,6 @@ package vex import ( "bytes" - "context" "encoding/json" "errors" "io" @@ -15,10 +14,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/klauspost/compress/snappy" "github.com/package-url/packageurl-go" - "github.com/quay/zlog" "github.com/quay/claircore" + "github.com/quay/claircore/test" "github.com/quay/claircore/toolkit/types/cpe" "github.com/quay/claircore/toolkit/types/csaf" ) @@ -373,7 +372,6 @@ func TestEscapeCPE(t *testing.T) { func TestParseCompare(t *testing.T) { t.Parallel() - ctx := context.Background() url, err := url.Parse(BaseURL) if err != nil { t.Error(err) @@ -415,7 +413,7 @@ func TestParseCompare(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f, err := os.Open(tc.filename) if err != nil { t.Fatalf("failed to open test data file %s: %v", tc.filename, err) @@ -458,7 +456,6 @@ func TestParseCompare(t *testing.T) { func TestParse(t *testing.T) { t.Parallel() - ctx := context.Background() url, err := url.Parse(BaseURL) if err != nil { t.Error(err) @@ -520,7 +517,7 @@ func TestParse(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) // Build a snappy stream consisting of compacted JSON blobs from each file, // newline-separated to simulate JSONL input across multiple files. diff --git a/rhel/vex/updater.go b/rhel/vex/updater.go index 3db969136..36285164a 100644 --- a/rhel/vex/updater.go +++ b/rhel/vex/updater.go @@ -4,13 +4,12 @@ import ( "context" "errors" "fmt" + "log/slog" "net/http" "net/url" "strings" "time" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" ) @@ -163,8 +162,6 @@ type UpdaterConfig struct { // Configure implements driver.Configurable. func (u *Updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, "component", "rhel/vex/Updater.Configure") - var cfg UpdaterConfig if err := f(&cfg); err != nil { return err @@ -178,9 +175,7 @@ func (u *Updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c * return err } u.url = url - zlog.Info(ctx). - Str("updater", u.Name()). - Msg("configured url") + slog.InfoContext(ctx, "configured url", "updater", u.Name()) u.client = c return nil diff --git a/rpm/packagescanner.go b/rpm/packagescanner.go index 0bd2e0304..6fd7d381d 100644 --- a/rpm/packagescanner.go +++ b/rpm/packagescanner.go @@ -6,10 +6,9 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/internal/rpm" @@ -56,12 +55,8 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) (out []*cla } defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "rpm/Scanner.Scan", - "version", ps.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := layer.FS() if err != nil { @@ -74,8 +69,7 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) (out []*cla }() for db := range dbs { err = func() error { - ctx := zlog.ContextWithValues(ctx, "db", db.String()) - zlog.Debug(ctx).Msg("examining database") + slog.DebugContext(ctx, "examining database", "db", db) db, dbErr := rpm.OpenDB(ctx, sys, db) switch { case dbErr == nil: diff --git a/rpm/packagescanner_test.go b/rpm/packagescanner_test.go index a2a464b3d..4f14237a9 100644 --- a/rpm/packagescanner_test.go +++ b/rpm/packagescanner_test.go @@ -1,7 +1,6 @@ package rpm import ( - "context" "os" "path/filepath" "slices" @@ -9,7 +8,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/internal/wart" @@ -25,7 +23,7 @@ import ( ////go:generate fetch -o testdata/package/ubi9_nodejs.txtar ubi9/nodejs-20 func TestPackageDetection(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) ms, err := filepath.Glob("testdata/package/*.txtar") if err != nil { panic("programmer error") // static glob @@ -66,7 +64,7 @@ func TestPackageDetection(t *testing.T) { arches[img.Archtecture] = struct{}{} t.Run(img.Archtecture, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) m, err := ar.Manifest(img.ID) if err != nil { t.Fatal(err) @@ -119,7 +117,7 @@ func TestPackageDetection(t *testing.T) { } func TestDanglingSymlink(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) desc := claircore.LayerDescription{ Digest: test.RandomSHA256Digest(t).String(), URI: "file:///dev/null", @@ -154,7 +152,6 @@ func TestDanglingSymlink(t *testing.T) { } func TestLayer(t *testing.T) { - ctx := context.Background() ents, err := os.ReadDir(`testdata/layers`) if err != nil { t.Fatal(err) @@ -173,7 +170,7 @@ func TestLayer(t *testing.T) { continue } t.Run(n, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) f, err := os.Open(filepath.Join(`testdata/layers`, n)) if err != nil { t.Fatal(err) @@ -203,7 +200,7 @@ func TestLayer(t *testing.T) { } func TestLayerDoesNotExist(t *testing.T) { - ctx := zlog.Test(t.Context(), t) + ctx := test.Logging(t) desc := &claircore.LayerDescription{ Digest: test.RandomSHA256Digest(t).String(), diff --git a/ruby/matcher.go b/ruby/matcher.go index 8f42df9ba..c1b18d772 100644 --- a/ruby/matcher.go +++ b/ruby/matcher.go @@ -2,10 +2,9 @@ package ruby import ( "context" + "log/slog" "net/url" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" ) @@ -41,10 +40,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v // Parse the package first. If it cannot be parsed, it cannot properly be analyzed for vulnerabilities. rv, err := NewVersion(record.Package.Version) if err != nil { - zlog.Warn(ctx). - Str("package", record.Package.Name). - Str("version", record.Package.Version). - Msg("unable to parse ruby gem package version") + slog.WarnContext(ctx, "unable to parse ruby gem package version", + "package", record.Package.Name, + "version", record.Package.Version) return false, err } @@ -58,10 +56,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v if introduced != "" { iv, err := NewVersion(introduced) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", introduced). - Msg("unable to parse ruby gem introduced version") + slog.WarnContext(ctx, "unable to parse ruby gem introduced version", + "package", vuln.Package.Name, + "version", introduced) return false, err } // If the package's version is less than the introduced version, it's not vulnerable. @@ -76,10 +73,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v case fixedVersion != "": fv, err := NewVersion(fixedVersion) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", fixedVersion). - Msg("unable to parse ruby gem fixed version") + slog.WarnContext(ctx, "unable to parse ruby gem fixed version", + "package", vuln.Package.Name, + "version", fixedVersion) return false, err } // The package is affected if its version is less than the fixed version. @@ -87,10 +83,9 @@ func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v case lastAffected != "": la, err := NewVersion(lastAffected) if err != nil { - zlog.Warn(ctx). - Str("package", vuln.Package.Name). - Str("version", lastAffected). - Msg("unable to parse ruby gem last_affected version") + slog.WarnContext(ctx, "unable to parse ruby gem last_affected version", + "package", vuln.Package.Name, + "version", lastAffected) return false, err } // The package is affected if its version is less than or equal to the last affected version. diff --git a/ruby/matcher_test.go b/ruby/matcher_test.go index a23a2ffe4..a4b9222f2 100644 --- a/ruby/matcher_test.go +++ b/ruby/matcher_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/test" @@ -183,5 +182,6 @@ func TestVulnerable(t *testing.T) { } func TestMatcher(t *testing.T) { - test.RunMatcherTests(zlog.Test(context.Background(), t), t, "testdata/matcher", new(Matcher)) + ctx := test.Logging(t) + test.RunMatcherTests(ctx, t, "testdata/matcher", new(Matcher)) } diff --git a/ruby/packagescanner.go b/ruby/packagescanner.go index 93100ef0e..c7da5fa17 100644 --- a/ruby/packagescanner.go +++ b/ruby/packagescanner.go @@ -7,13 +7,12 @@ import ( "context" "fmt" "io/fs" + "log/slog" "path/filepath" "regexp" "runtime/trace" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" "github.com/quay/claircore/rpm" @@ -82,12 +81,8 @@ func (*Scanner) Kind() string { return "package" } func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "ruby/Scanner.Scan", - "version", ps.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") if err := ctx.Err(); err != nil { return nil, err } @@ -109,9 +104,7 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco } for _, g := range gs { if set.Contains(g) { - zlog.Debug(ctx). - Str("path", g). - Msg("file path determined to be of RPM origin") + slog.DebugContext(ctx, "file path determined to be of RPM origin", "path", g) continue } f, err := sys.Open(g) @@ -132,17 +125,14 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco } } if err := scanner.Err(); err != nil { - zlog.Warn(ctx). - Err(err). - Str("path", g). - Msg("unable to read metadata, skipping") + slog.WarnContext(ctx, "unable to read metadata, skipping", + "reason", err, + "path", g) continue } if name == "" || version == "" { - zlog.Warn(ctx). - Str("path", g). - Msg("couldn't parse name or version, skipping") + slog.WarnContext(ctx, "couldn't parse name or version, skipping", "path", g) continue } @@ -172,14 +162,7 @@ func trim(s string) string { func gems(ctx context.Context, sys fs.FS) (out []string, err error) { return out, fs.WalkDir(sys, ".", func(p string, d fs.DirEntry, err error) error { - ev := zlog.Debug(ctx). - Str("file", p) - var success bool - defer func() { - if !success { - ev.Discard().Send() - } - }() + attrs := []slog.Attr{slog.String("file", p)} switch { case err != nil: return err @@ -189,12 +172,11 @@ func gems(ctx context.Context, sys fs.FS) (out []string, err error) { case strings.HasPrefix(filepath.Base(p), ".wh."): return nil case gemspecPath.MatchString(p): - ev = ev.Str("kind", "gem") + attrs = append(attrs, slog.String("kind", "gem")) default: return nil } - ev.Msg("found package") - success = true + slog.LogAttrs(ctx, slog.LevelDebug, "found package", attrs...) out = append(out, p) return nil }) diff --git a/ruby/packagescanner_test.go b/ruby/packagescanner_test.go index fc5bb422c..9704f7183 100644 --- a/ruby/packagescanner_test.go +++ b/ruby/packagescanner_test.go @@ -1,11 +1,9 @@ package ruby_test import ( - "context" "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/ruby" @@ -13,8 +11,6 @@ import ( ) func TestScanRemote(t *testing.T) { - ctx := zlog.Test(context.Background(), t) - type testcase struct { Name string Ref test.LayerRef @@ -138,7 +134,7 @@ func TestScanRemote(t *testing.T) { for _, tc := range table { t.Run(tc.Name, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) l := test.RealizeLayer(ctx, t, tc.Ref) var scanner ruby.Scanner diff --git a/ruby/purl.go b/ruby/purl.go new file mode 100644 index 000000000..7dc49a1f6 --- /dev/null +++ b/ruby/purl.go @@ -0,0 +1,39 @@ +package ruby + +import ( + "context" + + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for Ruby packages. + PURLType = "gem" +) + +// GeneratePURL generates a Ruby PURL for a given [claircore.IndexRecord]. +// Example: pkg:gem/rails@6.1.0 +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + return packageurl.PackageURL{ + Type: PURLType, + Name: ir.Package.Name, + Version: ir.Package.Version, + }, nil +} + +// ParsePURL parses a Ruby PURL into a list of [claircore.IndexRecord]s. +// The matcher needs the NormalizedVersion to be set. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, nil +} diff --git a/ruby/purl_test.go b/ruby/purl_test.go new file mode 100644 index 000000000..4190149a1 --- /dev/null +++ b/ruby/purl_test.go @@ -0,0 +1,106 @@ +package ruby + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordRuby(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "rails", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "rails", + Version: "6.1.0", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + { + name: "rack", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "rack", + Version: "2.2.8", + Kind: claircore.BINARY, + }, + Repository: &Repository, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParsePURL: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "rails", + Version: "6.1.0", + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Name: "rails", + Version: "6.1.0", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore fields not relevant to PURL round-trip for Ruby. + cmpopts.IgnoreFields(claircore.Package{}, "PackageDB", "Filepath"), +} diff --git a/sbom/spdx/encoder.go b/sbom/spdx/encoder.go index a5134ac76..e2ba77ebf 100644 --- a/sbom/spdx/encoder.go +++ b/sbom/spdx/encoder.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "log/slog" "runtime/debug" "slices" "strconv" @@ -18,7 +19,6 @@ import ( "github.com/quay/claircore" "github.com/quay/claircore/purl" "github.com/quay/claircore/sbom" - "github.com/quay/zlog" ) // Version describes the SPDX version to target. @@ -209,7 +209,7 @@ func (e *Encoder) parseIndexReport(ctx context.Context, ir *claircore.IndexRepor purl, err := e.PURLConverter.Generate(ctx, r) switch { case err != nil: - zlog.Warn(ctx).Err(err).Msg("failed to generate PURL") + slog.WarnContext(ctx, "failed to generate PURL", "reason", err) default: pkg.PackageExternalReferences = append(pkg.PackageExternalReferences, &v2_3.PackageExternalReference{ Category: "PACKAGE_MANAGER", diff --git a/scanner/pkgconfig/scanner.go b/scanner/pkgconfig/scanner.go index 0a1ee5ed2..53e8ea655 100644 --- a/scanner/pkgconfig/scanner.go +++ b/scanner/pkgconfig/scanner.go @@ -12,12 +12,11 @@ import ( "fmt" "io" "io/fs" + "log/slog" "os" "path/filepath" "runtime/trace" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -49,12 +48,8 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco } defer trace.StartRegion(ctx, "Scanner.Scan").End() trace.Log(ctx, "layer", layer.Hash.String()) - ctx = zlog.ContextWithValues(ctx, - "component", "scanner/pkgconfig/Scanner.Scan", - "version", ps.Version(), - "layer", layer.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := layer.FS() if err != nil { @@ -71,9 +66,8 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco case filepath.Ext(d.Name()) != ".pc": return nil } - zlog.Debug(ctx). - Str("path", p). - Msg("found possible pkg-config file") + log := slog.With("path", p) + log.DebugContext(ctx, "found possible pkg-config file") f, err := sys.Open(p) if err != nil { return err @@ -84,9 +78,7 @@ func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*clairco switch { case errors.Is(nil, err): case errors.Is(errInvalid, err): // skip - zlog.Info(ctx). - Str("path", p). - Msg("invalid pkg-config file") + slog.InfoContext(ctx, "invalid pkg-config file") return nil default: return err diff --git a/suse/distributionscanner.go b/suse/distributionscanner.go index cae924edb..05a1c34e0 100644 --- a/suse/distributionscanner.go +++ b/suse/distributionscanner.go @@ -4,10 +4,10 @@ import ( "bytes" "context" "fmt" + "log/slog" "runtime/trace" "github.com/Masterminds/semver" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -64,15 +64,11 @@ func (*DistributionScanner) Kind() string { return scannerKind } // If the files are found but all regexp fail to match an empty slice is returned. func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "suse/DistributionScanner.Scan", - "version", ds.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") files, err := l.Files(osReleasePath, suseReleasePath) if err != nil { - zlog.Debug(ctx).Msg("didn't find an os-release or SuSE-release") + slog.DebugContext(ctx, "didn't find an os-release or SuSE-release") return nil, nil } for _, buff := range files { @@ -91,7 +87,7 @@ func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([] func (ds *DistributionScanner) parse(ctx context.Context, buff *bytes.Buffer) *claircore.Distribution { kv, err := osrelease.Parse(ctx, buff) if err != nil { - zlog.Warn(ctx).Err(err).Msg("malformed os-release file") + slog.WarnContext(ctx, "malformed os-release file", "reason", err) return nil } cpeName, cpeOK := kv["CPE_NAME"] @@ -101,13 +97,13 @@ func (ds *DistributionScanner) parse(ctx context.Context, buff *bytes.Buffer) *c // Instead of regexing through, we can grab the CPE. c, err := cpe.Unbind(cpeName) if err != nil { - zlog.Warn(ctx).Err(err).Msg("could not unbind CPE") + slog.WarnContext(ctx, "could not unbind CPE", "reason", err) return nil } d, err := cpeToDist(c) if err != nil { - zlog.Warn(ctx).Err(err).Msg("error converting cpe to distribution") + slog.WarnContext(ctx, "error converting cpe to distribution", "reason", err) return nil } diff --git a/suse/factory.go b/suse/factory.go index 8f70875c7..c73c61644 100644 --- a/suse/factory.go +++ b/suse/factory.go @@ -3,6 +3,7 @@ package suse import ( "context" "fmt" + "log/slog" "net/http" "net/url" "regexp" @@ -10,7 +11,6 @@ import ( "sync" "github.com/Masterminds/semver" - "github.com/quay/zlog" "golang.org/x/net/html" "github.com/quay/claircore" @@ -37,20 +37,16 @@ type Factory struct { // UpdaterSet implements [driver.UpdaterSetFactory]. func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { - ctx = zlog.ContextWithValues(ctx, "component", "suse/Factory.UpdaterSet") us := driver.NewUpdaterSet() if f.c == nil { - zlog.Info(ctx). - Msg("unconfigured") + slog.InfoContext(ctx, "unconfigured") return us, nil } req, err := http.NewRequestWithContext(ctx, http.MethodGet, f.base.String(), nil) if err != nil { return us, fmt.Errorf("suse: unable to construct request: %w", err) } - zlog.Debug(ctx). - Stringer("url", f.base). - Msg("making request") + slog.DebugContext(ctx, "making request", "url", f.base) res, err := f.c.Do(req) if err != nil { return us, fmt.Errorf("suse: error requesting %q: %w", f.base.String(), err) @@ -128,26 +124,34 @@ var releases sync.Map func mkELDist(oURL, ver string) *claircore.Distribution { name := strings.TrimSuffix(oURL, ".xml.gz") - v, _ := releases.LoadOrStore(name, &claircore.Distribution{ + v, _ := releases.LoadOrStore(name, ELDist(ver)) + return v.(*claircore.Distribution) +} + +func mkLeapDist(oURL, ver string) *claircore.Distribution { + name := strings.TrimSuffix(oURL, ".xml.gz") + v, _ := releases.LoadOrStore(name, leapDist(ver)) + return v.(*claircore.Distribution) +} + +func ELDist(ver string) *claircore.Distribution { + return &claircore.Distribution{ Name: "SLES", DID: "sles", Version: ver, VersionID: ver, PrettyName: "SUSE Linux Enterprise Server " + ver, - }) - return v.(*claircore.Distribution) + } } -func mkLeapDist(oURL, ver string) *claircore.Distribution { - name := strings.TrimSuffix(oURL, ".xml.gz") - v, _ := releases.LoadOrStore(name, &claircore.Distribution{ +func leapDist(ver string) *claircore.Distribution { + return &claircore.Distribution{ Name: "openSUSE Leap", DID: "opensuse-leap", Version: ver, VersionID: ver, PrettyName: "openSUSE Leap " + ver, - }) - return v.(*claircore.Distribution) + } } // FactoryConfig is the configuration accepted by the Factory. diff --git a/suse/factory_test.go b/suse/factory_test.go index 22aca61b5..7ab76c838 100644 --- a/suse/factory_test.go +++ b/suse/factory_test.go @@ -1,16 +1,17 @@ package suse import ( - "context" "net/http" "net/url" "testing" - "github.com/quay/zlog" + "github.com/quay/claircore/test" + "github.com/quay/claircore/test/integration" ) func TestFactory(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + integration.Skip(t) + ctx := test.Logging(t) u, _ := url.Parse(base) f := &Factory{ c: http.DefaultClient, diff --git a/suse/parser.go b/suse/parser.go index e53cde0e0..eca750efb 100644 --- a/suse/parser.go +++ b/suse/parser.go @@ -5,9 +5,9 @@ import ( "encoding/xml" "fmt" "io" + "log/slog" "github.com/quay/goval-parser/oval" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/internal/xmlutil" @@ -18,9 +18,7 @@ import ( var _ driver.Parser = (*Updater)(nil) func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "suse/Updater.Parse") - zlog.Info(ctx).Msg("starting parse") + slog.InfoContext(ctx, "starting parse") defer r.Close() root := oval.Root{} dec := xml.NewDecoder(r) @@ -28,7 +26,7 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln if err := dec.Decode(&root); err != nil { return nil, fmt.Errorf("suse: unable to decode OVAL document: %w", err) } - zlog.Debug(ctx).Msg("xml decoded") + slog.DebugContext(ctx, "xml decoded") protoVulns := func(def oval.Definition) ([]*claircore.Vulnerability, error) { return []*claircore.Vulnerability{ { diff --git a/suse/purl.go b/suse/purl.go new file mode 100644 index 000000000..c9e98b382 --- /dev/null +++ b/suse/purl.go @@ -0,0 +1,93 @@ +package suse + +import ( + "context" + "fmt" + "strings" + + "github.com/package-url/packageurl-go" + "github.com/quay/claircore" + "github.com/quay/claircore/toolkit/types/cpe" +) + +const ( + // PURLType is the type of package URL for RPM packages. + PURLType = "rpm" + // PURLNamespace is the namespace of SUSE RPMs. + PURLNamespace = "opensuse" +) + +// GeneratePURL generates an RPM PURL for a given [claircore.IndexRecord]. +func GeneratePURL(ctx context.Context, ir *claircore.IndexRecord) (packageurl.PackageURL, error) { + p := packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: ir.Package.Name, + Version: ir.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": ir.Package.Arch, + }), + } + if ir.Distribution != nil { + // We don't persist the CPE in the Distribution but try it first in case it's available. + if c := ir.Distribution.CPE.String(); c != "" { + p.Qualifiers = append(p.Qualifiers, packageurl.Qualifier{ + Key: "distro_cpe", + Value: c, + }) + } + + if ir.Distribution.DID != "" && ir.Distribution.VersionID != "" { + p.Qualifiers = append(p.Qualifiers, packageurl.Qualifier{ + Key: "distro", + Value: ir.Distribution.DID + "-" + ir.Distribution.VersionID, + }) + } + } + return p, nil +} + +// ParsePURL parses an RPM PURL into a list of [claircore.IndexRecord]s. +// Preference order for distribution: +// 1. distro_cpe qualifier (converted to a [claircore.Distribution]) +// 2. fallback to "distro" qualifier in the form "-" converted to a [claircore.Distribution] +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + var dist *claircore.Distribution + // First try and parse a distro CPE. + if dc := purl.Qualifiers.Map()["distro_cpe"]; dc != "" { + if wf, err := cpe.Unbind(dc); err == nil { + if d, err := cpeToDist(wf); err == nil && d != nil { + dist = d + } + } + } + // Fallback to legacy "distro" qualifier. + if dist == nil { + distroQualifier := purl.Qualifiers.Map()["distro"] + parts := strings.SplitN(distroQualifier, "-", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid distro qualifier: %s", distroQualifier) + } + switch parts[0] { + case "sles": + dist = ELDist(parts[1]) + case "opensuse-leap": + dist = leapDist(parts[1]) + default: + return nil, fmt.Errorf("invalid distro name: %s", parts[0]) + } + + } + + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Arch: purl.Qualifiers.Map()["arch"], + Kind: claircore.BINARY, + }, + Distribution: dist, + }, + }, nil +} diff --git a/suse/purl_test.go b/suse/purl_test.go new file mode 100644 index 000000000..d35dcfaec --- /dev/null +++ b/suse/purl_test.go @@ -0,0 +1,160 @@ +package suse + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" + "github.com/quay/claircore/toolkit/types/cpe" +) + +func TestRoundTripIndexRecord(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "opensuse-leap-with-cpe", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "zlib", + Version: "1.2.11-150500.59.68.1", + Arch: "x86_64", + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{ + CPE: cpe.MustUnbind("cpe:2.3:o:opensuse:leap:15.5"), + Name: "openSUSE Leap", + VersionID: "15.5", + Version: "15.5", + DID: "opensuse-leap", + PrettyName: "openSUSE Leap 15.5", + }, + }, + }, + { + name: "sles-fallback-distro", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1-150300.51.1", + Arch: "aarch64", + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{ + Name: "SLES", + VersionID: "12", + Version: "12", + DID: "sles", + PrettyName: "SUSE Linux Enterprise Server 12", + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GenerateRPMPURL: %v", err) + } + t.Logf("generated PURL: %s", p.String()) + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParseRPMPURL: %v", err) + } + if diff := cmp.Diff(got, []*claircore.IndexRecord{tc.ir}, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-got +want):\n%s", diff) + } + }) + } +} + +func TestGeneratePURL(t *testing.T) { + t.Parallel() + ctx := context.Background() + tests := []struct { + name string + ir *claircore.IndexRecord + want packageurl.PackageURL + }{ + { + name: "with-distro-cpe", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "zlib", + Version: "1.2.11-150500.59.68.1", + Arch: "x86_64", + }, + Distribution: &claircore.Distribution{ + CPE: cpe.MustUnbind("cpe:2.3:o:opensuse:leap:15.5"), + Name: "openSUSE Leap", + VersionID: "15.5", + DID: "opensuse", + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: "zlib", + Version: "1.2.11-150500.59.68.1", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "x86_64"}, + {Key: "distro_cpe", Value: "cpe:2.3:o:opensuse:leap:15.5:*:*:*:*:*:*:*"}, + {Key: "distro", Value: "opensuse-15.5"}, + }, + }, + }, + { + name: "fallback-distro-qualifier", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1-150300.51.1", + Arch: "aarch64", + }, + Distribution: &claircore.Distribution{ + Name: "SUSE Linux Enterprise Server", + VersionID: "12", + DID: "suse", + }, + }, + want: packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: "bash", + Version: "5.1-150300.51.1", + Qualifiers: packageurl.Qualifiers{ + {Key: "arch", Value: "aarch64"}, + {Key: "distro", Value: "suse-12"}, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GenerateRPMPURL: %v", err) + } + t.Logf("generated PURL: %s", got.String()) + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Fatalf("purl mismatch (-got +want):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore Distribution field differences for round-trip; only assert package fields. + cmpopts.IgnoreFields(claircore.Distribution{}, "PrettyName", "CPE"), +} diff --git a/test/bisect/main_test.go b/test/bisect/main_test.go index ff054675b..dbe0f1c96 100644 --- a/test/bisect/main_test.go +++ b/test/bisect/main_test.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "hash/fnv" + "log/slog" "net/http" "net/http/httptest" "os" @@ -21,8 +22,6 @@ import ( "text/template" "time" - "github.com/quay/zlog" - "github.com/rs/zerolog" "golang.org/x/sync/errgroup" "github.com/quay/claircore" @@ -217,11 +216,11 @@ func escapeImage(i string) string { // Setup does a grip of test setup work, returning a context that will cancel on // interrupt and a server set up to serve files from "dir". func setup(ctx context.Context, t *testing.T, dir string) (context.Context, *httptest.Server) { - l := zerolog.Nop() + h := slog.DiscardHandler if stderr { - l = zerolog.New(zerolog.NewConsoleWriter()) + h = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{}) } - zlog.Set(&l) + slog.SetDefault(slog.New(h)) for _, v := range []struct { Tmpl **template.Template @@ -243,7 +242,7 @@ func setup(ctx context.Context, t *testing.T, dir string) (context.Context, *htt ctx, done := signal.NotifyContext(ctx, os.Interrupt) t.Cleanup(done) - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } fsrv := http.FileServer(http.Dir(dir)) diff --git a/test/logging.go b/test/logging.go new file mode 100644 index 000000000..50c8bacbe --- /dev/null +++ b/test/logging.go @@ -0,0 +1,148 @@ +package test + +import ( + "context" + "fmt" + "log/slog" + "os" + "path/filepath" + "runtime/debug" + "strings" + "sync" + "testing" + "time" + + "github.com/quay/claircore/toolkit/log" +) + +var ( + // Setup installs the test log handler exactly once. + setup = sync.OnceFunc(func() { + slog.SetDefault(slog.New(new(handler))) + }) + + // Getwd caches [os.Getwd], since it may be called for every [slog.Record]. + getwd = sync.OnceValue(func() string { + dir, err := os.Getwd() + if err != nil { + panic(err) + } + return dir + }) + + // Modname caches the main module name, since it may be needed for every + // [slog.Record]. + modname = sync.OnceValue(func() string { + if info, ok := debug.ReadBuildInfo(); ok { + return info.Main.Path + "/" + } + return "" + }) +) + +type ctxKey struct{} + +var logHandler ctxKey + +var _ slog.Handler = (handler)(nil) + +// DeferredOp is a closure used with [handler] that allows +// [slog.Handler.WithAttrs] and [slog.Handler.WithGroup] to work by deferring +// the call until the concrete [slog.Handler] implementation can be retrieved +// from a [context.Context]. +// +// When a Handler isn't in the Context passed to the Enabled or Handle methods, +// the call is a no-op. +type deferredOp func(slog.Handler) slog.Handler + +// Handler implements [slog.Handler] by extracting a "real" [slog.Handler] from +// the a [context.Context]. +type handler []deferredOp + +// Enabled implements [slog.Handler]. +func (h handler) Enabled(ctx context.Context, l slog.Level) bool { + v := ctx.Value(logHandler) + if v == nil { + return false // ??? + } + lh, ok := v.(slog.Handler) + if !ok { + return false // ??? + } + return lh.Enabled(ctx, l) +} + +// Handle implements [slog.Handler]. +func (h handler) Handle(ctx context.Context, r slog.Record) error { + v := ctx.Value(logHandler) + if v == nil { + return nil // ??? + } + lh, ok := v.(slog.Handler) + if !ok { + return nil // ??? + } + for _, op := range h { + lh = op(lh) + } + if v, ok := ctx.Value(log.AttrsKey).(slog.Value); ok { + r.AddAttrs(v.Group()...) + } + return lh.Handle(ctx, r) +} + +// WithAttrs implements [slog.Handler]. +func (h handler) WithAttrs(attrs []slog.Attr) slog.Handler { + return append(h, func(h slog.Handler) slog.Handler { + return h.WithAttrs(attrs) + }) +} + +// WithGroup implements [slog.Handler]. +func (h handler) WithGroup(name string) slog.Handler { + return append(h, func(h slog.Handler) slog.Handler { + return h.WithGroup(name) + }) +} + +// Logging returns a [context.Context] that's set up to make the default +// [slog.Logger] defer output to the provided [testing.TB] output. +func Logging(t testing.TB, parent ...context.Context) context.Context { + setup() + var ctx context.Context + if len(parent) > 0 { + ctx = parent[0] + } else { + // Don't use the test Context: users should pass that in if that's what + // they want. + ctx = context.Background() + } + start := time.Now() + w := logOutput(t) + h := slog.NewTextHandler(w, &slog.HandlerOptions{ + AddSource: true, + Level: slog.LevelDebug, + ReplaceAttr: func(g []string, a slog.Attr) slog.Attr { + if g != nil { + return a + } + switch a.Key { + case "time": + dur := time.Since(start) + return slog.String("time", "+"+dur.String()) + case "source": + src := a.Value.Any().(*slog.Source) + if src.Function != "" { + return slog.String("source", strings.TrimPrefix(src.Function, modname())) + } + f := src.File + if r, err := filepath.Rel(getwd(), f); err == nil && r != "" { + f = r + } + return slog.String("source", fmt.Sprintf("%s:%d", f, src.Line)) + } + return a + }, + }) + return context.WithValue(ctx, logHandler, h) +} diff --git a/test/logging_go124.go b/test/logging_go124.go new file mode 100644 index 000000000..ceb1dab26 --- /dev/null +++ b/test/logging_go124.go @@ -0,0 +1,36 @@ +//go:build !go1.25 + +package test + +import ( + "bufio" + "io" + "os" + "path/filepath" + "testing" +) + +func logOutput(t testing.TB) io.Writer { + d := t.TempDir() + n := filepath.Join(d, "output") + t.Cleanup(func() { + f, err := os.Open(n) + if err != nil { + t.Error(err) + return + } + defer f.Close() + s := bufio.NewScanner(f) + for s.Scan() { + t.Log(s.Text()) + } + if err := s.Err(); err != nil { + t.Error(err) + } + }) + w, err := os.Create(n) + if err != nil { + t.Fatal(err) + } + return w +} diff --git a/test/logging_go125.go b/test/logging_go125.go new file mode 100644 index 000000000..a90ea43d9 --- /dev/null +++ b/test/logging_go125.go @@ -0,0 +1,12 @@ +//go:build go1.25 + +package test + +import ( + "io" + "testing" +) + +func logOutput(t testing.TB) io.Writer { + return t.Output() +} diff --git a/test/matcher.go b/test/matcher.go index 38777c2e4..fb7f0fbbd 100644 --- a/test/matcher.go +++ b/test/matcher.go @@ -17,7 +17,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "go.uber.org/mock/gomock" "golang.org/x/tools/txtar" @@ -58,9 +57,6 @@ type MatcherGetCall struct { // [Jq]: https://jqlang.github.io/jq/ func RunMatcherTests(ctx context.Context, t *testing.T, dir string, matchers ...driver.Matcher) { t.Helper() - if ctx == nil { - ctx = zlog.Test(context.Background(), t) - } ms, err := filepath.Glob(filepath.Join(dir, "*.txtar")) if err != nil { @@ -73,7 +69,7 @@ func RunMatcherTests(ctx context.Context, t *testing.T, dir string, matchers ... n := filepath.Base(m) n = strings.TrimSuffix(n, filepath.Ext(n)) t.Run(n, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := Logging(t, ctx) t.Logf("loading txtar: %#q", m) ar, err := txtar.ParseFile(m) diff --git a/test/packagescanner.go b/test/packagescanner.go index 88dc64dc1..23bfc6c2c 100644 --- a/test/packagescanner.go +++ b/test/packagescanner.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/indexer" @@ -46,7 +45,7 @@ func (tc ScannerTestcase) Digest() claircore.Digest { func (tc ScannerTestcase) Run(ctx context.Context) func(*testing.T) { sort.Slice(tc.Want, pkgSort(tc.Want)) return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := Logging(t, ctx) l := tc.getLayer(ctx, t) got, err := tc.Scanner.Scan(ctx, l) @@ -69,7 +68,7 @@ func (tc ScannerTestcase) Run(ctx context.Context) func(*testing.T) { func (tc ScannerTestcase) RunSubset(ctx context.Context, n int) func(*testing.T) { sort.Slice(tc.Want, pkgSort(tc.Want)) return func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := Logging(t, ctx) l := tc.getLayer(ctx, t) got, err := tc.Scanner.Scan(ctx, l) diff --git a/test/periodic/enricher_test.go b/test/periodic/enricher_test.go index b1f470161..69b4b19db 100644 --- a/test/periodic/enricher_test.go +++ b/test/periodic/enricher_test.go @@ -6,14 +6,13 @@ import ( "testing" "time" - "github.com/quay/zlog" - "github.com/quay/claircore/enricher/cvss" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" ) func TestCVSS(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) e := &cvss.Enricher{} err := e.Configure(ctx, func(any) error { return nil }, pkgClient) if err != nil { diff --git a/test/periodic/rpm_test.go b/test/periodic/rpm_test.go index 8f1e12254..ed1be28c3 100644 --- a/test/periodic/rpm_test.go +++ b/test/periodic/rpm_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/rhel" @@ -133,7 +132,7 @@ func (doc hydraDoc) Run(dir string) func(*testing.T) { } return func(t *testing.T) { t.Parallel() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) try := 1 Retry: diff --git a/test/periodic/updater_test.go b/test/periodic/updater_test.go index fb6b64849..f74019644 100644 --- a/test/periodic/updater_test.go +++ b/test/periodic/updater_test.go @@ -7,8 +7,6 @@ import ( "time" "github.com/quay/claircore" - "github.com/quay/zlog" - "github.com/quay/claircore/alpine" "github.com/quay/claircore/aws" "github.com/quay/claircore/debian" @@ -17,6 +15,7 @@ import ( "github.com/quay/claircore/photon" "github.com/quay/claircore/rhel/vex" "github.com/quay/claircore/suse" + "github.com/quay/claircore/test" "github.com/quay/claircore/ubuntu" "github.com/quay/claircore/updater/osv" ) @@ -27,16 +26,16 @@ func noopConfigure(_ any) error { } func TestAWS(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) set, err := aws.UpdaterSet(ctx) if err != nil { t.Fatal() } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestAlpine(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) fac, err := alpine.NewFactory(ctx) if err != nil { t.Fatal() @@ -49,11 +48,11 @@ func TestAlpine(t *testing.T) { if err != nil { t.Fatal(err) } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestDebian(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) fac, err := debian.NewFactory(ctx) if err != nil { t.Fatal() @@ -66,29 +65,29 @@ func TestDebian(t *testing.T) { if err != nil { t.Fatal(err) } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestOracle(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) set, err := oracle.UpdaterSet(ctx) if err != nil { t.Fatal(err) } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestPhoton(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) set, err := photon.UpdaterSet(ctx) if err != nil { t.Fatal(err) } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestRHELVEX(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) fac := new(vex.Factory) err := fac.Configure(ctx, noopConfigure, pkgClient) if err != nil { @@ -98,11 +97,11 @@ func TestRHELVEX(t *testing.T) { if err != nil { t.Fatal(err) } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestSUSE(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) fac := new(suse.Factory) err := fac.Configure(ctx, noopConfigure, pkgClient) if err != nil { @@ -113,11 +112,11 @@ func TestSUSE(t *testing.T) { if err != nil { t.Fatal() } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestUbuntu(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) fac, err := ubuntu.NewFactory(ctx) if err != nil { t.Fatal(err) @@ -130,11 +129,11 @@ func TestUbuntu(t *testing.T) { if err != nil { t.Fatal(err) } - runUpdaterSet(ctx, t, set) + runUpdaterSet(t, set) } func TestOSV(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) fac := &osv.Factory{} if err := fac.Configure(ctx, noopConfigure, pkgClient); err != nil { t.Fatal(err) @@ -143,14 +142,14 @@ func TestOSV(t *testing.T) { if err != nil { t.Fatal(err) } - runUpdaterSet(ctx, t, us) + runUpdaterSet(t, us) } -func runUpdaterSet(ctx context.Context, t *testing.T, set driver.UpdaterSet) { +func runUpdaterSet(t *testing.T, set driver.UpdaterSet) { t.Helper() for _, u := range set.Updaters() { t.Run(u.Name(), func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) if cfg, ok := u.(driver.Configurable); ok { err := cfg.Configure(ctx, noopConfigure, pkgClient) if err != nil { diff --git a/test/rhel/rhcc_matcher_integration_test.go b/test/rhel/rhcc_matcher_integration_test.go index acd64a224..5cafa6bf0 100644 --- a/test/rhel/rhcc_matcher_integration_test.go +++ b/test/rhel/rhcc_matcher_integration_test.go @@ -1,7 +1,6 @@ package rhel import ( - "context" "encoding/json" "fmt" "io" @@ -9,8 +8,6 @@ import ( "path/filepath" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/datastore/postgres" match_engine "github.com/quay/claircore/internal/matcher" @@ -19,6 +16,7 @@ import ( "github.com/quay/claircore/pkg/ctxlock/v2" "github.com/quay/claircore/rhel/rhcc" "github.com/quay/claircore/rhel/vex" + "github.com/quay/claircore/test" "github.com/quay/claircore/test/integration" testpostgres "github.com/quay/claircore/test/postgres" ) @@ -67,7 +65,7 @@ func TestMatcherIntegration(t *testing.T) { } integration.NeedDB(t) - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) pool := testpostgres.TestMatcherDB(ctx, t) store := postgres.NewMatcherStore(pool) locks, err := ctxlock.New(ctx, pool) diff --git a/test/rpmtest/archive.go b/test/rpmtest/archive.go index 85b442e9f..03ed6ced8 100644 --- a/test/rpmtest/archive.go +++ b/test/rpmtest/archive.go @@ -16,7 +16,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/quay/zlog" "golang.org/x/tools/txtar" "github.com/quay/claircore" @@ -209,7 +208,7 @@ func (a *Archive) Tests( arches[img.Archtecture] = struct{}{} t.Run(img.Archtecture, func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) m, err := a.Manifest(img.ID) if err != nil { t.Fatal(err) diff --git a/toolkit/events/doc.go b/toolkit/events/doc.go new file mode 100644 index 000000000..c57f6c8d7 --- /dev/null +++ b/toolkit/events/doc.go @@ -0,0 +1,2 @@ +// Package events is a small event logging system. +package events diff --git a/toolkit/events/events.go b/toolkit/events/events.go index df4b7182f..3f6ab9fbc 100644 --- a/toolkit/events/events.go +++ b/toolkit/events/events.go @@ -1,68 +1,36 @@ -// Package events is a small event logging system. package events import ( "context" "errors" "fmt" - "runtime" - "sync" ) // Group is a grouping of log events going to a common Sink. -type Group struct { - mu sync.Mutex // Protects errs - sink Sink - name string - errs []error -} +// +// Deprecated: This was never used. +type Group struct{} // NewGroup creates a new Group "name" writing to Sink "sink". // // The passed Context is only used for the duration of the NewGroup call. -func NewGroup(ctx context.Context, sink Sink, name string) (*Group, error) { - if err := sink.StartGroup(ctx, name); err != nil { - return nil, err - } - g := &Group{ - sink: sink, - name: name, - } - _, file, line, _ := runtime.Caller(1) - runtime.SetFinalizer(g, func(g *Group) { - panic(fmt.Sprintf("%s:%d: Group not finished", file, line)) - }) - return g, nil +// +// Deprecated: This was never used. +func NewGroup(_ context.Context, _ Sink, _ string) (*Group, error) { + return nil, fmt.Errorf(`events.Group API: NewGroup: %w`, errors.ErrUnsupported) } // Finish signals to the underlying sink that this group is done and reports any // errors accumulated by derived Log objects. -func (g *Group) Finish(ctx context.Context) error { - runtime.SetFinalizer(g, nil) - if err := g.sink.FinishGroup(ctx, g.name); err != nil { - g.pushErr(err) - } - g.mu.Lock() - defer g.mu.Unlock() - if err := errors.Join(g.errs...); err != nil { - return fmt.Errorf("events: error(s) while emitting logs:\n%w", err) - } - return nil -} - -func (g *Group) event(topic string, ev Event) { - if err := g.sink.Event(g.name, topic, ev); err != nil { - g.pushErr(err) - } -} - -func (g *Group) pushErr(err error) { - g.mu.Lock() - g.errs = append(g.errs, err) - g.mu.Unlock() +// +// Deprecated: This was never used. +func (g *Group) Finish(_ context.Context) error { + return fmt.Errorf(`events.Group API: Group.Finish: %w`, errors.ErrUnsupported) } // Log is the facade that "user" code should expect. +// +// Deprecated: This was never used. type Log interface { Printf(format string, v ...interface{}) Errorf(format string, v ...interface{}) @@ -70,16 +38,15 @@ type Log interface { Finish() } -type opaque struct{} - -var ctxKey = (*opaque)(nil) - // WithGroup returns a Context with the provided Group embedded. // // Functions further down the call stack can derive Log interfaces with // FromContext. -func WithGroup(ctx context.Context, g *Group) context.Context { - return context.WithValue(ctx, ctxKey, g) +// +// Deprecated: This was never used, and will not return a child +// [context.Context]. +func WithGroup(ctx context.Context, _ *Group) context.Context { + return ctx } // FromContext returns a Log implementation grouping messages under the provided @@ -87,21 +54,22 @@ func WithGroup(ctx context.Context, g *Group) context.Context { // // The returned implementation may be all no-op methods, so callers should avoid // logging "expensive" data. -func FromContext(ctx context.Context, topic string) Log { - g := ctx.Value(ctxKey).(*Group) - if g == nil { - return noop{} - } - l := log{ - g: g, - topic: topic, - } - g.sink.Topic(topic) - l.buf.Grow(4 * 1024) // Guess - return &l +// +// Deprecated: This was never used, and will return a no-op implementation. +func FromContext(_ context.Context, _ string) Log { + return noop{} } +// Noop is an implementer of Log that does nothing. +type noop struct{} + +func (noop) Printf(_ string, _ ...interface{}) {} +func (noop) Errorf(_ string, _ ...interface{}) {} +func (noop) Finish() {} + // Sink is the interface that event sinks must implement. +// +// Deprecated: This was never used. type Sink interface { // StartGroup is called when a new group is created. The Context should only // be used for the duration of the StartGroup call. @@ -118,6 +86,8 @@ type Sink interface { } // Event is the event information delivered to a Sink. +// +// Deprecated: This was never used. type Event struct { _key struct{} Message string diff --git a/toolkit/events/log.go b/toolkit/events/log.go deleted file mode 100644 index 41bcb2e71..000000000 --- a/toolkit/events/log.go +++ /dev/null @@ -1,60 +0,0 @@ -package events - -import ( - "bytes" - "fmt" -) - -// Log implements Log by writing prints to a strings.Builder. -// -// Using a Builder should prevent extra copying. The log "lines" are written -// with invalid UTF-8 sequences between them, which lets the resulting giant -// string be split easily with very little chance of mangling inputs. -type log struct { - g *Group - buf bytes.Buffer - topic string -} - -func (l *log) Printf(format string, v ...interface{}) { - if format == "" { - return - } - l.msg(fmt.Sprintf(format, v...), false) -} - -func (l *log) Errorf(format string, v ...interface{}) { - if format == "" { - return - } - l.msg(fmt.Sprintf(format, v...), true) -} - -func (l *log) msg(msg string, err bool) { - if err { - l.buf.WriteByte('\xFF') - } - l.buf.WriteString(msg) - l.buf.WriteByte('\x00') -} - -func (l *log) Finish() { - b := l.buf.Bytes() - for len(b) != 0 { - i := bytes.IndexByte(b, '\x00') - var m []byte - m, b = b[:i], b[i+1:] - err := m[0] == '\xFF' - if err { - m = m[1:] - } - l.g.event(l.topic, Event{Error: err, Message: string(m)}) - } -} - -// Noop is an implementer of Log that does nothing. -type noop struct{} - -func (noop) Printf(_ string, _ ...interface{}) {} -func (noop) Errorf(_ string, _ ...interface{}) {} -func (noop) Finish() {} diff --git a/toolkit/events/log_test.go b/toolkit/events/log_test.go deleted file mode 100644 index 64a0ec9bf..000000000 --- a/toolkit/events/log_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package events - -import ( - "context" - "errors" - "io" - "testing" -) - -type testSink struct{ *testing.T } - -var _ Sink = (*testSink)(nil) - -func (s *testSink) StartGroup(ctx context.Context, group string) error { - s.Logf("group start: %s", group) - return nil -} - -func (s *testSink) Topic(t string) { - s.Logf("topic: %s", t) -} - -func (s *testSink) Event(group, topic string, ev Event) error { - s.Logf("%s/%s: err?%v %s", group, topic, ev.Error, ev.Message) - return nil -} - -func (s *testSink) FinishGroup(ctx context.Context, group string) error { - s.Logf("group finish: %s", group) - return nil -} - -func TestLog(t *testing.T) { - ctx := context.Background() - sink := &testSink{T: t} - g, err := NewGroup(ctx, sink, t.Name()) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := g.Finish(ctx); err != nil { - t.Error(err) - } - }() - - ctx = WithGroup(ctx, g) - l := FromContext(ctx, "topic") - defer l.Finish() - l.Printf("") - l.Errorf("") - for i := 0; i < 10; i++ { - l.Printf("hello, %d", i) - l.Errorf("hello, %d", i) - } -} - -type errSink struct { - start error - event error - finish error -} - -var _ Sink = (*errSink)(nil) - -func (s *errSink) StartGroup(ctx context.Context, group string) (err error) { - err, s.start = s.start, nil - return err -} - -func (s *errSink) Topic(t string) {} - -func (s *errSink) Event(group, topic string, ev Event) (err error) { - err, s.event = s.event, nil - return err -} - -func (s *errSink) FinishGroup(ctx context.Context, group string) (err error) { - err, s.finish = s.finish, nil - return err -} - -func TestLogError(t *testing.T) { - startErr := errors.New("start") - eventErr := errors.New("event") - finishErr := errors.New("finish") - ctx := context.Background() - sink := &errSink{ - start: startErr, - event: eventErr, - finish: finishErr, - } - _, err := NewGroup(ctx, sink, t.Name()) - if !errors.Is(err, startErr) { - t.Errorf("got: %v, want: %v", err, startErr) - } - g, err := NewGroup(ctx, sink, t.Name()) - if err != nil { - t.Fatal(err) - } - - ctx = WithGroup(ctx, g) - l := FromContext(ctx, "topic") - l.Printf("hello, test") - l.Finish() - - err = g.Finish(ctx) - if got, want := err, eventErr; !errors.Is(got, want) { - t.Errorf("got: %v, want: %v", got, want) - } - if got, want := err, finishErr; !errors.Is(got, want) { - t.Errorf("got: %v, want: %v", got, want) - } - if got, want := err, io.EOF; errors.Is(got, want) { - t.Errorf("got: %v, want: %v", got, want) - } - t.Log(err) -} diff --git a/toolkit/events/slog.go b/toolkit/events/slog.go new file mode 100644 index 000000000..cb125d365 --- /dev/null +++ b/toolkit/events/slog.go @@ -0,0 +1,43 @@ +package events + +import ( + "context" + "log/slog" + "sync" +) + +// Ctxkey is a Context key type. +// +// This is unexported so that other packages cannot construct these values. +type ctxkey int + +const ( + _ ctxkey = iota + + // HandlerKey is the value used in [WithHandler]/[Logger]. + handlerKey +) + +// WithHandler sets the provided [slog.Handler] to be used when calls to +// [Logger] are made with the returned [context.Context]. +func WithHandler(ctx context.Context, h slog.Handler) context.Context { + return context.WithValue(ctx, handlerKey, h) +} + +// DiscardLogger returns a [slog.Logger] configured to discard all records. +var discardLogger = sync.OnceValue(func() *slog.Logger { + return slog.New(slog.DiscardHandler) +}) + +// Logger returns a [slog.Logger] for recording current "request" (whatever that +// means in context) events. +// +// The returned Logger may simply ignore all events. See [WithHandler] for +// setting the backing [slog.Handler]. +func Logger(ctx context.Context) *slog.Logger { + v := ctx.Value(handlerKey) + if v == nil { + return discardLogger() + } + return slog.New(v.(slog.Handler)) +} diff --git a/toolkit/events/slog_test.go b/toolkit/events/slog_test.go new file mode 100644 index 000000000..a299c4f97 --- /dev/null +++ b/toolkit/events/slog_test.go @@ -0,0 +1,34 @@ +package events + +import ( + "bytes" + "context" + "log/slog" + "testing" +) + +func TestSlog(t *testing.T) { + t.Run("Handler", func(t *testing.T) { + var buf bytes.Buffer + want := slog.NewTextHandler(&buf, nil) + + ctx := WithHandler(context.Background(), want) + h := Logger(ctx).Handler() + got, ok := h.(*slog.TextHandler) + if !ok { + t.Errorf("expected a *slog.TextHandler, got: %T", h) + } + if got != want { + t.Errorf("unexpected Hander: got: %p, want: %p", got, want) + } + }) + + t.Run("Noop", func(t *testing.T) { + want := discardLogger() + ctx := context.Background() + got := Logger(ctx) + if got != want { + t.Errorf("unexpected Logger: got: %p, want: %p", got, want) + } + }) +} diff --git a/toolkit/go.mod b/toolkit/go.mod index e5cc1f27f..d0b8f2ee2 100644 --- a/toolkit/go.mod +++ b/toolkit/go.mod @@ -1,5 +1,5 @@ module github.com/quay/claircore/toolkit -go 1.22 +go 1.24 require github.com/google/go-cmp v0.7.0 diff --git a/ubuntu/distributionscanner.go b/ubuntu/distributionscanner.go index 95ec100f1..4dc513017 100644 --- a/ubuntu/distributionscanner.go +++ b/ubuntu/distributionscanner.go @@ -7,11 +7,10 @@ import ( "fmt" "io" "io/fs" + "log/slog" "runtime/trace" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -45,12 +44,8 @@ func (*DistributionScanner) Kind() string { return scannerKind } // Scan implements [indexer.DistributionScanner]. func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) { defer trace.StartRegion(ctx, "Scanner.Scan").End() - ctx = zlog.ContextWithValues(ctx, - "component", "ubuntu/DistributionScanner.Scan", - "version", ds.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { return nil, fmt.Errorf("ubuntu: unable to open layer: %w", err) diff --git a/ubuntu/matcher.go b/ubuntu/matcher.go index ed6793ad0..ee0f3dece 100644 --- a/ubuntu/matcher.go +++ b/ubuntu/matcher.go @@ -40,7 +40,7 @@ func (*Matcher) Query() []driver.MatchConstraint { return []driver.MatchConstraint{ driver.DistributionDID, driver.DistributionName, - driver.DistributionVersion, + driver.DistributionVersionID, } } diff --git a/ubuntu/purl.go b/ubuntu/purl.go new file mode 100644 index 000000000..f88c343c5 --- /dev/null +++ b/ubuntu/purl.go @@ -0,0 +1,65 @@ +package ubuntu + +import ( + "context" + "fmt" + "strings" + + "github.com/package-url/packageurl-go" + + "github.com/quay/claircore" +) + +const ( + // PURLType is the type of package URL for Ubuntu packages. + PURLType = "deb" + // PURLNamespace is the namespace of Ubuntu packages. + PURLNamespace = "ubuntu" + // PURLDistroQualifier is the qualifier key for the distribution. + PURLDistroQualifier = "distro" +) + +// GeneratePURL generates a PURL for a Ubuntu package in the format: +// pkg:deb/ubuntu/@?arch=&distro=ubuntu- +func GeneratePURL(ctx context.Context, r *claircore.IndexRecord) (packageurl.PackageURL, error) { + var distro string + if r.Distribution != nil { + // This completely ignores the version code name e.g. "ubuntu-24.04". + distro = "ubuntu-" + r.Distribution.VersionID + } + return packageurl.PackageURL{ + Type: PURLType, + Namespace: PURLNamespace, + Name: r.Package.Name, + Version: r.Package.Version, + Qualifiers: packageurl.QualifiersFromMap(map[string]string{ + "arch": r.Package.Arch, + PURLDistroQualifier: distro, + }), + }, nil +} + +// ParsePURL parses a PURL for a Ubuntu package into a list of IndexRecords. +func ParsePURL(ctx context.Context, purl packageurl.PackageURL) ([]*claircore.IndexRecord, error) { + dq := purl.Qualifiers.Map()[PURLDistroQualifier] + distroParts := strings.SplitN(dq, "-", 2) + if len(distroParts) != 2 { + return nil, fmt.Errorf("invalid distro PURL: %s", dq) + } + return []*claircore.IndexRecord{ + { + Package: &claircore.Package{ + Name: purl.Name, + Version: purl.Version, + Arch: purl.Qualifiers.Map()["arch"], + Kind: claircore.BINARY, + }, + Distribution: &claircore.Distribution{ + Name: "Ubuntu", + DID: "ubuntu", + VersionID: distroParts[1], + PrettyName: "Ubuntu " + distroParts[1], + }, + }, + }, nil +} diff --git a/ubuntu/purl_test.go b/ubuntu/purl_test.go new file mode 100644 index 000000000..e926b5127 --- /dev/null +++ b/ubuntu/purl_test.go @@ -0,0 +1,68 @@ +package ubuntu + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/quay/claircore" +) + +func TestRoundTripIndexRecordDebian(t *testing.T) { + t.Parallel() + ctx := context.Background() + + tests := []struct { + name string + ir *claircore.IndexRecord + }{ + { + name: "basic", + ir: &claircore.IndexRecord{ + Package: &claircore.Package{ + Name: "bash", + Version: "5.1.8-6", + Arch: "x86_64", + Kind: claircore.BINARY, + PackageDB: "deb:/var/lib/dpkg/status", + }, + Distribution: &claircore.Distribution{ + Name: "Ubuntu", + DID: "ubuntu", + VersionID: "24.04", + PrettyName: "Ubuntu 24.04", + VersionCodeName: "noble", + Version: "24.04 (Noble)", + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + p, err := GeneratePURL(ctx, tc.ir) + if err != nil { + t.Fatalf("GeneratePURL: %v", err) + } + fmt.Println(p.String()) + got, err := ParsePURL(ctx, p) + if err != nil { + t.Fatalf("ParsePURL: %v", err) + } + if diff := cmp.Diff([]*claircore.IndexRecord{tc.ir}, got, purlCmp); diff != "" { + t.Fatalf("round-trip mismatch (-want +got):\n%s", diff) + } + }) + } +} + +var purlCmp = cmp.Options{ + // Ignore PackageDB and Filepath as they are not currently used in the matching. + cmpopts.IgnoreFields(claircore.Package{}, "PackageDB", "Filepath"), + // The distribution that we can decipher from the PURL only has the fields that + // are used in the matching; DID, Name and VersionID. + cmpopts.IgnoreFields(claircore.Distribution{}, "VersionCodeName", "Version", "PrettyName"), +} diff --git a/ubuntu/updater.go b/ubuntu/updater.go index 8060ecf33..726c9cea6 100644 --- a/ubuntu/updater.go +++ b/ubuntu/updater.go @@ -6,11 +6,11 @@ import ( "encoding/xml" "fmt" "io" + "log/slog" "net/http" "net/url" "github.com/quay/goval-parser/oval" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/internal/xmlutil" @@ -43,9 +43,6 @@ func (u *updater) Name() string { // Configure implements [driver.Configurable]. func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, - "component", "ubuntu/Updater.Configure", - "updater", u.Name()) u.c = c var cfg UpdaterConfig @@ -58,8 +55,7 @@ func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c * return err } u.url = cfg.URL - zlog.Info(ctx). - Msg("configured database URL") + slog.InfoContext(ctx, "configured database URL") } if cfg.UseBzip2 != nil { u.useBzip2 = *cfg.UseBzip2 @@ -79,9 +75,7 @@ type UpdaterConfig struct { // Fetch implements [driver.Updater]. func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "ubuntu/Updater.Fetch", - "database", u.url) + log := slog.With("database", u.url) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.url, nil) if err != nil { @@ -101,7 +95,7 @@ func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io switch resp.StatusCode { case http.StatusOK: if fp := string(fingerprint); fp == "" || fp != resp.Header.Get("etag") { - zlog.Info(ctx).Msg("fetching latest oval database") + log.InfoContext(ctx, "fetching latest oval database") break } fallthrough @@ -120,7 +114,7 @@ func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io defer func() { if !success { if err := f.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close spool") + log.WarnContext(ctx, "unable to close spool", "reason", err) } } }() @@ -136,15 +130,13 @@ func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io } success = true - zlog.Info(ctx).Msg("fetched latest oval database successfully") + log.InfoContext(ctx, "fetched latest oval database successfully") return f, driver.Fingerprint(fp), err } // Parse implements [driver.Updater]. func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "ubuntu/Updater.Parse") - zlog.Info(ctx).Msg("starting parse") + slog.InfoContext(ctx, "starting parse") defer r.Close() root := oval.Root{} dec := xml.NewDecoder(r) @@ -152,7 +144,7 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln if err := dec.Decode(&root); err != nil { return nil, fmt.Errorf("ubuntu: unable to decode OVAL document: %w", err) } - zlog.Debug(ctx).Msg("xml decoded") + slog.DebugContext(ctx, "xml decoded") nameLookupFunc := func(def oval.Definition, name *oval.DpkgName) []string { // if the dpkginfo_object>name field has a var_ref it indicates @@ -167,7 +159,7 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln } _, i, err := root.Variables.Lookup(name.Ref) if err != nil { - zlog.Error(ctx).Err(err).Msg("could not lookup variable id") + slog.WarnContext(ctx, "could not lookup variable id", "reason", err) return ns } consts := root.Variables.ConstantVariables[i] diff --git a/ubuntu/updaterset.go b/ubuntu/updaterset.go index 42ffd4ed5..da9e59282 100644 --- a/ubuntu/updaterset.go +++ b/ubuntu/updaterset.go @@ -4,13 +4,13 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/http" "net/url" "path" "strings" "sync" - "github.com/quay/zlog" "golang.org/x/sync/errgroup" "github.com/quay/claircore" @@ -67,8 +67,6 @@ type FactoryConfig struct { // Configure implements [driver.Configurable]. func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, - "component", "ubuntu/Factory.Configure") var cfg FactoryConfig if err := cf(&cfg); err != nil { return err @@ -84,8 +82,7 @@ func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c if err != nil { return fmt.Errorf("ubuntu: unable to parse provided URL: %w", err) } - zlog.Info(ctx). - Msg("configured URL") + slog.InfoContext(ctx, "configured URL") } n := defaultName if cfg.Name != "" { @@ -106,9 +103,6 @@ func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c // UpdaterSet implements [driver.UpdaterSetFactory] func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "ubuntu/Factory.UpdaterSet") - set := driver.NewUpdaterSet() req, err := http.NewRequestWithContext(ctx, http.MethodGet, f.api, nil) if err != nil { @@ -146,7 +140,7 @@ func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { e := &series.Entries[i] mkDist(e.Version, e.Name) if !e.Active { - zlog.Debug(ctx).Str("release", e.Name).Msg("release not active") + slog.DebugContext(ctx, "release not active", "release", e.Name) continue } select { @@ -175,10 +169,9 @@ func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { switch res.StatusCode { case http.StatusOK: case http.StatusNotFound: - zlog.Debug(ctx). - Str("name", e.Name). - Str("version", e.Version). - Msg("OVAL database missing, skipping") + slog.DebugContext(ctx, "OVAL database missing, skipping", + "name", e.Name, + "version", e.Version) continue default: return fmt.Errorf("ubuntu: unexpected status requesting %q: %q", url, res.Status) @@ -210,7 +203,7 @@ func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { } if len(f.force) != 0 { - zlog.Info(ctx).Msg("configuring manually specified updaters") + slog.InfoContext(ctx, "configuring manually specified updaters") ns := make([]string, 0, len(f.force)) for _, p := range f.force { u := &updater{ @@ -221,12 +214,12 @@ func (f *Factory) UpdaterSet(ctx context.Context) (driver.UpdaterSet, error) { } if err := set.Add(u); err != nil { // Already exists, skip. - zlog.Debug(ctx).Err(err).Msg("skipping updater") + slog.DebugContext(ctx, "skipping updater", "reason", err) continue } ns = append(ns, u.Name()) } - zlog.Info(ctx).Strs("updaters", ns).Msg("added specified updaters") + slog.InfoContext(ctx, "added specified updaters", "updaters", ns) } return set, nil diff --git a/updater/offline_test.go b/updater/offline_test.go index 0d1ed83ee..effb69d20 100644 --- a/updater/offline_test.go +++ b/updater/offline_test.go @@ -3,7 +3,6 @@ package updater import ( "archive/zip" "bytes" - "context" "encoding/json" "io" "net/http" @@ -13,16 +12,15 @@ import ( "path/filepath" "testing" - "github.com/quay/zlog" "go.uber.org/mock/gomock" + "github.com/quay/claircore/test" mock_updater "github.com/quay/claircore/test/mock/updater" mock_driver "github.com/quay/claircore/test/mock/updater/driver/v1" driver "github.com/quay/claircore/updater/driver/v1" ) func TestOffline(t *testing.T) { - ctx := context.Background() vs := &driver.ParsedVulnerabilities{ Updater: t.Name(), Vulnerability: []driver.Vulnerability{{}}, @@ -40,7 +38,7 @@ func TestOffline(t *testing.T) { defer spool.Close() t.Run("Fetch", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) ctl := gomock.NewController(t) n := path.Dir(t.Name()) @@ -76,7 +74,7 @@ func TestOffline(t *testing.T) { }) t.Run("Parse", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) ctl := gomock.NewController(t) n := path.Dir(t.Name()) @@ -124,7 +122,7 @@ func TestOffline(t *testing.T) { }) t.Run("Prev", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) ctl := gomock.NewController(t) n := path.Dir(t.Name()) if _, err := spool.Seek(0, io.SeekStart); err != nil { diff --git a/updater/offline_v1.go b/updater/offline_v1.go index 7f8d03a40..1305336bd 100644 --- a/updater/offline_v1.go +++ b/updater/offline_v1.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "os" "path" "runtime" @@ -15,7 +16,6 @@ import ( "time" "github.com/google/uuid" - "github.com/quay/zlog" "golang.org/x/sync/errgroup" "github.com/quay/claircore/updater/driver/v1" @@ -41,7 +41,7 @@ func (u *Updater) importV1(ctx context.Context, sys fs.FS) error { } defer func() { if err := f.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("error closing config.json") + slog.WarnContext(ctx, "error closing config.json", "reason", err) } }() if err := json.NewDecoder(f).Decode(&cfg); err != nil { @@ -52,9 +52,7 @@ func (u *Updater) importV1(ctx context.Context, sys fs.FS) error { if err != nil { return err } - zlog.Info(ctx). - Int("ct", len(us)). - Msg("got updaters") + slog.InfoContext(ctx, "got updaters", "count", len(us)) spool, err := os.CreateTemp(tmpDir, tmpPattern) if err != nil { @@ -63,22 +61,20 @@ func (u *Updater) importV1(ctx context.Context, sys fs.FS) error { defer func() { spoolname := spool.Name() if err := os.Remove(spoolname); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to remove spool file") + slog.WarnContext(ctx, "unable to remove spool file", "filename", spoolname, "reason", err) } if err := spool.Close(); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("error closing spool file") + slog.WarnContext(ctx, "error closing spool file", "filename", spoolname, "reason", err) } }() for _, upd := range us { name := upd.Name - ctx := zlog.ContextWithValues(ctx, "updater", name) fi, err := fs.Stat(sys, name) switch { case errors.Is(err, nil): case errors.Is(err, fs.ErrNotExist): - zlog.Info(ctx). - Msg("no import, skipping") + slog.InfoContext(ctx, "no import, skipping", "updater", name) continue default: return err @@ -128,13 +124,13 @@ func (u *Updater) importV1(ctx context.Context, sys fs.FS) error { if err := u.store.UpdateVulnerabilities(ctx, ref, name, fp, res.Vulnerabilities); err != nil { return err } - zlog.Info(ctx).Stringer("ref", ref).Msg("updated vulnerabilites") + slog.InfoContext(ctx, "updated vulnerabilites", "ref", ref) } if len(res.Enrichments) != 0 { if err := u.store.UpdateEnrichments(ctx, ref, name, fp, res.Enrichments); err != nil { return err } - zlog.Info(ctx).Stringer("ref", ref).Msg("updated enrichments") + slog.InfoContext(ctx, "updated enrichments", "ref", ref) } } @@ -161,9 +157,7 @@ func (u *Updater) exportV1(ctx context.Context, z *zip.Writer, prev fs.FS) error if err != nil { return err } - zlog.Info(ctx). - Int("ct", len(us)). - Msg("got updaters") + slog.InfoContext(ctx, "got updaters", "count", len(us)) // WaitGroup for the worker goroutines. var wg sync.WaitGroup @@ -218,7 +212,7 @@ func (u *Updater) exportV1(ctx context.Context, z *zip.Writer, prev fs.FS) error defer wg.Done() for upd := range feed { name := upd.Name - ctx := zlog.ContextWithValues(ctx, "updater", name) + log := slog.With("updater", name) // Zips have to be written serially, so we spool the fetcher // output to disk, then seek back to the start so it's ready to // read. @@ -229,29 +223,29 @@ func (u *Updater) exportV1(ctx context.Context, z *zip.Writer, prev fs.FS) error // can't do much to recover from. spool, err := os.CreateTemp(tmpDir, tmpPattern) if err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to create spool file") + log.WarnContext(ctx, "unable to create spool file", "reason", err) continue } spoolname := spool.Name() fp, err := u.fetchOne(ctx, upd, pfps[name], spool) if err != nil { if err := os.Remove(spoolname); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to remove spool file") + log.WarnContext(ctx, "unable to remove spool file", "filename", spoolname, "reason", err) } if err := spool.Close(); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("error closing spool file") + log.WarnContext(ctx, "error closing spool file", "filename", spoolname, "reason", err) } - zlog.Info(ctx).Err(err).Msg("updater error") + log.InfoContext(ctx, "updater error", "reason", err) continue } if _, err := spool.Seek(0, io.SeekStart); err != nil { if err := os.Remove(spoolname); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to remove spool file") + log.WarnContext(ctx, "unable to remove spool file", "filename", spoolname, "reason", err) } if err := spool.Close(); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("error closing spool file") + log.WarnContext(ctx, "error closing spool file", "filename", spoolname, "reason", err) } - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to seek to start") + log.WarnContext(ctx, "unable to seek to start", "filename", spoolname, "reason", err) continue } res <- &result{ @@ -274,10 +268,10 @@ func addUpdater(ctx context.Context, z *zip.Writer, now time.Time, r *result) er defer func() { fn := r.spool.Name() if err := os.Remove(fn); err != nil { - zlog.Warn(ctx).Err(err).Str("file", fn).Msg("unable to remove fetch spool") + slog.WarnContext(ctx, "unable to remove fetch spool", "reason", err, "file", fn) } if err := r.spool.Close(); err != nil { - zlog.Warn(ctx).Err(err).Str("file", fn).Msg("unable to close fetch spool") + slog.WarnContext(ctx, "unable to close fetch spool", "reason", err, "file", fn) } }() n := r.name @@ -327,10 +321,7 @@ func addUpdater(ctx context.Context, z *zip.Writer, now time.Time, r *result) er if _, err := io.Copy(w, r.spool); err != nil { return err } - zlog.Debug(ctx). - Stringer("ref", ref). - Str("name", n). - Msg("wrote out fetch results") + slog.DebugContext(ctx, "wrote out fetch results", "ref", ref, "name", n) return nil } diff --git a/updater/osv/osv.go b/updater/osv/osv.go index 1d7333c75..d6f4bf4aa 100644 --- a/updater/osv/osv.go +++ b/updater/osv/osv.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "net/http" "net/http/httputil" "net/url" @@ -20,7 +21,6 @@ import ( "github.com/Masterminds/semver" "github.com/quay/claircore/toolkit/types/cvss" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" @@ -74,7 +74,6 @@ type FactoryConfig struct { // Configure implements driver.Configurable. func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, "component", "updater/osv/factory.Configure") var err error f.c = c @@ -100,12 +99,11 @@ func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c } } - zlog.Debug(ctx).Msg("loaded incoming config") + slog.DebugContext(ctx, "loaded incoming config") return nil } func (f *Factory) UpdaterSet(ctx context.Context) (s driver.UpdaterSet, err error) { - ctx = zlog.ContextWithValues(ctx, "component", "updater/osv/factory.UpdaterSet") s = driver.NewUpdaterSet() if f.root == nil || f.c == nil { return s, errors.New("osv: factory not configured") // Purposely return an unhandleable error. @@ -117,10 +115,9 @@ func (f *Factory) UpdaterSet(ctx context.Context) (s driver.UpdaterSet, err erro defer func() { // This is an info print so operators can compare their allow list, // if need be. - zlog.Info(ctx). - Strs("ecosystems", stats.ecosystems). - Strs("skipped", stats.skipped). - Msg("ecosystems stats") + slog.InfoContext(ctx, "ecosystems stats", + "ecosystems", stats.ecosystems, + "skipped", stats.skipped) }() uri := *f.root @@ -156,9 +153,7 @@ func (f *Factory) UpdaterSet(ctx context.Context) (s driver.UpdaterSet, err erro } seen[e] = struct{}{} if _, ok := ignore[e]; ok { - zlog.Debug(ctx). - Str("ecosystem", e). - Msg("ignoring ecosystem") + slog.DebugContext(ctx, "ignoring ecosystem", "ecosystem", e) continue } stats.ecosystems = append(stats.ecosystems, e) @@ -170,10 +165,9 @@ func (f *Factory) UpdaterSet(ctx context.Context) (s driver.UpdaterSet, err erro uri := (*f.root).JoinPath(k, "all.zip") up := &updater{name: name, ecosystem: e, c: f.c, uri: uri} if err = s.Add(up); err != nil { - zlog.Error(ctx). - Str("ecosystem", e). - Err(err). - Msg("Failed to add updater to updaterset") + slog.WarnContext(ctx, "failed to add updater to updaterset", + "reason", err, + "ecosystem", e) continue } } @@ -188,9 +182,7 @@ func (f *Factory) UpdaterSet(ctx context.Context) (s driver.UpdaterSet, err erro err = fmt.Errorf("osv: unexpected response from %q: %v (request: %q) (body: %q)", res.Request.URL, res.Status, b, buf) } if err := res.Body.Close(); err != nil { - zlog.Info(ctx). - Err(err). - Msg("error closing ecosystems.txt response body") + slog.InfoContext(ctx, "error closing ecosystems.txt response body", "reason", err) } if err != nil { return s, err @@ -236,7 +228,6 @@ type UpdaterConfig struct { // Configure implements driver.Configurable. func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c *http.Client) error { - ctx = zlog.ContextWithValues(ctx, "component", "updater/osv/updater.Configure") var err error u.c = c @@ -250,28 +241,22 @@ func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c * return err } } - zlog.Debug(ctx).Msg("loaded incoming config") + slog.DebugContext(ctx, "loaded incoming config") return nil } // Fetcher implements driver.Updater. func (u *updater) Fetch(ctx context.Context, fp driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { - ctx = zlog.ContextWithValues(ctx, "component", "updater/osv/updater.Fetch") - out, err := tmp.NewFile("", "osv.fetch.*") if err != nil { return nil, fp, err } defer func() { if _, err := out.Seek(0, io.SeekStart); err != nil { - zlog.Warn(ctx). - Err(err). - Msg("unable to seek file back to start") + slog.WarnContext(ctx, "unable to seek file back to start", "reason", err) } }() - zlog.Debug(ctx). - Str("filename", out.Name()). - Msg("opened temporary file for output") + slog.DebugContext(ctx, "opened temporary file for output", "filename", out.Name()) w := zip.NewWriter(out) defer w.Close() var ct int @@ -282,9 +267,7 @@ func (u *updater) Fetch(ctx context.Context, fp driver.Fingerprint) (io.ReadClos } req.Header.Set(`accept`, `application/zip`) if fp != "" { - zlog.Debug(ctx). - Str("hint", string(fp)). - Msg("using hint") + slog.DebugContext(ctx, "using hint", "hint", string(fp)) req.Header.Set("if-none-match", string(fp)) } @@ -304,26 +287,20 @@ func (u *updater) Fetch(ctx context.Context, fp driver.Fingerprint) (io.ReadClos if err != nil { break } - zlog.Debug(ctx). - Str("name", n). - Msg("wrote zip") + slog.DebugContext(ctx, "wrote zip", "name", n) ct++ case http.StatusNotModified: default: err = fmt.Errorf("osv: unexpected response from %q: %v", res.Request.URL.String(), res.Status) } if err := res.Body.Close(); err != nil { - zlog.Info(ctx). - Err(err). - Msg("error closing advisory zip response body") + slog.InfoContext(ctx, "error closing advisory zip response body", "reason", err) } if err != nil { return nil, fp, err } newEtag := res.Header.Get(`etag`) - zlog.Info(ctx). - Int("count", ct). - Msg("found updates") + slog.InfoContext(ctx, "found updates", "count", ct) if ct == 0 { return nil, fp, driver.Unchanged } @@ -333,11 +310,9 @@ func (u *updater) Fetch(ctx context.Context, fp driver.Fingerprint) (io.ReadClos // Fetcher implements driver.Updater. func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) { - ctx = zlog.ContextWithValues(ctx, "component", "updater/osv/updater.Parse") ra, ok := r.(io.ReaderAt) if !ok { - zlog.Info(ctx). - Msg("spooling to disk") + slog.InfoContext(ctx, "spooling to disk") tf, err := tmp.NewFile("", `osv.parse.spool.*`) if err != nil { return nil, err @@ -388,9 +363,8 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln now := time.Now() ecs := newECS(u.Name()) for _, zf := range z.File { - ctx := zlog.ContextWithValues(ctx, "dumpfile", zf.Name) - zlog.Debug(ctx). - Msg("found file") + log := slog.With("dumpfile", zf.Name) + log.DebugContext(ctx, "found file") r, err := zf.Open() if err != nil { return nil, err @@ -411,7 +385,6 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln var skipped stats var ct int for _, zf := range z.File { - ctx := zlog.ContextWithValues(ctx, "advisory", strings.TrimSuffix(path.Base(zf.Name), ".json")) ct++ var a advisory rc, err := zf.Open() @@ -434,22 +407,18 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln default: } - if err := ecs.Insert(ctx, &skipped, name, &a); err != nil { + log := log.With("advisory", strings.TrimSuffix(path.Base(zf.Name), ".json")) + if err := ecs.Insert(ctx, log, &skipped, name, &a); err != nil { return nil, err } } - zlog.Debug(ctx). - Int("count", ct). - Msg("processed advisories") - zlog.Debug(ctx). - Strs("withdrawn", skipped.Withdrawn). - Strs("unaffected", skipped.Unaffected). - Strs("ignored", skipped.Ignored). - Msg("skipped advisories") - } - zlog.Info(ctx). - Int("count", ecs.Len()). - Msg("found vulnerabilities") + log.DebugContext(ctx, "processed advisories", "count", ct) + log.DebugContext(ctx, "skipped advisories", + "withdrawn", skipped.Withdrawn, + "unaffected", skipped.Unaffected, + "ignored", skipped.Ignored) + } + slog.InfoContext(ctx, "found vulnerabilities", "count", ecs.Len()) return ecs.Finalize(), nil } @@ -502,7 +471,7 @@ type rangeVer struct { ecosystemRange url.Values } -func (e *ecs) Insert(ctx context.Context, skipped *stats, name string, a *advisory) (err error) { +func (e *ecs) Insert(ctx context.Context, log *slog.Logger, skipped *stats, name string, a *advisory) (err error) { if a.GitOnly() { return nil } @@ -537,9 +506,7 @@ func (e *ecs) Insert(ctx context.Context, skipped *stats, name string, a *adviso continue } if err != nil { - zlog.Info(ctx). - Err(err). - Msg("odd cvss mangling result") + log.InfoContext(ctx, "odd cvss mangling result", "reason", err) continue } switch q { @@ -593,9 +560,7 @@ func (e *ecs) Insert(ctx context.Context, skipped *stats, name string, a *adviso // ignore, not going to fetch source. continue default: - zlog.Debug(ctx). - Str("type", r.Type). - Msg("odd range type") + log.DebugContext(ctx, "odd range type", "type", r.Type) } // This does some heavy assumptions about valid inputs. vs := &rangeVer{semverRange: &claircore.Range{}, ecosystemRange: url.Values{}} @@ -635,11 +600,10 @@ func (e *ecs) Insert(ctx context.Context, skipped *stats, name string, a *adviso } case ev.LastAffected != "" && len(af.Versions) != 0: // less than equal to // TODO(hank) Should be able to convert this to a "less than." - zlog.Info(ctx). - Str("which", "last_affected"). - Str("event", ev.LastAffected). - Strs("versions", af.Versions). - Msg("unsure how to interpret event") + log.InfoContext(ctx, "unsure how to interpret event", + "which", "last_affected", + "event", ev.LastAffected, + "versions", af.Versions) case ev.LastAffected != "": // less than equal to // This is semver, so we should be able to calculate the // "next" version: @@ -652,10 +616,9 @@ func (e *ecs) Insert(ctx context.Context, skipped *stats, name string, a *adviso vs.semverRange.Upper.Kind = `semver` vs.semverRange.Upper.V[0] = 65535 case ev.Limit != "": // Something arbitrary - zlog.Info(ctx). - Str("which", "limit"). - Str("event", ev.Limit). - Msg("unsure how to interpret event") + log.InfoContext(ctx, "unsure how to interpret event", + "which", "limit", + "event", ev.Limit) } case `ECOSYSTEM`: switch af.Package.Ecosystem { @@ -696,7 +659,7 @@ func (e *ecs) Insert(ctx context.Context, skipped *stats, name string, a *adviso } } if err != nil { - zlog.Warn(ctx).Err(err).Msg("event version error") + log.WarnContext(ctx, "event version error", "reason", err) } vers = append(vers, vs) } diff --git a/updater/osv/osv_test.go b/updater/osv/osv_test.go index bda1597bd..6b42f1bf5 100644 --- a/updater/osv/osv_test.go +++ b/updater/osv/osv_test.go @@ -4,12 +4,12 @@ import ( "archive/zip" "bufio" "bytes" - "context" "encoding/json" "errors" "fmt" "io" "io/fs" + "log/slog" "net/http" "net/http/httptest" "os" @@ -21,16 +21,16 @@ import ( "github.com/Masterminds/semver" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/quay/zlog" "github.com/quay/claircore" "github.com/quay/claircore/libvuln/driver" + "github.com/quay/claircore/test" ) func TestFetch(t *testing.T) { srv := httptest.NewServer(&apiStub{t, ""}) defer srv.Close() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) f := Factory{} cfgFunc := func(v any) error { @@ -110,7 +110,7 @@ func (a *apiStub) ServeHTTP(w http.ResponseWriter, r *http.Request) { func TestParse(t *testing.T) { srv := httptest.NewServer(&apiStub{t, ""}) defer srv.Close() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) f := Factory{} cfgFunc := func(v any) error { @@ -615,13 +615,13 @@ var cmpIgnore = cmpopts.IgnoreFields( claircore.Vulnerability{}, "ID", "Updater", "Description", "Severity", "NormalizedSeverity", "Package", "Repo") func TestInsert(t *testing.T) { - ctx := zlog.Test(context.Background(), t) - for _, tt := range insertTestCases { t.Run(tt.name, func(t *testing.T) { + ctx := test.Logging(t) ecs := newECS("test") - err := ecs.Insert(ctx, nil, "", tt.ad) + log := slog.Default() + err := ecs.Insert(ctx, log, nil, "", tt.ad) if err != nil { t.Error("got error Inserting advisory", err) } @@ -773,13 +773,13 @@ var severityTestCases = []struct { } func TestSeverityParsing(t *testing.T) { - ctx := zlog.Test(context.Background(), t) - for _, tt := range severityTestCases { t.Run(tt.name, func(t *testing.T) { + ctx := test.Logging(t) ecs := newECS("test") - err := ecs.Insert(ctx, nil, "", tt.a) + log := slog.Default() + err := ecs.Insert(ctx, log, nil, "", tt.a) if err != nil { t.Error("got error Inserting advisory", err) } @@ -793,7 +793,6 @@ func TestSeverityParsing(t *testing.T) { if v.Severity != tt.expectedSeverity { t.Errorf("expected severity %q but got %q", tt.expectedSeverity, v.Severity) } - }) } } @@ -875,12 +874,13 @@ func TestInsertLinksAliases(t *testing.T) { }, } - ctx := zlog.Test(context.Background(), t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + ctx := test.Logging(t) + log := slog.Default() e := newECS("osv") var st stats - if err := (&e).Insert(ctx, &st, "pkg", &tt.adv); err != nil { + if err := (&e).Insert(ctx, log, &st, "pkg", &tt.adv); err != nil { t.Fatalf("Insert() error: %v", err) } if len(e.Vulnerability) == 0 { diff --git a/updater/registry.go b/updater/registry.go index 9578f0d29..dda500c29 100644 --- a/updater/registry.go +++ b/updater/registry.go @@ -3,13 +3,12 @@ package updater import ( "context" "errors" + "log/slog" "maps" "net/http" "strings" "sync" - "github.com/quay/zlog" - "github.com/quay/claircore/libvuln/driver" ) @@ -58,11 +57,10 @@ func Configure(ctx context.Context, fs map[string]driver.UpdaterSetFactory, cfg b.WriteString("updater: errors configuring factories:") for name, fac := range fs { - ev := zlog.Debug(ctx). - Str("factory", name) + msg := "factory unconfigurable" f, ok := fac.(driver.Configurable) if ok { - ev.Msg("configuring factory") + msg = "configuring factory" cf := cfg[name] if cf == nil { cf = noopConfig @@ -72,9 +70,8 @@ func Configure(ctx context.Context, fs map[string]driver.UpdaterSetFactory, cfg b.WriteString("\n\t") b.WriteString(err.Error()) } - } else { - ev.Msg("factory unconfigurable") } + slog.DebugContext(ctx, msg, "factory", name) } if errd { diff --git a/updater/updater.go b/updater/updater.go index 869156dc5..97d7722ef 100644 --- a/updater/updater.go +++ b/updater/updater.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "net/http" "os" "runtime" @@ -16,9 +17,9 @@ import ( "sync" "github.com/google/uuid" - "github.com/quay/zlog" "golang.org/x/sync/errgroup" + "github.com/quay/claircore/toolkit/log" driver "github.com/quay/claircore/updater/driver/v1" ) @@ -54,15 +55,15 @@ func New(ctx context.Context, opts *Options) (*Updater, error) { } if opts.Locker == nil { - zlog.Warn(ctx).Msg("no locker passed, using process-local locking") + slog.WarnContext(ctx, "no locker passed, using process-local locking") u.locker = newLocalLocker() } if opts.Configs == nil { - zlog.Info(ctx).Msg("no updater configuration passed") + slog.InfoContext(ctx, "no updater configuration passed") u.configs = make(driver.Configs) } if opts.Factories == nil { - zlog.Warn(ctx).Msg("no updater factories provided, this may be a misconfiguration") + slog.WarnContext(ctx, "no updater factories provided, this may be a misconfiguration") } _, file, line, _ := runtime.Caller(1) @@ -171,16 +172,16 @@ func (u *Updater) Run(ctx context.Context, strict bool) error { defer wg.Done() spool, err := os.CreateTemp(tmpDir, tmpPattern) if err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to create spool file") + slog.WarnContext(ctx, "unable to create spool file", "reason", err) return err } spoolname := spool.Name() defer func() { if err := os.Remove(spoolname); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("unable to remove spool file") + slog.WarnContext(ctx, "unable to remove spool file", "filename", spoolname, "reason", err) } if err := spool.Close(); err != nil { - zlog.Warn(ctx).Str("filename", spoolname).Err(err).Msg("error closing spool file") + slog.WarnContext(ctx, "error closing spool file", "filename", spoolname, "reason", err) } }() var updErr *updaterError @@ -189,7 +190,7 @@ func (u *Updater) Run(ctx context.Context, strict bool) error { switch { case errors.Is(err, nil): case errors.As(err, &updErr): - zlog.Debug(ctx).Err(updErr).Msg("updater error") + slog.DebugContext(ctx, "updater error", "reason", updErr) errCh <- updErr.Unwrap() default: return err @@ -206,7 +207,7 @@ func (u *Updater) Run(ctx context.Context, strict bool) error { if strict { return errors.Join(errs...) } - zlog.Info(ctx).Errs("errors", errs).Msg("updater errors") + slog.InfoContext(ctx, "updater errors", "errors", errs) } return nil } @@ -229,7 +230,7 @@ func (u *Updater) updaters(ctx context.Context, cfg driver.Configs) ([]taggedUpd set, err = fac.Create(ctx, cfg[key]) }) if err != nil { - zlog.Info(ctx).Err(err).Msg("factory errored") + slog.InfoContext(ctx, "factory errored", "reason", err) continue } for _, upd := range set { @@ -238,11 +239,11 @@ func (u *Updater) updaters(ctx context.Context, cfg driver.Configs) ([]taggedUpd name = upd.Name() }) if strings.Contains(name, "/") { - zlog.Info(ctx).Str("updater", name).Msg("name contains invalid character: /") + slog.InfoContext(ctx, "name contains invalid character: /", "updater", name) continue } if _, ok := dedup[name]; ok { - zlog.Info(ctx).Str("updater", name).Msg("updater already exists") + slog.InfoContext(ctx, "updater already exists", "updater", name) continue } dedup[name] = struct{}{} @@ -258,17 +259,16 @@ func (u *Updater) updaters(ctx context.Context, cfg driver.Configs) ([]taggedUpd func (u *Updater) fetchOne(ctx context.Context, tu taggedUpdater, pfp driver.Fingerprint, out io.Writer) (fp driver.Fingerprint, err error) { name := tu.Name - ctx = zlog.ContextWithValues(ctx, "updater", name) - zlog.Info(ctx).Msg("fetch start") - defer zlog.Info(ctx).Msg("fetch done") + slog.InfoContext(ctx, "fetch start") + defer slog.InfoContext(ctx, "fetch done") lctx, done := u.locker.TryLock(ctx, name) defer done() if err := lctx.Err(); err != nil { if pErr := ctx.Err(); pErr != nil { - zlog.Debug(ctx).Err(err).Msg("parent context canceled") + slog.DebugContext(ctx, "parent context canceled", "reason", err) return fp, nil } - zlog.Info(ctx).Err(err).Msg("lock acquisition failed, skipping") + slog.InfoContext(ctx, "lock acquisition failed, skipping", "reason", err) return fp, err } ctx = lctx @@ -276,11 +276,11 @@ func (u *Updater) fetchOne(ctx context.Context, tu taggedUpdater, pfp driver.Fin zw := zip.NewWriter(out) defer func() { if err := zw.Close(); err != nil { - zlog.Warn(ctx).Err(err).Msg("unable to close zip writer") + slog.WarnContext(ctx, "unable to close zip writer", "reason", err) } }() if len(pfp) != 0 { - zlog.Debug(ctx).Str("fingerprint", string(pfp)).Msg("found previous fingerprint") + slog.DebugContext(ctx, "found previous fingerprint", "fingerprint", string(pfp)) } pprof.Do(ctx, pprof.Labels("task", "updater_fetch", "updater", name), func(ctx context.Context) { fp, err = tu.Updater.Fetch(ctx, zw, pfp, u.client) @@ -295,36 +295,33 @@ func (u *Updater) parseOne(ctx context.Context, tu taggedUpdater, in fs.FS) (*pa err error ) name := tu.Name - ctx = zlog.ContextWithValues(ctx, "updater", name) - zlog.Info(ctx).Msg("parse start") - defer zlog.Info(ctx).Msg("parse done") + ctx = log.With(ctx, "updater", name) + slog.InfoContext(ctx, "parse start") + defer slog.InfoContext(ctx, "parse done") pprof.Do(ctx, pprof.Labels("task", "updater_parse", "updater", name), func(ctx context.Context) { - ctx = zlog.ContextWithValues(ctx, "updater", name) upd := tu.Updater if p, ok := upd.(driver.VulnerabilityParser); ok { - zlog.Debug(ctx).Msg("implements VulnerabilityParser") + slog.DebugContext(ctx, "implements VulnerabilityParser") any = true res.Vulnerabilities, err = p.ParseVulnerability(ctx, in) if err != nil { return } - zlog.Debug(ctx). - Err(err). - Int("ct", len(res.Vulnerabilities.Vulnerability)). - Msg("found vulnerabilities") + slog.DebugContext(ctx, "found vulnerabilities", + "reason", err, + "ct", len(res.Vulnerabilities.Vulnerability)) } if p, ok := upd.(driver.EnrichmentParser); ok { - zlog.Debug(ctx).Msg("implements EnrichmentParser") + slog.DebugContext(ctx, "implements EnrichmentParser") any = true res.Enrichments, err = p.ParseEnrichment(ctx, in) if err != nil { return } - zlog.Debug(ctx). - Err(err). - Int("ct", len(res.Enrichments)). - Msg("found enrichments") + slog.DebugContext(ctx, "found enrichments", + "reason", err, + "ct", len(res.Enrichments)) } }) if !any { @@ -341,28 +338,27 @@ type parseResult struct { func (u *Updater) fetchAndParse(ctx context.Context, spool *os.File, pfps map[string]driver.Fingerprint, tu taggedUpdater) error { spoolname := spool.Name() name := tu.Name - ctx = zlog.ContextWithValues(ctx, "updater", name) if _, err := spool.Seek(0, io.SeekStart); err != nil { - zlog.Error(ctx).Str("filename", spoolname).Err(err).Msg("unable to seek to start") + slog.ErrorContext(ctx, "unable to seek to start", "filename", spoolname, "reason", err) return err } fp, err := u.fetchOne(ctx, tu, pfps[name], spool) switch { case errors.Is(err, nil): case errors.Is(err, driver.ErrUnchanged): - zlog.Debug(ctx).Msg("unchanged") + slog.DebugContext(ctx, "unchanged") return nil default: return updaterErr(err) } sz, err := spool.Seek(0, io.SeekCurrent) if err != nil { - zlog.Error(ctx).Str("filename", spoolname).Err(err).Msg("unable to seek spoolfile") + slog.ErrorContext(ctx, "unable to seek spoolfile", "filename", spoolname, "reason", err) return err } z, err := zip.NewReader(spool, sz) if err != nil { - zlog.Error(ctx).Str("filename", spoolname).Err(err).Msg("unable to create zip reader") + slog.ErrorContext(ctx, "unable to create zip reader", "filename", spoolname, "reason", err) return err } res, err := u.parseOne(ctx, tu, z) @@ -377,14 +373,14 @@ func (u *Updater) fetchAndParse(ctx context.Context, spool *os.File, pfps map[st if err != nil { return } - zlog.Info(ctx).Stringer("ref", ref).Msg("updated vulnerabilites") + slog.InfoContext(ctx, "updated vulnerabilites", "ref", ref) } if len(res.Enrichments) != 0 { err = u.store.UpdateEnrichments(ctx, ref, name, fp, res.Enrichments) if err != nil { return } - zlog.Info(ctx).Stringer("ref", ref).Msg("updated enrichments") + slog.InfoContext(ctx, "updated enrichments", "ref", ref) } }) if err != nil { diff --git a/updater/updater_test.go b/updater/updater_test.go index 44e4a57cb..028a055c6 100644 --- a/updater/updater_test.go +++ b/updater/updater_test.go @@ -15,18 +15,17 @@ import ( "testing" "github.com/google/uuid" - "github.com/quay/zlog" "go.uber.org/mock/gomock" + "github.com/quay/claircore/test" mock_updater "github.com/quay/claircore/test/mock/updater" mock_driver "github.com/quay/claircore/test/mock/updater/driver/v1" "github.com/quay/claircore/updater/driver/v1" ) func TestNew(t *testing.T) { - ctx := zlog.Test(context.Background(), t) t.Run("MissingStore", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) u, err := New(ctx, &Options{ Client: &http.Client{}, }) @@ -39,7 +38,7 @@ func TestNew(t *testing.T) { } }) t.Run("MissingClient", func(t *testing.T) { - ctx := zlog.Test(ctx, t) + ctx := test.Logging(t) u, err := New(ctx, &Options{ Store: mock_updater.NewMockStore(nil), }) @@ -132,7 +131,7 @@ func parseEnrich(_ context.Context, in fs.FS) (r []driver.EnrichmentRecord, err } func TestRun(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) ctl := gomock.NewController(t) n := path.Base(t.Name()) vs := &driver.ParsedVulnerabilities{ diff --git a/whiteout/resolver.go b/whiteout/resolver.go index d98cac5f1..fa913f60e 100644 --- a/whiteout/resolver.go +++ b/whiteout/resolver.go @@ -2,18 +2,15 @@ package whiteout import ( "context" + "log/slog" "path/filepath" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) -var ( - _ indexer.Resolver = (*Resolver)(nil) -) +var _ indexer.Resolver = (*Resolver)(nil) type Resolver struct{} @@ -40,11 +37,10 @@ func (r *Resolver) Resolve(ctx context.Context, ir *claircore.IndexReport, layer // layer. if f.Kind == claircore.FileKindWhiteout && ls.isChildOf(fileLayer, packageLayer) && fileIsDeleted(pkg.Filepath, f.Path) { packageDeleted = true - zlog.Debug(ctx). - Str("package name", pkg.Name). - Str("package file", pkg.Filepath). - Str("whiteout file", f.Path). - Msg("package determined to be deleted") + slog.DebugContext(ctx, "package determined to be deleted", + "package name", pkg.Name, + "package file", pkg.Filepath, + "whiteout file", f.Path) } } if !packageDeleted { diff --git a/whiteout/resolver_test.go b/whiteout/resolver_test.go index 0212435ad..ccf9cfa62 100644 --- a/whiteout/resolver_test.go +++ b/whiteout/resolver_test.go @@ -1,14 +1,12 @@ package whiteout import ( - "context" "crypto/sha256" "io" "testing" "github.com/quay/claircore" - - "github.com/quay/zlog" + "github.com/quay/claircore/test" ) func Digest(name string) claircore.Digest { @@ -260,7 +258,7 @@ func TestResolver(t *testing.T) { r := &Resolver{} for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) { - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) report := r.Resolve(ctx, tc.Report, tc.Layers) if tc.LenPackage != len(report.Packages) { t.Fatalf("wrong number of packages: expected: %d got: %d", tc.LenPackage, len(report.Packages)) @@ -269,7 +267,6 @@ func TestResolver(t *testing.T) { t.Fatalf("wrong number of environments: expected: %d got: %d", tc.LenEnvs, len(report.Environments)) } }) - } } diff --git a/whiteout/scanner.go b/whiteout/scanner.go index a81a70091..bd5dce4a7 100644 --- a/whiteout/scanner.go +++ b/whiteout/scanner.go @@ -4,10 +4,9 @@ import ( "context" "fmt" "io/fs" + "log/slog" "strings" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/indexer" ) @@ -32,12 +31,8 @@ func (*Scanner) Version() string { return scannerVersion } func (*Scanner) Kind() string { return scannerKind } func (s *Scanner) Scan(ctx context.Context, l *claircore.Layer) ([]claircore.File, error) { - ctx = zlog.ContextWithValues(ctx, - "component", "whiteout/Scanner.Scan", - "version", s.Version(), - "layer", l.Hash.String()) - zlog.Debug(ctx).Msg("start") - defer zlog.Debug(ctx).Msg("done") + slog.DebugContext(ctx, "start") + defer slog.DebugContext(ctx, "done") sys, err := l.FS() if err != nil { return nil, fmt.Errorf("whiteout: unable to create fs: %w", err) diff --git a/whiteout/scanner_test.go b/whiteout/scanner_test.go index 754b8bab8..77a69f009 100644 --- a/whiteout/scanner_test.go +++ b/whiteout/scanner_test.go @@ -1,12 +1,9 @@ package whiteout import ( - "context" "os" "testing" - "github.com/quay/zlog" - "github.com/quay/claircore" "github.com/quay/claircore/test" ) @@ -17,7 +14,7 @@ var testScanner = &Scanner{} // layer in "name". func checkCount(t testing.TB, name string, ct int) { t.Helper() - ctx := zlog.Test(context.Background(), t) + ctx := test.Logging(t) lf, err := os.Open(name) if err != nil { t.Fatal(err)