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

     1  package postgres
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/prometheus/client_golang/prometheus"
     9  	"github.com/prometheus/client_golang/prometheus/promauto"
    10  	"github.com/quay/zlog"
    11  
    12  	"github.com/quay/claircore"
    13  	"github.com/quay/claircore/indexer"
    14  	"github.com/quay/claircore/pkg/microbatch"
    15  )
    16  
    17  var zeroPackage = claircore.Package{}
    18  
    19  var (
    20  	indexPackageCounter = promauto.NewCounterVec(
    21  		prometheus.CounterOpts{
    22  			Namespace: "claircore",
    23  			Subsystem: "indexer",
    24  			Name:      "indexpackage_total",
    25  			Help:      "Total number of database queries issued in the IndexPackage method.",
    26  		},
    27  		[]string{"query"},
    28  	)
    29  
    30  	indexPackageDuration = promauto.NewHistogramVec(
    31  		prometheus.HistogramOpts{
    32  			Namespace: "claircore",
    33  			Subsystem: "indexer",
    34  			Name:      "indexpackage_duration_seconds",
    35  			Help:      "The duration of all queries issued in the IndexPackage method",
    36  		},
    37  		[]string{"query"},
    38  	)
    39  )
    40  
    41  // IndexPackages indexes all provided packages along with creating a scan artifact.
    42  //
    43  // If a source package is nested inside a binary package we index the source
    44  // package first and then create a relation between the binary package and
    45  // source package.
    46  //
    47  // Scan artifacts are used to determine if a particular layer has been scanned by a
    48  // particular scanner. See the LayerScanned method for more details.
    49  func (s *IndexerStore) IndexPackages(ctx context.Context, pkgs []*claircore.Package, layer *claircore.Layer, scnr indexer.VersionedScanner) error {
    50  	const (
    51  		insert = ` 
    52  		INSERT INTO package (name, kind, version, norm_kind, norm_version, module, arch)
    53  		VALUES ($1, $2, $3, $4, $5::int[], $6, $7)
    54  		ON CONFLICT (name, kind, version, module, arch) DO NOTHING;
    55  		`
    56  
    57  		insertWith = `
    58  		WITH source_package AS (
    59  			SELECT id AS source_id
    60  			FROM package
    61  			WHERE name = $1
    62  			  AND kind = $2
    63  			  AND version = $3
    64  			  AND module = $4
    65  			  AND arch = $5
    66  		),
    67  			 binary_package AS (
    68  				 SELECT id AS package_id
    69  				 FROM package
    70  				 WHERE name = $6
    71  				   AND kind = $7
    72  				   AND version = $8
    73  				   AND module = $9
    74  				   AND arch = $10
    75  			 ),
    76  			 scanner AS (
    77  				 SELECT id AS scanner_id
    78  				 FROM scanner
    79  				 WHERE name = $11
    80  				   AND version = $12
    81  				   AND kind = $13
    82  			 ),
    83  			 layer AS (
    84  				 SELECT id AS layer_id
    85  				 FROM layer
    86  				 WHERE layer.hash = $14
    87  			 )
    88  		INSERT
    89  		INTO package_scanartifact (layer_id, package_db, repository_hint, filepath, package_id, source_id, scanner_id)
    90  		VALUES ((SELECT layer_id FROM layer),
    91  				$15,
    92  				$16,
    93  				$17,
    94  				(SELECT package_id FROM binary_package),
    95  				(SELECT source_id FROM source_package),
    96  				(SELECT scanner_id FROM scanner))
    97  		ON CONFLICT DO NOTHING;
    98  		`
    99  	)
   100  
   101  	ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/indexPackages")
   102  	// obtain a transaction scoped batch
   103  	tx, err := s.pool.Begin(ctx)
   104  	if err != nil {
   105  		return fmt.Errorf("store:indexPackage failed to create transaction: %w", err)
   106  	}
   107  	defer tx.Rollback(ctx)
   108  
   109  	insertPackageStmt, err := tx.Prepare(ctx, "insertPackageStmt", insert)
   110  	if err != nil {
   111  		return fmt.Errorf("failed to create statement: %w", err)
   112  	}
   113  	insertPackageScanArtifactWithStmt, err := tx.Prepare(ctx, "insertPackageScanArtifactWith", insertWith)
   114  	if err != nil {
   115  		return fmt.Errorf("failed to create statement: %w", err)
   116  	}
   117  
   118  	skipCt := 0
   119  
   120  	start := time.Now()
   121  	mBatcher := microbatch.NewInsert(tx, 500, time.Minute)
   122  	for _, pkg := range pkgs {
   123  		if pkg.Name == "" {
   124  			skipCt++
   125  		}
   126  		if pkg.Source == nil {
   127  			pkg.Source = &zeroPackage
   128  		}
   129  
   130  		if err := queueInsert(ctx, mBatcher, insertPackageStmt.Name, pkg.Source); err != nil {
   131  			return err
   132  		}
   133  		if err := queueInsert(ctx, mBatcher, insertPackageStmt.Name, pkg); err != nil {
   134  			return err
   135  		}
   136  	}
   137  	err = mBatcher.Done(ctx)
   138  	if err != nil {
   139  		return fmt.Errorf("final batch insert failed for pkg: %w", err)
   140  	}
   141  	indexPackageCounter.WithLabelValues("insert_batch").Add(1)
   142  	indexPackageDuration.WithLabelValues("insert_batch").Observe(time.Since(start).Seconds())
   143  
   144  	zlog.Debug(ctx).
   145  		Int("skipped", skipCt).
   146  		Int("inserted", len(pkgs)-skipCt).
   147  		Msg("packages inserted")
   148  
   149  	skipCt = 0
   150  	// make package scan artifacts
   151  	mBatcher = microbatch.NewInsert(tx, 500, time.Minute)
   152  
   153  	start = time.Now()
   154  	for _, pkg := range pkgs {
   155  		if pkg.Name == "" {
   156  			skipCt++
   157  			continue
   158  		}
   159  		err := mBatcher.Queue(
   160  			ctx,
   161  			insertPackageScanArtifactWithStmt.SQL,
   162  			pkg.Source.Name,
   163  			pkg.Source.Kind,
   164  			pkg.Source.Version,
   165  			pkg.Source.Module,
   166  			pkg.Source.Arch,
   167  			pkg.Name,
   168  			pkg.Kind,
   169  			pkg.Version,
   170  			pkg.Module,
   171  			pkg.Arch,
   172  			scnr.Name(),
   173  			scnr.Version(),
   174  			scnr.Kind(),
   175  			layer.Hash,
   176  			pkg.PackageDB,
   177  			pkg.RepositoryHint,
   178  			pkg.Filepath,
   179  		)
   180  		if err != nil {
   181  			return fmt.Errorf("batch insert failed for package_scanartifact %v: %w", pkg, err)
   182  		}
   183  	}
   184  	err = mBatcher.Done(ctx)
   185  	if err != nil {
   186  		return fmt.Errorf("final batch insert failed for package_scanartifact: %w", err)
   187  	}
   188  	indexPackageCounter.WithLabelValues("insertWith_batch").Add(1)
   189  	indexPackageDuration.WithLabelValues("insertWith_batch").Observe(time.Since(start).Seconds())
   190  	zlog.Debug(ctx).
   191  		Int("skipped", skipCt).
   192  		Int("inserted", len(pkgs)-skipCt).
   193  		Msg("scanartifacts inserted")
   194  
   195  	err = tx.Commit(ctx)
   196  	if err != nil {
   197  		return fmt.Errorf("store:indexPackages failed to commit tx: %w", err)
   198  	}
   199  	return nil
   200  }
   201  
   202  func queueInsert(ctx context.Context, b *microbatch.Insert, stmt string, pkg *claircore.Package) error {
   203  	var vKind *string
   204  	var vNorm []int32
   205  	if pkg.NormalizedVersion.Kind != "" {
   206  		vKind = &pkg.NormalizedVersion.Kind
   207  		vNorm = pkg.NormalizedVersion.V[:]
   208  	}
   209  	err := b.Queue(ctx, stmt,
   210  		pkg.Name, pkg.Kind, pkg.Version, vKind, vNorm, pkg.Module, pkg.Arch,
   211  	)
   212  	if err != nil {
   213  		return fmt.Errorf("failed to queue insert for package %q: %w", pkg.Name, err)
   214  	}
   215  	return nil
   216  }