Skip to content

Commit a52edcc

Browse files
authored
Merge pull request #1384 from chainguard-dev/ruby-sca
feat(sca): Generate dependency on Ruby when building gems
2 parents 751494a + 9796e43 commit a52edcc

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

pkg/sca/sca.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,86 @@ func generatePythonDeps(ctx context.Context, hdl SCAHandle, generated *config.De
556556
return nil
557557
}
558558

559+
// generateRubyDeps generates a ruby-X.Y dependency for packages which ship
560+
// Ruby gems.
561+
func generateRubyDeps(ctx context.Context, hdl SCAHandle, generated *config.Dependencies) error {
562+
log := clog.FromContext(ctx)
563+
log.Infof("scanning for ruby gems...")
564+
565+
fsys, err := hdl.Filesystem()
566+
if err != nil {
567+
return err
568+
}
569+
570+
var rubyGemVer string
571+
if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
572+
if err != nil {
573+
return err
574+
}
575+
576+
// Ruby gems are installed in paths such as /usr/lib/ruby/gems/X.Y.Z/gems/...,
577+
// so if we find a directory named gems, and its parent is a ruby directory,
578+
// then we have a Ruby gem directory.
579+
basename := filepath.Base(path)
580+
if basename != "gems" {
581+
return nil
582+
}
583+
584+
parent := filepath.Dir(path)
585+
basename = filepath.Base(parent)
586+
587+
// The gems path we want is nested in another gems directory, where the parent
588+
// contains the Ruby version. Return if the parent is the Ruby directory
589+
if basename == "ruby" {
590+
return nil
591+
}
592+
593+
// Ruby versions are formatted as major.minor.patch
594+
majorMinorPatch := `^\d+\.\d+\.\d+$`
595+
596+
// Compile expected Ruby version format
597+
re := regexp.MustCompile(majorMinorPatch)
598+
599+
// Match the directory against version format
600+
if !re.MatchString(basename) {
601+
return nil
602+
}
603+
604+
// This probably shouldn't ever happen, but lets check to make sure.
605+
if !d.IsDir() {
606+
return nil
607+
}
608+
609+
// This takes the X.Y part of the ruby/gems/X.Y.Z directory name as the version to pin against.
610+
// If the X.Y part is not present, then rubyModuleVer will remain an empty string and
611+
// no dependency will be generated.
612+
rubyGemVer = basename[:3]
613+
return nil
614+
}); err != nil {
615+
return err
616+
}
617+
618+
// Nothing to do...
619+
if rubyGemVer == "" {
620+
return nil
621+
}
622+
623+
// Do not add a Ruby dependency if one already exists.
624+
for _, dep := range hdl.BaseDependencies().Runtime {
625+
if strings.HasPrefix(dep, "ruby") {
626+
log.Warnf("%s: Ruby dependency %q already specified, consider removing it in favor of SCA-generated dependency", hdl.PackageName(), dep)
627+
return nil
628+
}
629+
}
630+
631+
log.Infof(" found ruby gem, generating ruby-%s dependency", rubyGemVer)
632+
if !hdl.Options().NoDepends {
633+
generated.Runtime = append(generated.Runtime, fmt.Sprintf("ruby-%s", rubyGemVer))
634+
}
635+
636+
return nil
637+
}
638+
559639
func sonameLibver(soname string) string {
560640
parts := strings.Split(soname, ".so.")
561641
if len(parts) < 2 {
@@ -677,6 +757,7 @@ func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies)
677757
generateCmdProviders,
678758
generatePkgConfigDeps,
679759
generatePythonDeps,
760+
generateRubyDeps,
680761
generateShbangDeps,
681762
}
682763

0 commit comments

Comments
 (0)