github.com/quay/claircore@v1.5.28/datastore/postgres/getupdateoperations.go (about)

     1  package postgres
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/google/uuid"
    10  	"github.com/jackc/pgx/v4"
    11  	"github.com/jackc/pgx/v4/pgxpool"
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"github.com/prometheus/client_golang/prometheus/promauto"
    14  	"github.com/quay/zlog"
    15  
    16  	"github.com/quay/claircore/libvuln/driver"
    17  )
    18  
    19  var (
    20  	getLatestUpdateRefCounter = promauto.NewCounterVec(
    21  		prometheus.CounterOpts{
    22  			Namespace: "claircore",
    23  			Subsystem: "vulnstore",
    24  			Name:      "getlatestupdateref_total",
    25  			Help:      "Total number of database queries issued in the GetLatestUpdateRef method.",
    26  		},
    27  		[]string{"query"},
    28  	)
    29  	getLatestUpdateRefDuration = promauto.NewHistogramVec(
    30  		prometheus.HistogramOpts{
    31  			Namespace: "claircore",
    32  			Subsystem: "vulnstore",
    33  			Name:      "getlatestupdateref_duration_seconds",
    34  			Help:      "The duration of all queries issued in the GetLatestUpdateRef method",
    35  		},
    36  		[]string{"query"},
    37  	)
    38  	getLatestRefsCounter = promauto.NewCounterVec(
    39  		prometheus.CounterOpts{
    40  			Namespace: "claircore",
    41  			Subsystem: "vulnstore",
    42  			Name:      "getlatestrefs_total",
    43  			Help:      "Total number of database queries issued in the getLatestRefs method.",
    44  		},
    45  		[]string{"query"},
    46  	)
    47  	getLatestRefsDuration = promauto.NewHistogramVec(
    48  		prometheus.HistogramOpts{
    49  			Namespace: "claircore",
    50  			Subsystem: "vulnstore",
    51  			Name:      "getlatestrefs_duration_seconds",
    52  			Help:      "The duration of all queries issued in the getLatestRefs method",
    53  		},
    54  		[]string{"query"},
    55  	)
    56  	getUpdateOperationsCounter = promauto.NewCounterVec(
    57  		prometheus.CounterOpts{
    58  			Namespace: "claircore",
    59  			Subsystem: "vulnstore",
    60  			Name:      "getupdateoperations_total",
    61  			Help:      "Total number of database queries issued in the getUpdateOperations method.",
    62  		},
    63  		[]string{"query"},
    64  	)
    65  	getUpdateOperationsDuration = promauto.NewHistogramVec(
    66  		prometheus.HistogramOpts{
    67  			Namespace: "claircore",
    68  			Subsystem: "vulnstore",
    69  			Name:      "getupdateoperations_duration_seconds",
    70  			Help:      "The duration of all queries issued in the getUpdateOperations method",
    71  		},
    72  		[]string{"query"},
    73  	)
    74  )
    75  
    76  // GetLatestUpdateRef implements driver.Updater.
    77  func (s *MatcherStore) GetLatestUpdateRef(ctx context.Context, kind driver.UpdateKind) (uuid.UUID, error) {
    78  	const (
    79  		query              = `SELECT ref FROM update_operation ORDER BY id USING > LIMIT 1;`
    80  		queryEnrichment    = `SELECT ref FROM update_operation WHERE kind = 'enrichment' ORDER BY id USING > LIMIT 1;`
    81  		queryVulnerability = `SELECT ref FROM update_operation WHERE kind = 'vulnerability' ORDER BY id USING > LIMIT 1;`
    82  	)
    83  	ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/getLatestRef")
    84  
    85  	var q string
    86  	var label string
    87  	switch kind {
    88  	case "":
    89  		q = query
    90  		label = "query"
    91  	case driver.EnrichmentKind:
    92  		q = queryEnrichment
    93  		label = "query_enrichment"
    94  	case driver.VulnerabilityKind:
    95  		q = queryVulnerability
    96  		label = "query_vulnerability"
    97  	}
    98  
    99  	var ref uuid.UUID
   100  	start := time.Now()
   101  	if err := s.pool.QueryRow(ctx, q).Scan(&ref); err != nil {
   102  		return uuid.Nil, err
   103  	}
   104  	getLatestUpdateRefCounter.WithLabelValues(label).Add(1)
   105  	getLatestUpdateRefDuration.WithLabelValues(label).Observe(time.Since(start).Seconds())
   106  	return ref, nil
   107  }
   108  
   109  func (s *MatcherStore) GetLatestUpdateRefs(ctx context.Context, kind driver.UpdateKind) (map[string][]driver.UpdateOperation, error) {
   110  	const (
   111  		query              = `SELECT DISTINCT ON (updater) updater, ref, fingerprint, date FROM update_operation ORDER BY updater, id USING >;`
   112  		queryEnrichment    = `SELECT DISTINCT ON (updater) updater, ref, fingerprint, date FROM update_operation WHERE kind = 'enrichment' ORDER BY updater, id USING >;`
   113  		queryVulnerability = `SELECT DISTINCT ON (updater) updater, ref, fingerprint, date FROM update_operation WHERE kind = 'vulnerability' ORDER BY updater, id USING >;`
   114  	)
   115  
   116  	var q string
   117  	var label string
   118  	switch kind {
   119  	case "":
   120  		q = query
   121  		label = "query"
   122  	case driver.EnrichmentKind:
   123  		q = queryEnrichment
   124  		label = "query_enrichment"
   125  	case driver.VulnerabilityKind:
   126  		q = queryVulnerability
   127  		label = "query_vulnerability"
   128  	}
   129  
   130  	start := time.Now()
   131  
   132  	rows, err := s.pool.Query(ctx, q)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	getLatestRefsCounter.WithLabelValues(label).Add(1)
   138  	getLatestRefsDuration.WithLabelValues(label).Observe(time.Since(start).Seconds())
   139  
   140  	defer rows.Close()
   141  
   142  	ret := make(map[string][]driver.UpdateOperation)
   143  	for rows.Next() {
   144  		ops := []driver.UpdateOperation{}
   145  		ops = append(ops, driver.UpdateOperation{})
   146  		uo := &ops[len(ops)-1]
   147  		err := rows.Scan(
   148  			&uo.Updater,
   149  			&uo.Ref,
   150  			&uo.Fingerprint,
   151  			&uo.Date,
   152  		)
   153  		if err != nil {
   154  			rows.Close()
   155  			return nil, fmt.Errorf("failed to scan update operation for updater %q: %w", uo.Updater, err)
   156  		}
   157  		ret[uo.Updater] = ops
   158  	}
   159  	zlog.Debug(ctx).
   160  		Int("count", len(ret)).
   161  		Msg("found updaters")
   162  	return ret, nil
   163  }
   164  
   165  func getLatestRefs(ctx context.Context, pool *pgxpool.Pool) (map[string][]driver.UpdateOperation, error) {
   166  	const query = `SELECT DISTINCT ON (updater) updater, ref, fingerprint, date FROM update_operation ORDER BY updater, id USING >;`
   167  	ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/getLatestRefs")
   168  
   169  	start := time.Now()
   170  
   171  	rows, err := pool.Query(ctx, query)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	getLatestRefsCounter.WithLabelValues("query").Add(1)
   177  	getLatestRefsDuration.WithLabelValues("query").Observe(time.Since(start).Seconds())
   178  
   179  	defer rows.Close()
   180  
   181  	ret := make(map[string][]driver.UpdateOperation)
   182  	for rows.Next() {
   183  		ops := []driver.UpdateOperation{}
   184  		ops = append(ops, driver.UpdateOperation{})
   185  		uo := &ops[len(ops)-1]
   186  		err := rows.Scan(
   187  			&uo.Updater,
   188  			&uo.Ref,
   189  			&uo.Fingerprint,
   190  			&uo.Date,
   191  		)
   192  		if err != nil {
   193  			rows.Close()
   194  			return nil, fmt.Errorf("failed to scan update operation for updater %q: %w", uo.Updater, err)
   195  		}
   196  		ret[uo.Updater] = ops
   197  	}
   198  	zlog.Debug(ctx).
   199  		Int("count", len(ret)).
   200  		Msg("found updaters")
   201  	return ret, nil
   202  }
   203  
   204  func (s *MatcherStore) GetUpdateOperations(ctx context.Context, kind driver.UpdateKind, updater ...string) (map[string][]driver.UpdateOperation, error) {
   205  	const (
   206  		query              = `SELECT ref, updater, fingerprint, date FROM update_operation WHERE updater = ANY($1) ORDER BY id DESC;`
   207  		queryVulnerability = `SELECT ref, updater, fingerprint, date FROM update_operation WHERE updater = ANY($1) AND kind = 'vulnerability' ORDER BY id DESC;`
   208  		queryEnrichment    = `SELECT ref, updater, fingerprint, date FROM update_operation WHERE updater = ANY($1) AND kind = 'enrichment' ORDER BY id DESC;`
   209  		getUpdaters        = `SELECT DISTINCT(updater) FROM update_operation;`
   210  	)
   211  	ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/postgres/getUpdateOperations")
   212  
   213  	tx, err := s.pool.Begin(ctx)
   214  	if err != nil {
   215  		return nil, fmt.Errorf("failed to begin transaction: %w", err)
   216  	}
   217  	defer tx.Rollback(ctx)
   218  	out := make(map[string][]driver.UpdateOperation)
   219  
   220  	// Get distinct updaters from database if nothing specified.
   221  	if len(updater) == 0 {
   222  		updater = []string{}
   223  
   224  		start := time.Now()
   225  
   226  		rows, err := tx.Query(ctx, getUpdaters)
   227  		switch {
   228  		case err == nil:
   229  		case errors.Is(err, pgx.ErrNoRows):
   230  			return out, nil
   231  		default:
   232  			return nil, fmt.Errorf("failed to get distinct updates: %w", err)
   233  		}
   234  
   235  		getUpdateOperationsCounter.WithLabelValues("getUpdaters").Add(1)
   236  		getUpdateOperationsDuration.WithLabelValues("getUpdaters").Observe(time.Since(start).Seconds())
   237  
   238  		defer rows.Close() // OK to defer and call, as per docs.
   239  
   240  		for rows.Next() {
   241  			var u string
   242  			err := rows.Scan(&u)
   243  			if err != nil {
   244  				return nil, fmt.Errorf("failed to scan updater: %w", err)
   245  			}
   246  			updater = append(updater, u)
   247  		}
   248  		if err := rows.Err(); err != nil {
   249  			return nil, err
   250  		}
   251  		rows.Close()
   252  	}
   253  
   254  	var q string
   255  	var label string
   256  	switch kind {
   257  	case "":
   258  		q = query
   259  		label = "query"
   260  	case driver.EnrichmentKind:
   261  		q = queryEnrichment
   262  		label = "query_enrichment"
   263  	case driver.VulnerabilityKind:
   264  		q = queryVulnerability
   265  		label = "query_vulnerability"
   266  	}
   267  
   268  	start := time.Now()
   269  	rows, err := tx.Query(ctx, q, updater)
   270  	switch {
   271  	case err == nil:
   272  	case errors.Is(err, pgx.ErrNoRows):
   273  		return out, nil
   274  	default:
   275  		return nil, fmt.Errorf("failed to get distinct updates: %w", err)
   276  	}
   277  	getUpdateOperationsCounter.WithLabelValues(label).Add(1)
   278  	getUpdateOperationsDuration.WithLabelValues(label).Observe(time.Since(start).Seconds())
   279  
   280  	for rows.Next() {
   281  		var uo driver.UpdateOperation
   282  		err := rows.Scan(
   283  			&uo.Ref,
   284  			&uo.Updater,
   285  			&uo.Fingerprint,
   286  			&uo.Date,
   287  		)
   288  		if err != nil {
   289  			rows.Close()
   290  			return nil, fmt.Errorf("failed to scan update operation for updater %q: %w", uo.Updater, err)
   291  		}
   292  		out[uo.Updater] = append(out[uo.Updater], uo)
   293  	}
   294  	return out, nil
   295  }