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 }