github.com/google/osv-scalibr@v0.4.1/clients/resolution/maven_registry_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  	"errors"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"deps.dev/util/maven"
    24  	"deps.dev/util/resolve"
    25  	"deps.dev/util/resolve/version"
    26  	"github.com/google/osv-scalibr/clients/datasource"
    27  	"github.com/google/osv-scalibr/internal/mavenutil"
    28  )
    29  
    30  // MavenRegistryClient is a client to fetch data from Maven registry.
    31  type MavenRegistryClient struct {
    32  	api *datasource.MavenRegistryAPIClient
    33  }
    34  
    35  // NewMavenRegistryClient makes a new MavenRegistryClient.
    36  func NewMavenRegistryClient(ctx context.Context, remote, local string, disableGoogleAuth bool) (*MavenRegistryClient, error) {
    37  	client, err := datasource.NewMavenRegistryAPIClient(ctx, datasource.MavenRegistry{URL: remote, ReleasesEnabled: true}, local, disableGoogleAuth)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	return &MavenRegistryClient{api: client}, nil
    42  }
    43  
    44  // NewMavenRegistryClientWithAPI makes a new MavenRegistryClient with the given Maven registry client.
    45  func NewMavenRegistryClientWithAPI(api *datasource.MavenRegistryAPIClient) *MavenRegistryClient {
    46  	if api == nil {
    47  		panic("NewMavenRegistryClientWithAPI: api must not be nil")
    48  	}
    49  	return &MavenRegistryClient{api: api}
    50  }
    51  
    52  // Version returns metadata of a version specified by the VersionKey.
    53  func (c *MavenRegistryClient) Version(ctx context.Context, vk resolve.VersionKey) (resolve.Version, error) {
    54  	g, a, found := strings.Cut(vk.Name, ":")
    55  	if !found {
    56  		return resolve.Version{}, fmt.Errorf("invalid Maven package name %s", vk.Name)
    57  	}
    58  	proj, err := c.api.GetProject(ctx, g, a, vk.Version)
    59  	if err != nil {
    60  		return resolve.Version{}, err
    61  	}
    62  
    63  	regs := make([]string, len(proj.Repositories))
    64  	// Repositories are served as dependency registries.
    65  	// https://github.com/google/deps.dev/blob/main/util/resolve/api.go#L106
    66  	for i, repo := range proj.Repositories {
    67  		regs[i] = "dep:" + string(repo.URL)
    68  	}
    69  	var attr version.AttrSet
    70  	if len(regs) > 0 {
    71  		attr.SetAttr(version.Registries, strings.Join(regs, "|"))
    72  	}
    73  
    74  	return resolve.Version{VersionKey: vk, AttrSet: attr}, nil
    75  }
    76  
    77  // Versions returns all the available versions of the package specified by the given PackageKey.
    78  // TODO: we should also include versions not listed in the metadata file
    79  // There exist versions in the repository but not listed in the metada file,
    80  // for example version 20030203.000550 of package commons-io:commons-io
    81  // https://repo1.maven.org/maven2/commons-io/commons-io/20030203.000550/.
    82  // A package may depend on such version if a soft requirement of this version
    83  // is declared.
    84  // We need to find out if there are such versions and include them in the
    85  // returned versions.
    86  func (c *MavenRegistryClient) Versions(ctx context.Context, pk resolve.PackageKey) ([]resolve.Version, error) {
    87  	if pk.System != resolve.Maven {
    88  		return nil, fmt.Errorf("wrong system: %v", pk.System)
    89  	}
    90  
    91  	g, a, found := strings.Cut(pk.Name, ":")
    92  	if !found {
    93  		return nil, fmt.Errorf("invalid Maven package name %s", pk.Name)
    94  	}
    95  	versions, err := c.api.GetVersions(ctx, g, a)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	vks := make([]resolve.Version, len(versions))
   101  	for i, v := range versions {
   102  		vks[i] = resolve.Version{
   103  			VersionKey: resolve.VersionKey{
   104  				PackageKey:  pk,
   105  				Version:     string(v),
   106  				VersionType: resolve.Concrete,
   107  			}}
   108  	}
   109  
   110  	return vks, nil
   111  }
   112  
   113  // Requirements returns requirements of a version specified by the VersionKey.
   114  func (c *MavenRegistryClient) Requirements(ctx context.Context, vk resolve.VersionKey) ([]resolve.RequirementVersion, error) {
   115  	if vk.System != resolve.Maven {
   116  		return nil, fmt.Errorf("wrong system: %v", vk.System)
   117  	}
   118  
   119  	g, a, found := strings.Cut(vk.Name, ":")
   120  	if !found {
   121  		return nil, fmt.Errorf("invalid Maven package name %s", vk.Name)
   122  	}
   123  	proj, err := c.api.GetProject(ctx, g, a, vk.Version)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	// Only merge default profiles by passing empty JDK and OS information.
   129  	if err := proj.MergeProfiles("", maven.ActivationOS{}); err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	// We need to merge parents for potential dependencies in parents.
   134  	if err := mavenutil.MergeParents(ctx, proj.Parent, &proj, mavenutil.Options{
   135  		Client: c.api,
   136  		// We should not add registries defined in dependencies pom.xml files.
   137  		AddRegistry:        false,
   138  		AllowLocal:         false,
   139  		InitialParentIndex: 1,
   140  	}); err != nil {
   141  		return nil, err
   142  	}
   143  	proj.ProcessDependencies(func(groupID, artifactID, version maven.String) (maven.DependencyManagement, error) {
   144  		return mavenutil.GetDependencyManagement(ctx, c.api, groupID, artifactID, version)
   145  	})
   146  
   147  	reqs := make([]resolve.RequirementVersion, 0, len(proj.Dependencies))
   148  	for _, d := range proj.Dependencies {
   149  		reqs = append(reqs, resolve.RequirementVersion{
   150  			VersionKey: resolve.VersionKey{
   151  				PackageKey: resolve.PackageKey{
   152  					System: resolve.Maven,
   153  					Name:   d.Name(),
   154  				},
   155  				VersionType: resolve.Requirement,
   156  				Version:     string(d.Version),
   157  			},
   158  			Type: resolve.MavenDepType(d, ""),
   159  		})
   160  	}
   161  
   162  	return reqs, nil
   163  }
   164  
   165  // MatchingVersions returns versions matching the requirement specified by the VersionKey.
   166  func (c *MavenRegistryClient) MatchingVersions(ctx context.Context, vk resolve.VersionKey) ([]resolve.Version, error) {
   167  	if vk.System != resolve.Maven {
   168  		return nil, fmt.Errorf("wrong system: %v", vk.System)
   169  	}
   170  
   171  	versions, err := c.Versions(ctx, vk.PackageKey)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	return resolve.MatchRequirement(vk, versions), nil
   177  }
   178  
   179  // AddRegistries adds registries to the MavenRegistryClient.
   180  func (c *MavenRegistryClient) AddRegistries(ctx context.Context, registries []Registry) error {
   181  	for _, reg := range registries {
   182  		specific, ok := reg.(datasource.MavenRegistry)
   183  		if !ok {
   184  			return errors.New("invalid Maven registry information")
   185  		}
   186  		if err := c.api.AddRegistry(ctx, specific); err != nil {
   187  			return err
   188  		}
   189  	}
   190  
   191  	return nil
   192  }