mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
refactor: unify package dependency resolution and improve PyPI version handling using registry adapter
This commit is contained in:
+16
-1
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/flows"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
@@ -31,6 +32,20 @@ func executeNpmFlow(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create npm package manager proxy: %s", err)
|
||||
}
|
||||
config, err := config.FromContext(ctx)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to get config: %s", err)
|
||||
}
|
||||
|
||||
return flows.Common(packageManager).Run(ctx, args)
|
||||
packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig()
|
||||
packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
|
||||
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create dependency resolver: %s", err)
|
||||
}
|
||||
|
||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args)
|
||||
}
|
||||
|
||||
+13
-1
@@ -5,6 +5,7 @@ import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/flows"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
@@ -32,6 +33,17 @@ func executePnpmFlow(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create pnpm package manager proxy: %s", err)
|
||||
}
|
||||
config, err := config.FromContext(ctx)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to get config: %s", err)
|
||||
}
|
||||
|
||||
return flows.Common(packageManager).Run(ctx, args)
|
||||
packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig()
|
||||
packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
|
||||
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||
|
||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args)
|
||||
}
|
||||
|
||||
+18
-10
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/flows"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -17,12 +18,7 @@ func NewPipCommand() *cobra.Command {
|
||||
Short: "Guard pip package manager",
|
||||
DisableFlagParsing: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
config, err := config.FromContext(cmd.Context())
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to get config: %s", err)
|
||||
}
|
||||
|
||||
err = executePipFlow(cmd.Context(), config, args)
|
||||
err := executePipFlow(cmd.Context(), args)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to execute pip flow: %s", err)
|
||||
}
|
||||
@@ -32,12 +28,24 @@ func NewPipCommand() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func executePipFlow(context context.Context, config config.Config, args []string) error {
|
||||
func executePipFlow(ctx context.Context, args []string) error {
|
||||
packageManager, err := packagemanager.NewPipPackageManager(packagemanager.DefaultPipPackageManagerConfig())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create pip package manager: %w", err)
|
||||
}
|
||||
cmd, _ := packageManager.ParseCommand(args)
|
||||
fmt.Println("Cmd: ", cmd.InstallTargets[0])
|
||||
return nil
|
||||
config, err := config.FromContext(ctx)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to get config: %s", err)
|
||||
}
|
||||
packageResolverConfig := packagemanager.NewDefaultPypiDependencyResolverConfig()
|
||||
packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
|
||||
packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create dependency resolver: %s", err)
|
||||
}
|
||||
|
||||
return flows.Common(packageManager, packageResolver, config).Run(ctx, args)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
diff --git a/cmd/npm/npm.go b/cmd/npm/npm.go
|
||||
index e89dc36..5e8f86f 100644
|
||||
--- a/cmd/npm/npm.go
|
||||
+++ b/cmd/npm/npm.go
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
+ "github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/flows"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
@@ -31,6 +32,20 @@ func executeNpmFlow(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create npm package manager proxy: %s", err)
|
||||
}
|
||||
+ config, err := config.FromContext(ctx)
|
||||
+ if err != nil {
|
||||
+ ui.Fatalf("Failed to get config: %s", err)
|
||||
+ }
|
||||
+
|
||||
+ packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig()
|
||||
+ packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
+ packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
+ packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
+
|
||||
+ packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||
+ if err != nil {
|
||||
+ ui.Fatalf("Failed to create dependency resolver: %s", err)
|
||||
+ }
|
||||
|
||||
- return flows.Common(packageManager).Run(ctx, args)
|
||||
+ return flows.Common(packageManager, packageResolver, config).Run(ctx, args)
|
||||
}
|
||||
diff --git a/cmd/npm/pnpm.go b/cmd/npm/pnpm.go
|
||||
index c161c0d..a010244 100644
|
||||
--- a/cmd/npm/pnpm.go
|
||||
+++ b/cmd/npm/pnpm.go
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
+ "github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/flows"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
@@ -32,6 +33,17 @@ func executePnpmFlow(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create pnpm package manager proxy: %s", err)
|
||||
}
|
||||
+ config, err := config.FromContext(ctx)
|
||||
+ if err != nil {
|
||||
+ ui.Fatalf("Failed to get config: %s", err)
|
||||
+ }
|
||||
+
|
||||
+ packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig()
|
||||
+ packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
+ packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
+ packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
+
|
||||
+ packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||
|
||||
- return flows.Common(packageManager).Run(ctx, args)
|
||||
+ return flows.Common(packageManager, packageResolver, config).Run(ctx, args)
|
||||
}
|
||||
diff --git a/cmd/pypi/pip.go b/cmd/pypi/pip.go
|
||||
index 56d03f7..4cc9a65 100644
|
||||
--- a/cmd/pypi/pip.go
|
||||
+++ b/cmd/pypi/pip.go
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/config"
|
||||
+ "github.com/safedep/pmg/internal/flows"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -17,12 +18,7 @@ func NewPipCommand() *cobra.Command {
|
||||
Short: "Guard pip package manager",
|
||||
DisableFlagParsing: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
- config, err := config.FromContext(cmd.Context())
|
||||
- if err != nil {
|
||||
- ui.Fatalf("Failed to get config: %s", err)
|
||||
- }
|
||||
-
|
||||
- err = executePipFlow(cmd.Context(), config, args)
|
||||
+ err := executePipFlow(cmd.Context(), args)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to execute pip flow: %s", err)
|
||||
}
|
||||
@@ -32,12 +28,24 @@ func NewPipCommand() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
-func executePipFlow(context context.Context, config config.Config, args []string) error {
|
||||
+func executePipFlow(ctx context.Context, args []string) error {
|
||||
packageManager, err := packagemanager.NewPipPackageManager(packagemanager.DefaultPipPackageManagerConfig())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create pip package manager: %w", err)
|
||||
}
|
||||
- cmd, _ := packageManager.ParseCommand(args)
|
||||
- fmt.Println("Cmd: ", cmd.InstallTargets[0])
|
||||
- return nil
|
||||
+ config, err := config.FromContext(ctx)
|
||||
+ if err != nil {
|
||||
+ ui.Fatalf("Failed to get config: %s", err)
|
||||
+ }
|
||||
+ packageResolverConfig := packagemanager.NewDefaultPypiDependencyResolverConfig()
|
||||
+ packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
+ packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
+ packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
+
|
||||
+ packageResolver, err := packagemanager.NewPypiDependencyResolver(packageResolverConfig)
|
||||
+ if err != nil {
|
||||
+ ui.Fatalf("Failed to create dependency resolver: %s", err)
|
||||
+ }
|
||||
+
|
||||
+ return flows.Common(packageManager, packageResolver, config).Run(ctx, args)
|
||||
}
|
||||
diff --git a/internal/flows/common.go b/internal/flows/common.go
|
||||
index a8cbaae..dbc82ca 100644
|
||||
--- a/internal/flows/common.go
|
||||
+++ b/internal/flows/common.go
|
||||
@@ -11,37 +11,26 @@ import (
|
||||
)
|
||||
|
||||
type commonFlow struct {
|
||||
- pm packagemanager.PackageManager
|
||||
+ pm packagemanager.PackageManager
|
||||
+ packageResolver packagemanager.PackageResolver
|
||||
+ config config.Config
|
||||
}
|
||||
|
||||
// Creates a common flow of execution for all package managers. This should work for most
|
||||
// of the cases unless a package manager has its own unique requirements. Configuration
|
||||
// should be passed through the context (Global Config)
|
||||
-func Common(pm packagemanager.PackageManager) *commonFlow {
|
||||
+func Common(pm packagemanager.PackageManager, pkgResolver packagemanager.PackageResolver, config config.Config) *commonFlow {
|
||||
return &commonFlow{
|
||||
- pm: pm,
|
||||
+ pm: pm,
|
||||
+ packageResolver: pkgResolver,
|
||||
+ config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *commonFlow) Run(ctx context.Context, args []string) error {
|
||||
- config, err := config.FromContext(ctx)
|
||||
- if err != nil {
|
||||
- ui.Fatalf("Failed to get config: %s", err)
|
||||
- }
|
||||
-
|
||||
- packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig()
|
||||
- packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
- packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
- packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
-
|
||||
- packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||
- if err != nil {
|
||||
- ui.Fatalf("Failed to create dependency resolver: %s", err)
|
||||
- }
|
||||
-
|
||||
var analyzers []analyzer.PackageVersionAnalyzer
|
||||
|
||||
- if config.Paranoid {
|
||||
+ if f.config.Paranoid {
|
||||
malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig())
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create malware analyzer: %s", err)
|
||||
@@ -65,9 +54,9 @@ func (f *commonFlow) Run(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
guardConfig := guard.DefaultPackageManagerGuardConfig()
|
||||
- guardConfig.DryRun = config.DryRun
|
||||
+ guardConfig.DryRun = f.config.DryRun
|
||||
|
||||
- proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, packageResolver, analyzers, interaction)
|
||||
+ proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create package manager guard: %s", err)
|
||||
}
|
||||
diff --git a/packagemanager/dependency_resolver.go b/packagemanager/dependency_resolver.go
|
||||
index 1f3c7a1..2d48a8f 100644
|
||||
--- a/packagemanager/dependency_resolver.go
|
||||
+++ b/packagemanager/dependency_resolver.go
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// Contract for a function that implements ecosystem specific version
|
||||
// resolver from a version range specification.
|
||||
-type versionSpecResolver func(version string) string
|
||||
+type versionSpecResolver func(packageName, version string) string
|
||||
|
||||
type dependencyResolverConfig struct {
|
||||
IncludeDevDependencies bool
|
||||
@@ -38,7 +38,7 @@ func newDependencyResolver(client packageregistry.Client, config dependencyResol
|
||||
|
||||
if versionSpecResolver == nil {
|
||||
// Default version spec resolver
|
||||
- versionSpecResolver = func(version string) string {
|
||||
+ versionSpecResolver = func(packageName, version string) string {
|
||||
return version
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (r *dependencyResolver) resolvePackageDependenciesConcurrent(
|
||||
Ecosystem: packageVersion.GetPackage().GetEcosystem(),
|
||||
Name: dependency.Name,
|
||||
},
|
||||
- Version: r.versionSpecResolver(dependency.VersionSpec),
|
||||
+ Version: r.versionSpecResolver(dependency.Name, dependency.VersionSpec),
|
||||
})
|
||||
}
|
||||
|
||||
diff --git a/packagemanager/npm_resolver.go b/packagemanager/npm_resolver.go
|
||||
index 68293a4..dd46c2c 100644
|
||||
--- a/packagemanager/npm_resolver.go
|
||||
+++ b/packagemanager/npm_resolver.go
|
||||
@@ -78,7 +78,9 @@ func (r *npmDependencyResolver) ResolveDependencies(ctx context.Context,
|
||||
TransitiveDepth: r.config.TransitiveDepth,
|
||||
FailFast: r.config.FailFast,
|
||||
MaxConcurrency: r.config.MaxConcurrency,
|
||||
- }, npmCleanVersion)
|
||||
+ }, func(packageName, version string) string {
|
||||
+ return npmCleanVersion(version)
|
||||
+ })
|
||||
|
||||
return resolver.resolveDependencies(ctx, packageVersion)
|
||||
}
|
||||
diff --git a/packagemanager/pypi.go b/packagemanager/pypi.go
|
||||
index 615a664..08a79cf 100644
|
||||
--- a/packagemanager/pypi.go
|
||||
+++ b/packagemanager/pypi.go
|
||||
@@ -83,8 +83,6 @@ func (pip *pipPackageManager) ParseCommand(args []string) (*ParsedCommand, error
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Printf("Package Name: %s Version: %s\n", packageName, version)
|
||||
-
|
||||
installTargets = append(installTargets, &PackageInstallTarget{
|
||||
PackageVersion: &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
diff --git a/packagemanager/pypi_resolver.go b/packagemanager/pypi_resolver.go
|
||||
index dd6d22a..5e952bb 100644
|
||||
--- a/packagemanager/pypi_resolver.go
|
||||
+++ b/packagemanager/pypi_resolver.go
|
||||
@@ -2,11 +2,8 @@ package packagemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
- "encoding/json"
|
||||
"fmt"
|
||||
- "net/http"
|
||||
"strings"
|
||||
- "time"
|
||||
|
||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
||||
"github.com/Masterminds/semver"
|
||||
@@ -62,6 +59,13 @@ func (p *pypiDependencyResolver) ResolveDependencies(ctx context.Context, pkg *p
|
||||
TransitiveDepth: p.config.TransitiveDepth,
|
||||
FailFast: p.config.FailFast,
|
||||
MaxConcurrency: p.config.MaxConcurrency,
|
||||
+ }, func(packageName, version string) string {
|
||||
+ ver, err := pipGetMatchingVersion(packageName, version)
|
||||
+ if err != nil {
|
||||
+ log.Debugf("error getting matching version for %s@%s", packageName, version)
|
||||
+ return ""
|
||||
+ }
|
||||
+ return ver
|
||||
})
|
||||
|
||||
return resolver.resolveDependencies(ctx, pkg)
|
||||
@@ -74,6 +78,7 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg *
|
||||
}
|
||||
|
||||
pkgInfo, err := pd.GetPackage(pkg.Name)
|
||||
+ fmt.Println("Package Info version: ", pkgInfo.LatestVersion, " Error: ", err)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get package: %w", err)
|
||||
}
|
||||
@@ -85,13 +90,6 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg *
|
||||
}, nil
|
||||
}
|
||||
|
||||
-// PyPIPackage represents the package information from PyPI
|
||||
-type PyPIPackage struct {
|
||||
- Releases map[string]any `json:"releases"`
|
||||
-}
|
||||
-
|
||||
-var httpClient = &http.Client{Timeout: 10 * time.Second}
|
||||
-
|
||||
func pipGetMatchingVersion(packageName, versionConstraint string) (string, error) {
|
||||
// Already a exact version
|
||||
if strings.HasPrefix(versionConstraint, "==") {
|
||||
@@ -103,8 +101,18 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error
|
||||
versionConstraint = pipConvertCompatibleRelease(versionConstraint)
|
||||
}
|
||||
|
||||
+ registry, err := packageregistry.NewPypiAdapter()
|
||||
+ if err != nil {
|
||||
+ return "", fmt.Errorf("failed to create pypi adapter: %w", err)
|
||||
+ }
|
||||
+
|
||||
+ pd, err := registry.PackageDiscovery()
|
||||
+ if err != nil {
|
||||
+ return "", fmt.Errorf("failed to get package discovery: %w", err)
|
||||
+ }
|
||||
+
|
||||
// Get package info from PyPI
|
||||
- pkg, err := pipFetchPackageVersionsInfo(packageName)
|
||||
+ pkg, err := pd.GetPackage(packageName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -116,7 +124,7 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error
|
||||
}
|
||||
|
||||
// Get valid versions and find best match
|
||||
- bestMatch, err := findBestMatchingVersion(pkg.Releases, constraint)
|
||||
+ bestMatch, err := findBestMatchingVersion(pkg.Versions, constraint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("no version matches constraint %q: %w", versionConstraint, err)
|
||||
}
|
||||
@@ -124,36 +132,15 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error
|
||||
return bestMatch.Original(), nil
|
||||
}
|
||||
|
||||
-// pipFetchPackageVersionsInfo retrieves package information from PyPI
|
||||
-func pipFetchPackageVersionsInfo(packageName string) (*PyPIPackage, error) {
|
||||
- url := fmt.Sprintf("https://pypi.org/pypi/%s/json", packageName)
|
||||
- resp, err := httpClient.Get(url)
|
||||
- if err != nil {
|
||||
- return nil, fmt.Errorf("failed to fetch package info: %w", err)
|
||||
- }
|
||||
- defer resp.Body.Close()
|
||||
-
|
||||
- if resp.StatusCode != 200 {
|
||||
- return nil, fmt.Errorf("package not found or HTTP error: %d", resp.StatusCode)
|
||||
- }
|
||||
-
|
||||
- var pypiPkg PyPIPackage
|
||||
- if err := json.NewDecoder(resp.Body).Decode(&pypiPkg); err != nil {
|
||||
- return nil, fmt.Errorf("failed to parse JSON: %w", err)
|
||||
- }
|
||||
-
|
||||
- return &pypiPkg, nil
|
||||
-}
|
||||
-
|
||||
-func findBestMatchingVersion(releases map[string]any, constraint *semver.Constraints) (*semver.Version, error) {
|
||||
+func findBestMatchingVersion(releases []packageregistry.PackageVersionInfo, constraint *semver.Constraints) (*semver.Version, error) {
|
||||
if len(releases) == 0 {
|
||||
return nil, fmt.Errorf("no versions available")
|
||||
}
|
||||
|
||||
var bestMatch *semver.Version
|
||||
// We'll iterate once through all versions
|
||||
- for v := range releases {
|
||||
- ver, err := semver.NewVersion(v)
|
||||
+ for _, v := range releases {
|
||||
+ ver, err := semver.NewVersion(v.Version)
|
||||
if err != nil {
|
||||
continue // Skip invalid versions
|
||||
}
|
||||
+10
-21
@@ -11,37 +11,26 @@ import (
|
||||
)
|
||||
|
||||
type commonFlow struct {
|
||||
pm packagemanager.PackageManager
|
||||
pm packagemanager.PackageManager
|
||||
packageResolver packagemanager.PackageResolver
|
||||
config config.Config
|
||||
}
|
||||
|
||||
// Creates a common flow of execution for all package managers. This should work for most
|
||||
// of the cases unless a package manager has its own unique requirements. Configuration
|
||||
// should be passed through the context (Global Config)
|
||||
func Common(pm packagemanager.PackageManager) *commonFlow {
|
||||
func Common(pm packagemanager.PackageManager, pkgResolver packagemanager.PackageResolver, config config.Config) *commonFlow {
|
||||
return &commonFlow{
|
||||
pm: pm,
|
||||
pm: pm,
|
||||
packageResolver: pkgResolver,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *commonFlow) Run(ctx context.Context, args []string) error {
|
||||
config, err := config.FromContext(ctx)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to get config: %s", err)
|
||||
}
|
||||
|
||||
packageResolverConfig := packagemanager.NewDefaultNpmDependencyResolverConfig()
|
||||
packageResolverConfig.IncludeTransitiveDependencies = config.Transitive
|
||||
packageResolverConfig.TransitiveDepth = config.TransitiveDepth
|
||||
packageResolverConfig.IncludeDevDependencies = config.IncludeDevDependencies
|
||||
|
||||
packageResolver, err := packagemanager.NewNpmDependencyResolver(packageResolverConfig)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create dependency resolver: %s", err)
|
||||
}
|
||||
|
||||
var analyzers []analyzer.PackageVersionAnalyzer
|
||||
|
||||
if config.Paranoid {
|
||||
if f.config.Paranoid {
|
||||
malysisActiveScanAnalyzer, err := analyzer.NewMalysisActiveScanAnalyzer(analyzer.DefaultMalysisActiveScanAnalyzerConfig())
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create malware analyzer: %s", err)
|
||||
@@ -65,9 +54,9 @@ func (f *commonFlow) Run(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
guardConfig := guard.DefaultPackageManagerGuardConfig()
|
||||
guardConfig.DryRun = config.DryRun
|
||||
guardConfig.DryRun = f.config.DryRun
|
||||
|
||||
proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, packageResolver, analyzers, interaction)
|
||||
proxy, err := guard.NewPackageManagerGuard(guardConfig, f.pm, f.packageResolver, analyzers, interaction)
|
||||
if err != nil {
|
||||
ui.Fatalf("Failed to create package manager guard: %s", err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// Contract for a function that implements ecosystem specific version
|
||||
// resolver from a version range specification.
|
||||
type versionSpecResolver func(version string) string
|
||||
type versionSpecResolver func(packageName, version string) string
|
||||
|
||||
type dependencyResolverConfig struct {
|
||||
IncludeDevDependencies bool
|
||||
@@ -38,7 +38,7 @@ func newDependencyResolver(client packageregistry.Client, config dependencyResol
|
||||
|
||||
if versionSpecResolver == nil {
|
||||
// Default version spec resolver
|
||||
versionSpecResolver = func(version string) string {
|
||||
versionSpecResolver = func(packageName, version string) string {
|
||||
return version
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (r *dependencyResolver) resolvePackageDependenciesConcurrent(
|
||||
Ecosystem: packageVersion.GetPackage().GetEcosystem(),
|
||||
Name: dependency.Name,
|
||||
},
|
||||
Version: r.versionSpecResolver(dependency.VersionSpec),
|
||||
Version: r.versionSpecResolver(dependency.Name, dependency.VersionSpec),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ func (r *npmDependencyResolver) ResolveDependencies(ctx context.Context,
|
||||
TransitiveDepth: r.config.TransitiveDepth,
|
||||
FailFast: r.config.FailFast,
|
||||
MaxConcurrency: r.config.MaxConcurrency,
|
||||
}, npmCleanVersion)
|
||||
}, func(packageName, version string) string {
|
||||
return npmCleanVersion(version)
|
||||
})
|
||||
|
||||
return resolver.resolveDependencies(ctx, packageVersion)
|
||||
}
|
||||
|
||||
@@ -83,8 +83,6 @@ func (pip *pipPackageManager) ParseCommand(args []string) (*ParsedCommand, error
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Package Name: %s Version: %s\n", packageName, version)
|
||||
|
||||
installTargets = append(installTargets, &PackageInstallTarget{
|
||||
PackageVersion: &packagev1.PackageVersion{
|
||||
Package: &packagev1.Package{
|
||||
|
||||
@@ -2,11 +2,8 @@ package packagemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
|
||||
"github.com/Masterminds/semver"
|
||||
@@ -62,6 +59,13 @@ func (p *pypiDependencyResolver) ResolveDependencies(ctx context.Context, pkg *p
|
||||
TransitiveDepth: p.config.TransitiveDepth,
|
||||
FailFast: p.config.FailFast,
|
||||
MaxConcurrency: p.config.MaxConcurrency,
|
||||
}, func(packageName, version string) string {
|
||||
ver, err := pipGetMatchingVersion(packageName, version)
|
||||
if err != nil {
|
||||
log.Debugf("error getting matching version for %s@%s", packageName, version)
|
||||
return ""
|
||||
}
|
||||
return ver
|
||||
})
|
||||
|
||||
return resolver.resolveDependencies(ctx, pkg)
|
||||
@@ -74,6 +78,7 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg *
|
||||
}
|
||||
|
||||
pkgInfo, err := pd.GetPackage(pkg.Name)
|
||||
fmt.Println("Package Info version: ", pkgInfo.LatestVersion, " Error: ", err)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get package: %w", err)
|
||||
}
|
||||
@@ -85,13 +90,6 @@ func (p *pypiDependencyResolver) ResolveLatestVersion(ctx context.Context, pkg *
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PyPIPackage represents the package information from PyPI
|
||||
type PyPIPackage struct {
|
||||
Releases map[string]any `json:"releases"`
|
||||
}
|
||||
|
||||
var httpClient = &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
func pipGetMatchingVersion(packageName, versionConstraint string) (string, error) {
|
||||
// Already a exact version
|
||||
if strings.HasPrefix(versionConstraint, "==") {
|
||||
@@ -103,8 +101,18 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error
|
||||
versionConstraint = pipConvertCompatibleRelease(versionConstraint)
|
||||
}
|
||||
|
||||
registry, err := packageregistry.NewPypiAdapter()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create pypi adapter: %w", err)
|
||||
}
|
||||
|
||||
pd, err := registry.PackageDiscovery()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get package discovery: %w", err)
|
||||
}
|
||||
|
||||
// Get package info from PyPI
|
||||
pkg, err := pipFetchPackageVersionsInfo(packageName)
|
||||
pkg, err := pd.GetPackage(packageName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -116,7 +124,7 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error
|
||||
}
|
||||
|
||||
// Get valid versions and find best match
|
||||
bestMatch, err := findBestMatchingVersion(pkg.Releases, constraint)
|
||||
bestMatch, err := findBestMatchingVersion(pkg.Versions, constraint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("no version matches constraint %q: %w", versionConstraint, err)
|
||||
}
|
||||
@@ -124,36 +132,15 @@ func pipGetMatchingVersion(packageName, versionConstraint string) (string, error
|
||||
return bestMatch.Original(), nil
|
||||
}
|
||||
|
||||
// pipFetchPackageVersionsInfo retrieves package information from PyPI
|
||||
func pipFetchPackageVersionsInfo(packageName string) (*PyPIPackage, error) {
|
||||
url := fmt.Sprintf("https://pypi.org/pypi/%s/json", packageName)
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch package info: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("package not found or HTTP error: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var pypiPkg PyPIPackage
|
||||
if err := json.NewDecoder(resp.Body).Decode(&pypiPkg); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse JSON: %w", err)
|
||||
}
|
||||
|
||||
return &pypiPkg, nil
|
||||
}
|
||||
|
||||
func findBestMatchingVersion(releases map[string]any, constraint *semver.Constraints) (*semver.Version, error) {
|
||||
func findBestMatchingVersion(releases []packageregistry.PackageVersionInfo, constraint *semver.Constraints) (*semver.Version, error) {
|
||||
if len(releases) == 0 {
|
||||
return nil, fmt.Errorf("no versions available")
|
||||
}
|
||||
|
||||
var bestMatch *semver.Version
|
||||
// We'll iterate once through all versions
|
||||
for v := range releases {
|
||||
ver, err := semver.NewVersion(v)
|
||||
for _, v := range releases {
|
||||
ver, err := semver.NewVersion(v.Version)
|
||||
if err != nil {
|
||||
continue // Skip invalid versions
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user