ecosystems

package
v1.15.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

Ecosystems Package

The ecosystems package provides a unified plugin interface for discovering and building dependency graphs across multiple package managers and programming language ecosystems.

Purpose

Modern software projects use diverse package managers (pip, npm, maven, gradle, etc.) to manage dependencies. Each ecosystem has its own format for declaring and resolving dependencies. This package standardizes how dependency graphs are discovered, built, and represented, regardless of the underlying package manager.

Key Goals
  1. Unified Interface: Provide a consistent API for dependency graph generation across all package managers
  2. Extensibility: Make it easy to add support for new ecosystems without modifying core code
  3. Type Safety: Leverage Go's type system to catch errors at compile time
  4. Testability: Enable dependency injection and mocking for robust testing
  5. Ecosystem Isolation: Keep ecosystem-specific logic contained within dedicated plugins

Architecture

Core Interface

Every plugin implements the SCAPlugin interface to:

  • Discover dependency manifests in a directory (e.g., requirements.txt, package.json)
  • Resolve dependencies using the ecosystem's native tooling
  • Build a standardized dependency graph representation
  • Return results with metadata about the analysis

Data Structures

DepGraph: Snyk Dependency Graph Format

The SCAResult.DepGraph field uses the standard Snyk dependency graph format from github.com/snyk/dep-graph/go/pkg/depgraph:

import "github.com/snyk/dep-graph/go/pkg/depgraph"

type SCAResult struct {
    DepGraph          *depgraph.DepGraph         `json:"depGraph,omitempty"`
    ProjectDescriptor identity.ProjectDescriptor `json:"projectDescriptor"`
    ResolverMetadata  *ResolverMetadata          `json:"meta,omitempty"`
    Error             error                      `json:"error,omitempty"`
}
DepGraph Structure

The depgraph.DepGraph type provides a standardized format for representing dependency graphs:

{
  "schemaVersion": "1.3.0",
  "pkgManager": {
    "name": "pip"
  },
  "pkgs": [
    {
      "id": "root@0.0.0",
      "info": {
        "name": "root",
        "version": "0.0.0"
      }
    },
    {
      "id": "requests@2.31.0",
      "info": {
        "name": "requests",
        "version": "2.31.0"
      }
    }
  ],
  "graph": {
    "rootNodeId": "root-node",
    "nodes": [
      {
        "nodeId": "root-node",
        "pkgId": "root@0.0.0",
        "deps": [
          { "nodeId": "requests@2.31.0" }
        ]
      },
      {
        "nodeId": "requests@2.31.0",
        "pkgId": "requests@2.31.0",
        "deps": []
      }
    ]
  }
}
Key Components
Field Description
schemaVersion Version of the dep-graph schema (e.g., "1.3.0")
pkgManager Package manager info with name (e.g., "pip", "npm")
pkgs Array of all packages with id and info (name, version)
graph.rootNodeId ID of the root node in the graph
graph.nodes Array of nodes, each with nodeId, pkgId, and deps
Building a DepGraph

Use the depgraph.Builder to construct dependency graphs:

import "github.com/snyk/dep-graph/go/pkg/depgraph"

// Create a builder with package manager and root package info
builder, err := depgraph.NewBuilder(
    &depgraph.PkgManager{Name: "pip"},
    &depgraph.PkgInfo{Name: "root", Version: "0.0.0"},
)
if err != nil {
    return nil, err
}

// Add package nodes
builder.AddNode("requests@2.31.0", &depgraph.PkgInfo{
    Name:    "requests",
    Version: "2.31.0",
})

// Connect dependencies
rootNode := builder.GetRootNode()
err = builder.ConnectNodes(rootNode.NodeID, "requests@2.31.0")
if err != nil {
    return nil, err
}

// Build the final graph
depGraph := builder.Build()
SCAResult: Analysis Output
type SCAResult struct {
    DepGraph          *depgraph.DepGraph         `json:"depGraph,omitempty"`
    ProjectDescriptor identity.ProjectDescriptor `json:"projectDescriptor"`
    ResolverMetadata  *ResolverMetadata          `json:"meta,omitempty"`
    Error             error                      `json:"error,omitempty"`
}

Each result contains:

  • DepGraph: The complete dependency graph using Snyk's standard format
  • ProjectDescriptor: Project identity information (type, target file, runtime)
  • ResolverMetadata: Information about the resolver/plugin that performed the analysis
  • Error: Any error encountered during analysis (optional)
ProjectDescriptor
type ProjectDescriptor struct {
    Identity ProjectIdentity `json:"identity"`
}

type ProjectIdentity struct {
    ProjectType       string  `json:"type,omitempty"`           // Project type (e.g., "npm", "maven", "pip")
    TargetFile        *string `json:"targetFile,omitempty"`     // Manifest/build file path
    TargetRuntime     *string `json:"targetRuntime,omitempty"`  // Runtime environment
    RootComponentName string  `json:"rootComponentName,omitempty"` // Root component name
}

The ProjectDescriptor contains project identity information that uniquely identifies what was analyzed. This includes the project type (ecosystem), the specific manifest file, and runtime details.

ResolverMetadata
type ResolverMetadata struct {
    PluginName       string            `json:"pluginName"`       // Name of the plugin/resolver
    VersionBuildInfo map[string]string `json:"versionBuildInfo"` // Version and build information
}

Standard keys are available in the metadata package:

import "github.com/snyk/cli-extension-dep-graph/pkg/ecosystems/metadata"

// Available keys:
const (
    // Gradle ecosystem
    metadata.GradleVersion  = "gradleVersion"
    metadata.JavaVersion    = "javaVersion" 
    
    // Python ecosystem
    metadata.PythonVersion  = "pythonVersion"
    
    // General build info
    metadata.BuildTimestamp = "buildTimestamp"
)

Example usage:

import "github.com/snyk/cli-extension-dep-graph/pkg/ecosystems/metadata"

resolverMetadata := ResolverMetadata{
    PluginName: "gradle",
    VersionBuildInfo: map[string]string{
        metadata.GradleVersion:  "8.5",
        metadata.JavaVersion:    "17.0.1", 
        metadata.BuildTimestamp: "2024-01-01T12:00:00Z",
    },
}

Plugins return PluginResult, where:

  • Results contains depgraphs and other data. Multiple findings are permitted to allow for workspaces and other scenarios where more than one project are found within an ecosytem.
  • ProcessedFiles contains files that other plugins should not handle (either because the plugin has processed them directly, e.g. uv.lock for uv, or because it is associated with the handled project, e.g. pyproject.toml or requirements.txt associated with a uv project).

Usage Examples

Basic Usage
import (
    "context"
    "fmt"
    "github.com/snyk/cli-extension-dep-graph/pkg/ecosystems"
    "github.com/snyk/cli-extension-dep-graph/pkg/ecosystems/python/pip"
)

func main() {
    plugin := &pip.Plugin{}
    options := ecosystems.NewPluginOptions().
        WithTargetFile("requirements.txt")
    
    result, err := plugin.BuildDepGraphsFromDir(
        context.Background(),
        logger.Nop(),
        "/path/to/python/project",
        options,
    )
    if err != nil {
        // Handle error
    }
    
    for _, scaResult := range result.Results {
        if scaResult.Error != nil {
            fmt.Printf("Error analyzing with %s: %v\n", scaResult.ResolverMetadata.PluginName, scaResult.Error)
            continue
        }

        fmt.Printf("Package Manager: %s\n", scaResult.DepGraph.PkgManager.Name)
        fmt.Printf("Total Packages: %d\n", len(scaResult.DepGraph.Pkgs))
    }
}
Analyzing All Projects
options := ecosystems.NewPluginOptions().
    WithAllProjects(true)

result, err := plugin.BuildDepGraphsFromDir(ctx, logger.Nop(), "/path/to/monorepo", options)
// Returns results for all requirements.txt files found
Handling Errors in Results
for _, result := range result.Results {
    if result.Error != nil {
        // Individual file failed, but others may have succeeded
        log.Printf("Failed to analyze with %s: %v", result.ResolverMetadata.PluginName, result.Error)
        continue
    }
    
    // Process successful result
    processDepGraph(result.DepGraph)
}

Adding a New Plugin

To add support for a new ecosystem:

  1. Create package directory:

    pkg/ecosystems/<ecosystem>/<tool>/
    
  2. Implement the interface:

    package tool
    
    import (
        "context"
        "github.com/snyk/cli-extension-dep-graph/pkg/ecosystems"
        "github.com/snyk/cli-extension-dep-graph/pkg/ecosystems/logger"
    )
    
    type Plugin struct {
        // Plugin-specific fields
    }
    
    var _ ecosystems.SCAPlugin = (*Plugin)(nil)
    
    func (p *Plugin) BuildDepGraphsFromDir(
        ctx context.Context,
        log logger.Logger,
        dir string,
        options *ecosystems.SCAPluginOptions,
    ) (*ecosystems.PluginResult, error) {
        // 1. Discover manifest files in dir
        // 2. Resolve dependencies using ecosystem tooling
        // 3. Build depgraph.DepGraph using the builder
        // 4. Return PluginResult with results and processed files
    }
    
  3. Add ecosystem-specific options (if needed):

    // In options.go
    type MyEcosystemOptions struct {
        SpecificOption string
    }
    
    type SCAPluginOptions struct {
        Global      GlobalOptions
        Python      *PythonOptions
        MyEcosystem *MyEcosystemOptions  // Add here
    }
    
  4. Write tests:

    func TestPlugin_BuildDepGraphsFromDir(t *testing.T) {
        plugin := &Plugin{}
        options := ecosystems.NewPluginOptions()
    
        result, err := plugin.BuildDepGraphsFromDir(context.Background(), logger.Nop(), "./testdata", options)
        assert.NoError(t, err)
        assert.Len(t, result.Results, 1)
        assert.NotNil(t, result.Results[0].DepGraph)
        assert.Equal(t, "mymanager", result.Results[0].DepGraph.PkgManager.Name)
    }
    

Design Principles

  1. Single Responsibility: Each plugin focuses only on its ecosystem
  2. Dependency Inversion: Depend on abstractions (interfaces), not concrete implementations
  3. Open/Closed: Open for extension (new plugins), closed for modification (core interface)
  4. Interface Segregation: Keep interfaces minimal and focused
  5. Don't Repeat Yourself: Common functionality should be extracted to shared utilities

References

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CommaSeparatedString added in v0.22.0

type CommaSeparatedString []string

CommaSeparatedString is a custom type that parses comma-separated values.

func (*CommaSeparatedString) UnmarshalText added in v0.22.0

func (c *CommaSeparatedString) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

type GlobalOptions

type GlobalOptions struct {
	TargetFile                    *string              `arg:"--target-file"`
	AllProjects                   bool                 `arg:"--all-projects"`
	IncludeDev                    bool                 `arg:"--dev,-d"`
	Exclude                       CommaSeparatedString `arg:"--exclude"`
	ExcludePaths                  CommaSeparatedString `arg:"--exclude-paths"`
	FailFast                      bool                 `arg:"--fail-fast"`
	AllowOutOfSync                bool                 // Derived from --strict-out-of-sync (inverted); parsed in NewPluginOptionsFromRawFlags.
	ForceSingleGraph              bool                 `arg:"--force-single-graph"`
	ForceIncludeWorkspacePackages bool                 `arg:"--internal-uv-workspace-packages"`
	ProjectName                   *string              `arg:"--project-name"`
	RawFlags                      []string
}

GlobalOptions contains options that apply globally across all SCA plugins.

type GradleOptions added in v1.9.0

type GradleOptions struct {
	// ConfigurationMatching is a regex to select only matching Gradle configurations.
	ConfigurationMatching string `arg:"--configuration-matching"`
	// SubProject restricts scanning to a single named Gradle sub-project.
	SubProject string `arg:"--gradle-sub-project"`
	// AllSubProjects scans all sub-projects in a multi-project build.
	AllSubProjects bool `arg:"--all-sub-projects"`
	// InitScript overrides the built-in init script with a user-supplied path.
	InitScript string `arg:"--init-script"`
	// SkipWrapper bypasses gradlew discovery and forces use of the gradle command.
	SkipWrapper bool `arg:"--gradle-skip-wrapper"`
}

GradleOptions contains Gradle-specific options for dependency graph generation.

type PluginResult added in v0.32.1

type PluginResult struct {
	Results        []SCAResult `json:"results"`
	ProcessedFiles []string    `json:"processedFiles"`
}

type PythonOptions

type PythonOptions struct {
	NoBuildIsolation bool `arg:"--no-build-isolation"`
}

PythonOptions contains Python-specific options for dependency graph generation.

type ResolverMetadata added in v1.9.1

type ResolverMetadata struct {
	PluginName       string            `json:"pluginName,omitempty"`
	VersionBuildInfo map[string]string `json:"versionBuildInfo,omitempty"`
}

type SCAPlugin

type SCAPlugin interface {
	BuildDepGraphsFromDir(ctx context.Context, log logger.Logger, dir string, options *SCAPluginOptions) (*PluginResult, error)
	GetName() string
}

SCAPlugin defines the interface for SCA plugins that build dependency graphs from a directory containing project files.

type SCAPluginOptions

type SCAPluginOptions struct {
	Global GlobalOptions
	Python PythonOptions
	Gradle GradleOptions
}

SCAPluginOptions contains configuration options for SCA plugins, including global settings and language-specific options.

func NewPluginOptions

func NewPluginOptions() *SCAPluginOptions

func NewPluginOptionsFromRawFlags added in v0.19.0

func NewPluginOptionsFromRawFlags(rawFlags []string) (*SCAPluginOptions, error)

func (*SCAPluginOptions) WithAllProjects added in v0.3.0

func (o *SCAPluginOptions) WithAllProjects(allProjects bool) *SCAPluginOptions

func (*SCAPluginOptions) WithAllowOutOfSync added in v0.32.3

func (o *SCAPluginOptions) WithAllowOutOfSync(allowOutOfSync bool) *SCAPluginOptions

func (*SCAPluginOptions) WithExclude added in v0.22.0

func (o *SCAPluginOptions) WithExclude(exclude []string) *SCAPluginOptions

func (*SCAPluginOptions) WithExcludePaths added in v1.15.0

func (o *SCAPluginOptions) WithExcludePaths(excludePaths []string) *SCAPluginOptions

func (*SCAPluginOptions) WithFailFast added in v0.32.3

func (o *SCAPluginOptions) WithFailFast(failFast bool) *SCAPluginOptions

func (*SCAPluginOptions) WithForceIncludeWorkspacePackages added in v0.32.3

func (o *SCAPluginOptions) WithForceIncludeWorkspacePackages(forceIncludeWorkspacePackages bool) *SCAPluginOptions

func (*SCAPluginOptions) WithForceSingleGraph added in v0.32.3

func (o *SCAPluginOptions) WithForceSingleGraph(forceSingleGraph bool) *SCAPluginOptions

func (*SCAPluginOptions) WithGradleAllSubProjects added in v1.9.0

func (o *SCAPluginOptions) WithGradleAllSubProjects(all bool) *SCAPluginOptions

func (*SCAPluginOptions) WithGradleConfigurationMatching added in v1.9.0

func (o *SCAPluginOptions) WithGradleConfigurationMatching(pattern string) *SCAPluginOptions

func (*SCAPluginOptions) WithGradleInitScript added in v1.9.0

func (o *SCAPluginOptions) WithGradleInitScript(initScript string) *SCAPluginOptions

func (*SCAPluginOptions) WithGradleSkipWrapper added in v1.9.0

func (o *SCAPluginOptions) WithGradleSkipWrapper(skipWrapper bool) *SCAPluginOptions

func (*SCAPluginOptions) WithGradleSubProject added in v1.9.0

func (o *SCAPluginOptions) WithGradleSubProject(subProject string) *SCAPluginOptions

func (*SCAPluginOptions) WithIncludeDev added in v0.17.3

func (o *SCAPluginOptions) WithIncludeDev(includeDev bool) *SCAPluginOptions

func (*SCAPluginOptions) WithNoBuildIsolation added in v0.13.0

func (o *SCAPluginOptions) WithNoBuildIsolation(noBuildIsolation bool) *SCAPluginOptions

func (*SCAPluginOptions) WithProjectName added in v1.3.0

func (o *SCAPluginOptions) WithProjectName(projectName string) *SCAPluginOptions

func (*SCAPluginOptions) WithRawFlags added in v0.18.0

func (o *SCAPluginOptions) WithRawFlags(rawflags string) *SCAPluginOptions

func (*SCAPluginOptions) WithTargetFile

func (o *SCAPluginOptions) WithTargetFile(targetFile string) *SCAPluginOptions

type SCAResult

type SCAResult struct {
	DepGraph          *depgraph.DepGraph         `json:"depGraph,omitempty"`
	ProjectDescriptor identity.ProjectDescriptor `json:"projectDescriptor"`
	ResolverMetadata  *ResolverMetadata          `json:"meta,omitempty"`
	Error             error                      `json:"error,omitempty"`
}

SCAResult represents the result of a Software Composition Analysis (SCA), containing the dependency graph and associated project descriptor.

Directories

Path Synopsis
javascript
bun
python
pip
uv

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL