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

     1  package postgres
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/prometheus/client_golang/prometheus"
    10  	"github.com/prometheus/client_golang/prometheus/promauto"
    11  	"github.com/quay/zlog"
    12  
    13  	"github.com/quay/claircore"
    14  	"github.com/quay/claircore/pkg/microbatch"
    15  )
    16  
    17  var (
    18  	indexManifestCounter = promauto.NewCounterVec(
    19  		prometheus.CounterOpts{
    20  			Namespace: "claircore",
    21  			Subsystem: "indexer",
    22  			Name:      "indexmanifest_total",
    23  			Help:      "Total number of database queries issued in the IndexManifest method.",
    24  		},
    25  		[]string{"query"},
    26  	)
    27  
    28  	indexManifestDuration = promauto.NewHistogramVec(
    29  		prometheus.HistogramOpts{
    30  			Namespace: "claircore",
    31  			Subsystem: "indexer",
    32  			Name:      "indexmanifest_duration_seconds",
    33  			Help:      "The duration of all queries issued in the IndexManifest method",
    34  		},
    35  		[]string{"query"},
    36  	)
    37  )
    38  
    39  func (s *IndexerStore) IndexManifest(ctx context.Context, ir *claircore.IndexReport) error {
    40  	const (
    41  		query = `
    42  		WITH manifests AS (
    43  			SELECT id AS manifest_id
    44  			FROM manifest
    45  			WHERE hash = $4
    46  		)
    47  		INSERT
    48  		INTO manifest_index(package_id, dist_id, repo_id, manifest_id)
    49  		VALUES ($1, $2, $3, (SELECT manifest_id FROM manifests))
    50  		ON CONFLICT DO NOTHING;
    51  		`
    52  	)
    53  	ctx = zlog.ContextWithValues(ctx, "component", "datastore/postgres/indexManifest")
    54  
    55  	if ir.Hash.String() == "" {
    56  		return fmt.Errorf("received empty hash. cannot associate contents with a manifest hash")
    57  	}
    58  	hash := ir.Hash.String()
    59  
    60  	records := ir.IndexRecords()
    61  	if len(records) == 0 {
    62  		zlog.Warn(ctx).Msg("manifest being indexed has 0 index records")
    63  		return nil
    64  	}
    65  
    66  	// obtain a transaction scoped batch
    67  	tx, err := s.pool.Begin(ctx)
    68  	if err != nil {
    69  		return fmt.Errorf("postgres: indexManifest failed to create transaction: %w", err)
    70  	}
    71  	defer tx.Rollback(ctx)
    72  
    73  	queryStmt, err := tx.Prepare(ctx, "queryStmt", query)
    74  	if err != nil {
    75  		return fmt.Errorf("failed to create statement: %w", err)
    76  	}
    77  
    78  	start := time.Now()
    79  	mBatcher := microbatch.NewInsert(tx, 500, time.Minute)
    80  	for _, record := range records {
    81  		// ignore nil packages
    82  		if record.Package == nil {
    83  			continue
    84  		}
    85  
    86  		v, err := toValues(*record)
    87  		if err != nil {
    88  			return fmt.Errorf("received a record with an invalid id: %v", err)
    89  		}
    90  
    91  		// if source package exists create record
    92  		if v[0] != nil {
    93  			err = mBatcher.Queue(
    94  				ctx,
    95  				queryStmt.SQL,
    96  				v[0],
    97  				v[2],
    98  				v[3],
    99  				hash,
   100  			)
   101  			if err != nil {
   102  				return fmt.Errorf("batch insert failed for source package record %v: %w", record, err)
   103  			}
   104  		}
   105  
   106  		err = mBatcher.Queue(
   107  			ctx,
   108  			queryStmt.SQL,
   109  			v[1],
   110  			v[2],
   111  			v[3],
   112  			hash,
   113  		)
   114  		if err != nil {
   115  			return fmt.Errorf("batch insert failed for package record %v: %w", record, err)
   116  		}
   117  
   118  	}
   119  	err = mBatcher.Done(ctx)
   120  	if err != nil {
   121  		return fmt.Errorf("final batch insert failed: %w", err)
   122  	}
   123  	indexManifestCounter.WithLabelValues("query_batch").Add(1)
   124  	indexManifestDuration.WithLabelValues("query_batch").Observe(time.Since(start).Seconds())
   125  
   126  	err = tx.Commit(ctx)
   127  	if err != nil {
   128  		return fmt.Errorf("failed to commit tx: %w", err)
   129  	}
   130  	return nil
   131  }
   132  
   133  // toValues is a helper method which checks for
   134  // nil pointers inside an IndexRecord before
   135  // returning an associated pointer to the artifact
   136  // in question.
   137  //
   138  // v[0] source package id or nil
   139  // v[1] package id or nil
   140  // v[2] distribution id or nil
   141  // v[3] repository id or nil
   142  func toValues(r claircore.IndexRecord) ([4]*uint64, error) {
   143  	res := [4]*uint64{}
   144  
   145  	if r.Package.Source != nil {
   146  		id, err := strconv.ParseUint(r.Package.Source.ID, 10, 64)
   147  		if err != nil {
   148  			return res, fmt.Errorf("source package id %v: %v", r.Package.ID, err)
   149  		}
   150  		res[0] = &id
   151  	}
   152  
   153  	if r.Package != nil {
   154  		id, err := strconv.ParseUint(r.Package.ID, 10, 64)
   155  		if err != nil {
   156  			return res, fmt.Errorf("package id %v: %v", r.Package.ID, err)
   157  		}
   158  		res[1] = &id
   159  
   160  	}
   161  
   162  	if r.Distribution != nil {
   163  		id, err := strconv.ParseUint(r.Distribution.ID, 10, 64)
   164  		if err != nil {
   165  			return res, fmt.Errorf("distribution id %v: %v", r.Distribution.ID, err)
   166  		}
   167  		res[2] = &id
   168  	}
   169  
   170  	if r.Repository != nil {
   171  		id, err := strconv.ParseUint(r.Repository.ID, 10, 64)
   172  		if err != nil {
   173  			// return res, fmt.Errorf("repository id %v: %v", r.Package.ID, err)
   174  			return res, nil
   175  		}
   176  		res[3] = &id
   177  	}
   178  
   179  	return res, nil
   180  }