github.com/google/osv-scalibr@v0.4.1/enricher/packagedeprecation/packagedeprecation.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 packagedeprecation enriches inventory details with package version deprecation status from deps.dev
    16  package packagedeprecation
    17  
    18  import (
    19  	"context"
    20  	"maps"
    21  	"slices"
    22  
    23  	"github.com/google/osv-scalibr/clients/depsdev/v1alpha1/grpcclient"
    24  	"github.com/google/osv-scalibr/depsdev/depsdevalpha"
    25  	"github.com/google/osv-scalibr/enricher"
    26  	"github.com/google/osv-scalibr/extractor"
    27  	"github.com/google/osv-scalibr/inventory"
    28  	"github.com/google/osv-scalibr/log"
    29  	"github.com/google/osv-scalibr/plugin"
    30  	"github.com/google/osv-scalibr/purl"
    31  )
    32  
    33  const (
    34  	// Name is the name of the package version deprecation enricher.
    35  	Name = "packagedeprecation/depsdev"
    36  )
    37  
    38  // Enricher is the package version deprecation enricher.
    39  type Enricher struct {
    40  	// client is the deps.dev GRPC client.
    41  	client Client
    42  }
    43  
    44  // Name of the package version deprecation enricher.
    45  func (*Enricher) Name() string { return Name }
    46  
    47  // Version of the package version deprecation enricher.
    48  func (*Enricher) Version() int { return 0 }
    49  
    50  // Requirements of the package version deprecation enricher.
    51  func (*Enricher) Requirements() *plugin.Capabilities {
    52  	return &plugin.Capabilities{Network: plugin.NetworkOnline}
    53  }
    54  
    55  // RequiredPlugins returns a list of Plugins that need to be enabled for this Enricher to work.
    56  // While this enricher can run independently,
    57  // it is intended to be used with extractors to provide the inventory data to enrich.
    58  func (*Enricher) RequiredPlugins() []string {
    59  	return []string{}
    60  }
    61  
    62  // SetClient sets the deps.dev GRPC client.
    63  // This is used for testing.
    64  func (e *Enricher) SetClient(client Client) {
    65  	e.client = client
    66  }
    67  
    68  // New returns a new package deprecation enricher.
    69  func New() enricher.Enricher {
    70  	grpcConfig := grpcclient.DefaultConfig()
    71  	grpcclient, err := grpcclient.New(grpcConfig)
    72  	if err != nil {
    73  		log.Errorf("Failed to create deps.dev gRPC client: %v", err)
    74  	}
    75  
    76  	c := NewClient(grpcclient)
    77  
    78  	return &Enricher{client: c}
    79  }
    80  
    81  // Enrich enriches the inventory with package version deprecation status from deps.dev.
    82  func (e *Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *inventory.Inventory) error {
    83  	log.Debugf("Package deprecation enricher starting, %d packages to enrich.", len(inv.Packages))
    84  
    85  	verToPkg := make(map[VersionKey][]*extractor.Package, len(inv.Packages))
    86  
    87  	for _, pkg := range inv.Packages {
    88  		verKey, ok := makeVersionKey(pkg)
    89  		if !ok {
    90  			// System is not supported by deps.dev. Default deprecated to false.
    91  			pkg.Deprecated = false
    92  			continue
    93  		}
    94  		verToPkg[verKey] = append(verToPkg[verKey], pkg)
    95  	}
    96  
    97  	if len(verToPkg) == 0 {
    98  		return nil
    99  	}
   100  
   101  	query := slices.Collect(maps.Keys(verToPkg))
   102  
   103  	resp, err := e.client.GetVersionBatch(ctx, Request{VersionKeys: query})
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	results := resp.Results
   109  	for verKey, pkgs := range verToPkg {
   110  		deprecated, ok := results[verKey]
   111  		if !ok {
   112  			// Version key not found in deps.dev. Default deprecated to false.
   113  			for _, pkg := range pkgs {
   114  				pkg.Deprecated = false
   115  			}
   116  			continue
   117  		}
   118  		for _, pkg := range pkgs {
   119  			pkg.Deprecated = deprecated
   120  		}
   121  	}
   122  
   123  	log.Debugf("Package deprecation enricher finished.")
   124  	return nil
   125  }
   126  
   127  // makeVersionKey translates system from PURL type to deps.dev system, and returns a version key.
   128  // Returns false if the system is not supported by deps.dev.
   129  func makeVersionKey(pkg *extractor.Package) (VersionKey, bool) {
   130  	system, ok := depsdevalpha.System[pkg.PURLType]
   131  	if !ok {
   132  		return VersionKey{}, false
   133  	}
   134  	name, ver := pkg.Name, pkg.Version
   135  
   136  	// Matching deps.dev naming convention.
   137  	if pkg.PURLType == purl.TypeGolang {
   138  		if name == "stdlib" {
   139  			ver = "go" + ver
   140  		} else {
   141  			ver = "v" + ver
   142  		}
   143  	}
   144  
   145  	return VersionKey{
   146  		System:  system,
   147  		Name:    name,
   148  		Version: ver,
   149  	}, true
   150  }