github.com/quay/claircore@v1.5.28/datastore/postgres/getupdateoperationdiff.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  
    15  	"github.com/quay/claircore"
    16  	"github.com/quay/claircore/libvuln/driver"
    17  )
    18  
    19  var (
    20  	getUpdateDiffCounter = promauto.NewCounterVec(
    21  		prometheus.CounterOpts{
    22  			Namespace: "claircore",
    23  			Subsystem: "vulnstore",
    24  			Name:      "getupdatediff_total",
    25  			Help:      "Total number of database queries issued in the getUpdateDiff  method.",
    26  		},
    27  		[]string{"query"},
    28  	)
    29  	getUpdateDiffDuration = promauto.NewHistogramVec(
    30  		prometheus.HistogramOpts{
    31  			Namespace: "claircore",
    32  			Subsystem: "vulnstore",
    33  			Name:      "getupdatediff_duration_seconds",
    34  			Help:      "The duration of all queries issued in the getUpdateDiff method",
    35  		},
    36  		[]string{"query"},
    37  	)
    38  	populateRefsCounter = promauto.NewCounterVec(
    39  		prometheus.CounterOpts{
    40  			Namespace: "claircore",
    41  			Subsystem: "vulnstore",
    42  			Name:      "populaterefs_total",
    43  			Help:      "Total number of database queries issued in the populateRefs  method.",
    44  		},
    45  		[]string{"query"},
    46  	)
    47  	populateRefsDuration = promauto.NewHistogramVec(
    48  		prometheus.HistogramOpts{
    49  			Namespace: "claircore",
    50  			Subsystem: "vulnstore",
    51  			Name:      "populaterefs_duration_seconds",
    52  			Help:      "The duration of all queries issued in the populateRefs method",
    53  		},
    54  		[]string{"query"},
    55  	)
    56  )
    57  
    58  func (s *MatcherStore) GetUpdateDiff(ctx context.Context, prev, cur uuid.UUID) (*driver.UpdateDiff, error) {
    59  	// confirmRefs will return a row only if both refs are kind = 'vulnerability'
    60  	// therefore, if a pgx.ErrNoRows is returned from this query, at least one
    61  	// of the incoming refs is not of kind = 'vulnerability'.
    62  	const confirmRefs = `
    63  SELECT 1
    64  WHERE ROW ('vulnerability') = ALL (SELECT kind FROM update_operation WHERE ref = $1 OR ref = $2);
    65  `
    66  	// Query takes two update IDs and returns rows that only exist in first
    67  	// argument's set of vulnerabilities.
    68  	const query = `WITH
    69  		lhs AS (SELECT id, updater FROM update_operation WHERE ref = $1),
    70  		rhs AS (SELECT id, updater  FROM update_operation WHERE ref = $2)
    71  	SELECT
    72  		id,
    73  		name,
    74  		updater,
    75  		description,
    76  		issued,
    77  		links,
    78  		severity,
    79  		normalized_severity,
    80  		package_name,
    81  		package_version,
    82  		package_module,
    83  		package_arch,
    84  		package_kind,
    85  		dist_id,
    86  		dist_name,
    87  		dist_version,
    88  		dist_version_code_name,
    89  		dist_version_id,
    90  		dist_arch,
    91  		dist_cpe,
    92  		dist_pretty_name,
    93  		arch_operation,
    94  		repo_name,
    95  		repo_key,
    96  		repo_uri,
    97  		fixed_in_version
    98  	FROM vuln
    99  	WHERE
   100  		vuln.id IN (
   101  			SELECT vuln AS id FROM uo_vuln JOIN lhs ON (uo_vuln.uo = lhs.id)
   102  			EXCEPT ALL
   103  			SELECT vuln AS id FROM uo_vuln JOIN rhs ON (uo_vuln.uo = rhs.id)
   104  		)
   105  		AND (
   106  			vuln.updater = (SELECT updater FROM rhs)
   107  			OR  vuln.updater = (SELECT updater FROM lhs)
   108  		);
   109  `
   110  
   111  	if cur == uuid.Nil {
   112  		return nil, errors.New("nil uuid is invalid as \"current\" endpoint")
   113  	}
   114  
   115  	// confirm both refs are of type == 'vulnerability'
   116  	start := time.Now()
   117  	rows, err := s.pool.Query(ctx, confirmRefs, cur, prev)
   118  	switch err {
   119  	case nil:
   120  		rows.Close()
   121  	case pgx.ErrNoRows:
   122  		return nil, fmt.Errorf("provided ref was not of kind 'vulnerability'")
   123  	default:
   124  		return nil, fmt.Errorf("failed to confirm update op ref types: %w", err)
   125  	}
   126  	getUpdateDiffCounter.WithLabelValues("confirmrefs").Add(1)
   127  	getUpdateDiffDuration.WithLabelValues("confirmrefs").Observe(time.Since(start).Seconds())
   128  
   129  	// Retrieve added first.
   130  	var diff driver.UpdateDiff
   131  	if err := populateRefs(ctx, &diff, s.pool, prev, cur); err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	rows, err = s.pool.Query(ctx, query, cur, prev)
   136  	if err != nil {
   137  		return nil, fmt.Errorf("failed to retrieve added vulnerabilities: %w", err)
   138  	}
   139  
   140  	getUpdateDiffCounter.WithLabelValues("query").Add(1)
   141  	getUpdateDiffDuration.WithLabelValues("query").Observe(time.Since(start).Seconds())
   142  
   143  	defer rows.Close()
   144  
   145  	for rows.Next() {
   146  		v := claircore.Vulnerability{
   147  			Package: &claircore.Package{},
   148  			Dist:    &claircore.Distribution{},
   149  			Repo:    &claircore.Repository{},
   150  		}
   151  		if err := scanVulnerability(&v, rows); err != nil {
   152  			return nil, fmt.Errorf("failed to scan added vulnerability: %v", err)
   153  		}
   154  		diff.Added = append(diff.Added, v)
   155  	}
   156  	if err := rows.Err(); err != nil {
   157  		return nil, err
   158  	}
   159  	rows.Close() // OK according to the docs.
   160  
   161  	// If we're starting at the beginning of time, nothing is going to
   162  	// be removed.
   163  	if prev == uuid.Nil {
   164  		return &diff, nil
   165  	}
   166  	rows, err = s.pool.Query(ctx, query, prev, cur)
   167  	if err != nil {
   168  		return nil, fmt.Errorf("failed to retrieve removed vulnerabilities: %w", err)
   169  	}
   170  	defer rows.Close()
   171  	for rows.Next() {
   172  		v := claircore.Vulnerability{
   173  			Package: &claircore.Package{},
   174  			Dist:    &claircore.Distribution{},
   175  			Repo:    &claircore.Repository{},
   176  		}
   177  		if err := scanVulnerability(&v, rows); err != nil {
   178  			return nil, fmt.Errorf("failed to scan removed vulnerability: %v", err)
   179  		}
   180  		diff.Removed = append(diff.Removed, v)
   181  	}
   182  	if err := rows.Err(); err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	return &diff, nil
   187  }
   188  
   189  // PopulateRefs fills in the provided UpdateDiff with the details of the
   190  // operations indicated by the two refs.
   191  func populateRefs(ctx context.Context, diff *driver.UpdateDiff, pool *pgxpool.Pool, prev, cur uuid.UUID) error {
   192  	const query = `SELECT updater, fingerprint, date FROM update_operation WHERE ref = $1;`
   193  	var err error
   194  
   195  	diff.Cur.Ref = cur
   196  	start := time.Now()
   197  	err = pool.QueryRow(ctx, query, cur).Scan(
   198  		&diff.Cur.Updater,
   199  		&diff.Cur.Fingerprint,
   200  		&diff.Cur.Date,
   201  	)
   202  	switch {
   203  	case err == nil:
   204  	case errors.Is(err, pgx.ErrNoRows):
   205  		return fmt.Errorf("operation %v does not exist", cur)
   206  	default:
   207  		return fmt.Errorf("failed to scan current UpdateOperation: %w", err)
   208  	}
   209  	populateRefsCounter.WithLabelValues("query").Add(1)
   210  	populateRefsDuration.WithLabelValues("query").Observe(time.Since(start).Seconds())
   211  
   212  	if prev == uuid.Nil {
   213  		return nil
   214  	}
   215  	diff.Prev.Ref = prev
   216  
   217  	start = time.Now()
   218  	err = pool.QueryRow(ctx, query, prev).Scan(
   219  		&diff.Prev.Updater,
   220  		&diff.Prev.Fingerprint,
   221  		&diff.Prev.Date,
   222  	)
   223  	switch {
   224  	case err == nil:
   225  	case errors.Is(err, pgx.ErrNoRows):
   226  		return fmt.Errorf("operation %v does not exist", prev)
   227  	default:
   228  		return fmt.Errorf("failed to scan previous UpdateOperation: %w", err)
   229  	}
   230  	populateRefsCounter.WithLabelValues("query").Add(1)
   231  	populateRefsDuration.WithLabelValues("query").Observe(time.Since(start).Seconds())
   232  
   233  	return nil
   234  }