Documentation
¶
Overview ¶
Package ruby implements omnibump support for Ruby projects using Bundler.
Updates are performed via direct Gemfile.lock text editing — no Ruby or Bundler CLI is required at runtime. This matches the deployment model where omnibump runs in melange build sandboxes without guaranteed toolchain availability.
Manifest files ¶
The package detects Ruby projects by the presence of Gemfile.lock in the target directory. Both Gemfile and Gemfile.lock are listed as manifest files, but only Gemfile.lock is parsed and modified.
Gemfile.lock parsing ¶
The parser handles the three source section types found in Bundler lockfiles:
- GEM — gems from a rubygems remote (e.g. rubygems.org)
- GIT — gems from a git repository
- PATH — gems from a local filesystem path
Within each section, spec lines (4-space indent) identify gems and their versions, while dependency constraint lines (6-space indent) are preserved but not modified during updates.
Update strategy ¶
All updates are direct text replacements: the version in " gemname (oldversion)" is replaced with the new version. Dependency constraint lines are left untouched. Downgrade attempts are detected via semver comparison and skipped with a warning.
Gem-dir overlay mode ¶
When --gem-dir is specified, omnibump installs patched gems directly into an existing gem directory using `gem install --install-dir`. This is used for CVE remediation when a Ruby package bundles a transitive dependency that needs patching without rebuilding the entire bundle.
Overlay mode validates gem names and versions, rejects downgrades, and verifies installed versions by scanning the specifications/ directory (no `gem list` command needed for validation). Each gem is installed individually with --force to overwrite existing versions.
Limitations ¶
- No constraint validation: bumping a gem to a version that violates another gem's constraints will produce an inconsistent lockfile.
- No checksum or integrity handling (Gemfile.lock has none, unlike go.sum).
- AnalyzeRemote is not yet implemented.
Example ¶
package main
import (
"fmt"
"github.com/chainguard-dev/omnibump/pkg/languages"
)
func main() {
// Get the Ruby language from the registry.
lang, err := languages.Get("ruby")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("Language:", lang.Name())
}
Output: Language: ruby
Index ¶
- Constants
- Variables
- func DoUpdate(ctx context.Context, packages map[string]*Package, gemfileLockPath string) error
- type Analyzer
- func (a *Analyzer) Analyze(ctx context.Context, projectPath string) (*analyzer.AnalysisResult, error)
- func (a *Analyzer) AnalyzeRemote(_ context.Context, _ map[string][]byte) (*analyzer.RemoteAnalysisResult, error)
- func (a *Analyzer) RecommendStrategy(_ context.Context, _ *analyzer.AnalysisResult, deps []analyzer.Dependency) (*analyzer.Strategy, error)
- type GemPackage
- type Package
- type Ruby
- func (r *Ruby) Detect(_ context.Context, dir string) (bool, error)
- func (r *Ruby) GetManifestFiles() []string
- func (r *Ruby) Name() string
- func (r *Ruby) SupportsAnalysis() bool
- func (r *Ruby) Update(ctx context.Context, cfg *languages.UpdateConfig) error
- func (r *Ruby) Validate(ctx context.Context, cfg *languages.UpdateConfig) error
Examples ¶
Constants ¶
const ( // LanguageRuby is the language identifier. LanguageRuby = "ruby" // ManifestGemfile is the Gemfile manifest filename. ManifestGemfile = "Gemfile" // ManifestGemfileLock is the Gemfile.lock manifest filename. ManifestGemfileLock = "Gemfile.lock" )
Language and manifest constants.
Variables ¶
var ( // ErrGemfileLockNotFound is returned when Gemfile.lock is not found. ErrGemfileLockNotFound = errors.New("gemfile.lock not found") // ErrRemoteAnalysisNotImplemented is returned when remote analysis is not implemented. ErrRemoteAnalysisNotImplemented = errors.New("remote analysis not yet implemented") // ErrValidationFailed is returned when post-update validation detects a version mismatch. ErrValidationFailed = errors.New("validation failed") // ErrPackageNotFound is returned when a dependency is not found. ErrPackageNotFound = errors.New("package not found") // ErrGemDirNotFound is returned when the specified gem directory does not exist. ErrGemDirNotFound = errors.New("gem directory not found") // ErrGemDirDowngrade is returned when a gem overlay would downgrade a version. ErrGemDirDowngrade = errors.New("downgrade rejected") // ErrGemInstallFailed is returned when `gem install` exits with an error. ErrGemInstallFailed = errors.New("gem install failed") // ErrGemDirInvalidGemName is returned when a gem name contains invalid characters. ErrGemDirInvalidGemName = errors.New("invalid gem name") // ErrGemDirEmptyVersion is returned when a gem version is empty. ErrGemDirEmptyVersion = errors.New("empty version for gem") // ErrGemDirInvalidVersionFormat is returned when a gem version has an invalid format. ErrGemDirInvalidVersionFormat = errors.New("invalid version format") )
var ( // ErrInvalidGemfileLock is returned when the lockfile cannot be parsed. ErrInvalidGemfileLock = errors.New("invalid Gemfile.lock format") )
Functions ¶
Types ¶
type Analyzer ¶
type Analyzer struct{}
Analyzer implements the analyzer.Analyzer interface for Ruby projects.
func (*Analyzer) Analyze ¶
func (a *Analyzer) Analyze(ctx context.Context, projectPath string) (*analyzer.AnalysisResult, error)
Analyze performs dependency analysis on a Ruby project.
func (*Analyzer) AnalyzeRemote ¶
func (a *Analyzer) AnalyzeRemote(_ context.Context, _ map[string][]byte) (*analyzer.RemoteAnalysisResult, error)
AnalyzeRemote performs dependency analysis on remotely-fetched Ruby files. Not yet implemented for Ruby — returns error.
func (*Analyzer) RecommendStrategy ¶
func (a *Analyzer) RecommendStrategy(_ context.Context, _ *analyzer.AnalysisResult, deps []analyzer.Dependency) (*analyzer.Strategy, error)
RecommendStrategy always recommends direct updates for Ruby dependencies. Ruby doesn't have a "property" abstraction like Maven.
type GemPackage ¶
type GemPackage struct {
Name string
Version string
Source string
Dependencies []string // Dependency constraints (e.g., "rack (~> 3.0)")
}
GemPackage represents a gem from Gemfile.lock.
func ParseGemfileLock ¶
func ParseGemfileLock(r io.Reader) ([]GemPackage, error)
ParseGemfileLock parses a Gemfile.lock file and returns the list of gem packages. It handles GEM, GIT, PATH, PLATFORMS, and BUNDLED WITH sections.
type Ruby ¶
type Ruby struct{}
Ruby implements the Language interface for Ruby projects.
func (*Ruby) Detect ¶
Detect checks if Ruby manifest files exist in the directory.
Example ¶
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/chainguard-dev/omnibump/pkg/languages/ruby"
)
func main() {
// Create a temporary directory with a Gemfile.lock to simulate a Ruby project.
dir, err := os.MkdirTemp("", "ruby-example-*")
if err != nil {
fmt.Println("error:", err)
return
}
defer func() { _ = os.RemoveAll(dir) }()
lockfile := filepath.Join(dir, "Gemfile.lock")
content := "GEM\n remote: https://rubygems.org/\n specs:\n rack (3.1.8)\n"
if err := os.WriteFile(lockfile, []byte(content), 0o644); err != nil {
fmt.Println("error:", err)
return
}
r := &ruby.Ruby{}
detected, err := r.Detect(context.Background(), dir)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("Detected:", detected)
}
Output: Detected: true
func (*Ruby) GetManifestFiles ¶
GetManifestFiles returns Ruby manifest files.
Example ¶
package main
import (
"fmt"
"github.com/chainguard-dev/omnibump/pkg/languages/ruby"
)
func main() {
r := &ruby.Ruby{}
files := r.GetManifestFiles()
for _, f := range files {
fmt.Println(f)
}
}
Output: Gemfile Gemfile.lock
func (*Ruby) SupportsAnalysis ¶
SupportsAnalysis returns true since Ruby has analysis capabilities.