github.com/google/osv-scalibr@v0.4.1/clients/resolution/combined_native_client.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 resolution
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sync"
    21  
    22  	"deps.dev/util/resolve"
    23  	"github.com/google/osv-scalibr/clients/datasource"
    24  )
    25  
    26  // CombinedNativeClient is a ResolutionClient that combines all the native clients:
    27  // MavenRegistryClient, NPMRegistryClient, PyPIRegistryClient.
    28  // Individual clients are lazy-initialized when needed.
    29  type CombinedNativeClient struct {
    30  	opts CombinedNativeClientOptions
    31  
    32  	mu                  sync.Mutex
    33  	mavenRegistryClient *MavenRegistryClient
    34  	npmRegistryClient   *NPMRegistryClient
    35  	pypiRegistryClient  *PyPIRegistryClient
    36  }
    37  
    38  // CombinedNativeClientOptions contains the options each client in the CombinedNativeClient.
    39  type CombinedNativeClientOptions struct {
    40  	ProjectDir        string                             // The project directory to use, currently only used for NPM to find .npmrc files.
    41  	LocalRegistry     string                             // The local directory to store the downloaded manifests during resolution.
    42  	MavenRegistry     string                             // The default Maven registry to use.
    43  	PyPIRegistry      string                             // The default PyPI registry to use.
    44  	MavenClient       *datasource.MavenRegistryAPIClient // The Maven registry client to use, if nil, a new client will be created.
    45  	DisableGoogleAuth bool                               // If true, do not try to create google.DefaultClient for Artifact Registry.
    46  }
    47  
    48  // NewCombinedNativeClient makes a new CombinedNativeClient.
    49  func NewCombinedNativeClient(opts CombinedNativeClientOptions) (*CombinedNativeClient, error) {
    50  	client := &CombinedNativeClient{opts: opts}
    51  	if opts.MavenClient != nil {
    52  		client.mavenRegistryClient = NewMavenRegistryClientWithAPI(opts.MavenClient)
    53  	}
    54  	return client, nil
    55  }
    56  
    57  // Version returns metadata of a version specified by the VersionKey.
    58  func (c *CombinedNativeClient) Version(ctx context.Context, vk resolve.VersionKey) (resolve.Version, error) {
    59  	client, err := c.clientForSystem(ctx, vk.System)
    60  	if err != nil {
    61  		return resolve.Version{}, err
    62  	}
    63  	return client.Version(ctx, vk)
    64  }
    65  
    66  // Versions returns all the available versions of the package specified by the given PackageKey.
    67  func (c *CombinedNativeClient) Versions(ctx context.Context, pk resolve.PackageKey) ([]resolve.Version, error) {
    68  	client, err := c.clientForSystem(ctx, pk.System)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return client.Versions(ctx, pk)
    73  }
    74  
    75  // Requirements returns requirements of a version specified by the VersionKey.
    76  func (c *CombinedNativeClient) Requirements(ctx context.Context, vk resolve.VersionKey) ([]resolve.RequirementVersion, error) {
    77  	client, err := c.clientForSystem(ctx, vk.System)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return client.Requirements(ctx, vk)
    82  }
    83  
    84  // MatchingVersions returns versions matching the requirement specified by the VersionKey.
    85  func (c *CombinedNativeClient) MatchingVersions(ctx context.Context, vk resolve.VersionKey) ([]resolve.Version, error) {
    86  	client, err := c.clientForSystem(ctx, vk.System)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	return client.MatchingVersions(ctx, vk)
    91  }
    92  
    93  // AddRegistries adds registries to the MavenRegistryClient.
    94  func (c *CombinedNativeClient) AddRegistries(ctx context.Context, registries []Registry) error {
    95  	// TODO(#541): Currently only MavenRegistryClient supports adding registries.
    96  	// We might need to add support for PyPIRegistryClient.
    97  	// But this AddRegistries method should take a system as input,
    98  	// so that we can add registries to the corresponding client.
    99  	client, err := c.clientForSystem(ctx, resolve.Maven)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	regCl, ok := client.(ClientWithRegistries)
   104  	if !ok {
   105  		// Currently should not happen.
   106  		return nil
   107  	}
   108  	return regCl.AddRegistries(ctx, registries)
   109  }
   110  
   111  func (c *CombinedNativeClient) clientForSystem(ctx context.Context, sys resolve.System) (resolve.Client, error) {
   112  	c.mu.Lock()
   113  	defer c.mu.Unlock()
   114  
   115  	var err error
   116  	switch sys {
   117  	case resolve.Maven:
   118  		if c.mavenRegistryClient == nil {
   119  			c.mavenRegistryClient, err = NewMavenRegistryClient(ctx, c.opts.MavenRegistry, c.opts.LocalRegistry, c.opts.DisableGoogleAuth)
   120  			if err != nil {
   121  				return nil, err
   122  			}
   123  		}
   124  		return c.mavenRegistryClient, nil
   125  	case resolve.NPM:
   126  		if c.npmRegistryClient == nil {
   127  			c.npmRegistryClient, err = NewNPMRegistryClient(c.opts.ProjectDir)
   128  			if err != nil {
   129  				return nil, err
   130  			}
   131  		}
   132  		return c.npmRegistryClient, nil
   133  	case resolve.PyPI:
   134  		if c.pypiRegistryClient == nil {
   135  			c.pypiRegistryClient = NewPyPIRegistryClient(c.opts.PyPIRegistry, c.opts.LocalRegistry)
   136  		}
   137  		return c.pypiRegistryClient, nil
   138  	case resolve.UnknownSystem:
   139  		fallthrough
   140  	default:
   141  		return nil, fmt.Errorf("unsupported system: %v", sys)
   142  	}
   143  }