github.com/google/osv-scalibr@v0.4.1/clients/datasource/insights.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package datasource provides clients to fetch data from different APIs.
    16  package datasource
    17  
    18  import (
    19  	"context"
    20  	"crypto/x509"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	pb "deps.dev/api/v3"
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/credentials"
    28  )
    29  
    30  // CachedInsightsClient is a wrapper for InsightsClient that caches requests.
    31  type CachedInsightsClient struct {
    32  	pb.InsightsClient
    33  
    34  	// cache fields
    35  	mu                sync.Mutex
    36  	cacheTimestamp    *time.Time
    37  	packageCache      *RequestCache[packageKey, *pb.Package]
    38  	versionCache      *RequestCache[versionKey, *pb.Version]
    39  	requirementsCache *RequestCache[versionKey, *pb.Requirements]
    40  }
    41  
    42  // Comparable types to use as map keys for cache.
    43  type packageKey struct {
    44  	System pb.System
    45  	Name   string
    46  }
    47  
    48  func makePackageKey(k *pb.PackageKey) packageKey {
    49  	return packageKey{
    50  		System: k.GetSystem(),
    51  		Name:   k.GetName(),
    52  	}
    53  }
    54  
    55  type versionKey struct {
    56  	System  pb.System
    57  	Name    string
    58  	Version string
    59  }
    60  
    61  func makeVersionKey(k *pb.VersionKey) versionKey {
    62  	return versionKey{
    63  		System:  k.GetSystem(),
    64  		Name:    k.GetName(),
    65  		Version: k.GetVersion(),
    66  	}
    67  }
    68  
    69  // NewCachedInsightsClient creates a CachedInsightsClient.
    70  func NewCachedInsightsClient(addr string, userAgent string) (*CachedInsightsClient, error) {
    71  	certPool, err := x509.SystemCertPool()
    72  	if err != nil {
    73  		return nil, fmt.Errorf("getting system cert pool: %w", err)
    74  	}
    75  	creds := credentials.NewClientTLSFromCert(certPool, "")
    76  	dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
    77  
    78  	if userAgent != "" {
    79  		dialOpts = append(dialOpts, grpc.WithUserAgent(userAgent))
    80  	}
    81  
    82  	conn, err := grpc.NewClient(addr, dialOpts...)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("dialling %q: %w", addr, err)
    85  	}
    86  
    87  	return &CachedInsightsClient{
    88  		InsightsClient:    pb.NewInsightsClient(conn),
    89  		packageCache:      NewRequestCache[packageKey, *pb.Package](),
    90  		versionCache:      NewRequestCache[versionKey, *pb.Version](),
    91  		requirementsCache: NewRequestCache[versionKey, *pb.Requirements](),
    92  	}, nil
    93  }
    94  
    95  // GetPackage returns metadata about a package by querying deps.dev API.
    96  func (c *CachedInsightsClient) GetPackage(ctx context.Context, in *pb.GetPackageRequest, opts ...grpc.CallOption) (*pb.Package, error) {
    97  	return c.packageCache.Get(makePackageKey(in.GetPackageKey()), func() (*pb.Package, error) {
    98  		return c.InsightsClient.GetPackage(ctx, in, opts...)
    99  	})
   100  }
   101  
   102  // GetVersion returns metadata about a version by querying deps.dev API.
   103  func (c *CachedInsightsClient) GetVersion(ctx context.Context, in *pb.GetVersionRequest, opts ...grpc.CallOption) (*pb.Version, error) {
   104  	return c.versionCache.Get(makeVersionKey(in.GetVersionKey()), func() (*pb.Version, error) {
   105  		return c.InsightsClient.GetVersion(ctx, in, opts...)
   106  	})
   107  }
   108  
   109  // GetRequirements returns requirements of the given version by querying deps.dev API.
   110  func (c *CachedInsightsClient) GetRequirements(ctx context.Context, in *pb.GetRequirementsRequest, opts ...grpc.CallOption) (*pb.Requirements, error) {
   111  	return c.requirementsCache.Get(makeVersionKey(in.GetVersionKey()), func() (*pb.Requirements, error) {
   112  		return c.InsightsClient.GetRequirements(ctx, in, opts...)
   113  	})
   114  }