Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SUSE support #100

Merged
merged 4 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# binary
btfhub
# arvhices
archive/*
custom-archive/*
# makefile leftovers
.check*
# binary
# JetBrains
.idea/
9 changes: 6 additions & 3 deletions cmd/btfhub/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (
"path/filepath"
"runtime"

"golang.org/x/sync/errgroup"

"github.com/aquasecurity/btfhub/pkg/job"
"github.com/aquasecurity/btfhub/pkg/repo"
"golang.org/x/sync/errgroup"
)

var distroReleases = map[string][]string{
Expand All @@ -24,6 +25,7 @@ var distroReleases = map[string][]string{
"ol": {"7", "8"},
"rhel": {"7", "8"},
"amzn": {"1", "2"},
"sles": {"12.3", "12.5", "15.1", "15.2", "15.3", "15.4"},
}

type repoFunc func() repo.Repository
Expand All @@ -36,15 +38,16 @@ var repoCreators = map[string]repoFunc{
"ol": repo.NewOracleRepo,
"rhel": repo.NewRHELRepo,
"amzn": repo.NewAmazonRepo,
"sles": repo.NewSUSERepo,
}

var distro, release, arch string
var numWorkers int
var force bool

func init() {
flag.StringVar(&distro, "distro", "", "distribution to update (ubuntu,debian,centos,fedora,ol,rhel,amazon)")
flag.StringVar(&distro, "d", "", "distribution to update (ubuntu,debian,centos,fedora,ol,rhel,amazon)")
flag.StringVar(&distro, "distro", "", "distribution to update (ubuntu,debian,centos,fedora,ol,rhel,amazon,sles)")
flag.StringVar(&distro, "d", "", "distribution to update (ubuntu,debian,centos,fedora,ol,rhel,amazon,sles)")
flag.StringVar(&release, "release", "", "distribution release to update, requires specifying distribution")
flag.StringVar(&release, "r", "", "distribution release to update, requires specifying distribution")
flag.StringVar(&arch, "arch", "", "architecture to update (x86_64,arm64)")
Expand Down
59 changes: 59 additions & 0 deletions pkg/pkg/suse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package pkg

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/aquasecurity/btfhub/pkg/kernel"
"github.com/aquasecurity/btfhub/pkg/utils"
)

type SUSEPackage struct {
Name string
NameOfFile string
Architecture string
KernelVersion kernel.Version
Repo string
Flavor string
Downloaddir string
}

func (pkg *SUSEPackage) Filename() string {
return pkg.NameOfFile
}

func (pkg *SUSEPackage) Version() kernel.Version {
return pkg.KernelVersion
}

func (pkg *SUSEPackage) String() string {
return fmt.Sprintf("%s-%s.%s", pkg.Name, pkg.KernelVersion.String(), pkg.Architecture)
}

func (pkg *SUSEPackage) ExtractKernel(ctx context.Context, pkgpath string, vmlinuxPath string) error {
// vmlinux at: /usr/lib/debug/boot/vmlinux-<ver>-<type>.debug
return utils.ExtractVmlinuxFromRPM(ctx, pkgpath, vmlinuxPath)
}

func (pkg *SUSEPackage) Download(ctx context.Context, _ string, force bool) (string, error) {
localFile := fmt.Sprintf("%s-%s.%s.rpm", pkg.Name, pkg.KernelVersion.String(), pkg.Architecture)
rpmpath := filepath.Join(pkg.Downloaddir, localFile)
if !force && utils.Exists(rpmpath) {
return rpmpath, nil
}

if err := zypperDownload(ctx, fmt.Sprintf("%s=%s", pkg.Name, pkg.KernelVersion.String())); err != nil {
os.Remove(rpmpath)
return "", fmt.Errorf("zypper download: %s", err)
}

return rpmpath, nil
}

func zypperDownload(ctx context.Context, pkg string) error {
stdout, err := utils.RunZypperCMD(ctx, "-q", "install", "-y", "--no-recommends", "--download-only", pkg)
_, _ = fmt.Fprint(os.Stdout, stdout.String())
return err
}
4 changes: 3 additions & 1 deletion pkg/pkg/ubuntu.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import (
"path/filepath"
"strings"

"pault.ag/go/debian/deb"

"github.com/aquasecurity/btfhub/pkg/kernel"
"github.com/aquasecurity/btfhub/pkg/utils"
"pault.ag/go/debian/deb"
)

// UbuntuPackage represents a package in Ubuntu
Expand Down Expand Up @@ -116,6 +117,7 @@ func (pkg *UbuntuPackage) ExtractKernel(ctx context.Context, pkgPath string, vml
return fmt.Errorf("create vmlinux file: %s", err)
}
counter := &utils.ProgressCounter{
Ctx: ctx,
Op: "Extract",
Name: hdr.Name,
Size: uint64(hdr.Size),
Expand Down
5 changes: 2 additions & 3 deletions pkg/pkg/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ func yumDownload(ctx context.Context, pkg string, destdir string) error {

destDirParam := fmt.Sprintf("--downloaddir=%s", destdir)

cmd := exec.CommandContext(ctx,
"sudo", "yum", "install", "-y", "--downloadonly", destDirParam, pkg,
)
binary, args := utils.SudoCMD("yum", "install", "-y", "--downloadonly", destDirParam, pkg)
cmd := exec.CommandContext(ctx, binary, args...)

cmd.Stdout = os.Stdout
cmd.Stderr = stderr
Expand Down
2 changes: 1 addition & 1 deletion pkg/repo/centos.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (d *CentosRepo) GetKernelPackages(

repoURL := fmt.Sprintf(d.repos[release], altArch)

links, err := utils.GetLinks(repoURL)
links, err := utils.GetLinks(ctx, repoURL)
if err != nil {
return fmt.Errorf("ERROR: list packages: %s", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/repo/fedora.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (d *FedoraRepo) GetKernelPackages(
// Pick all the links from multiple repositories

for _, repo := range repos {
rlinks, err := utils.GetLinks(repo)
rlinks, err := utils.GetLinks(ctx, repo)
if err != nil {
log.Printf("ERROR: list packages: %s\n", err)
continue
Expand Down
2 changes: 1 addition & 1 deletion pkg/repo/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (d *oracleRepo) GetKernelPackages(

repoURL := d.repos[release]

links, err := utils.GetLinks(repoURL)
links, err := utils.GetLinks(ctx, repoURL)
if err != nil {
return fmt.Errorf("ERROR: list packages: %s", err)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/repo/rhel.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func (d *RHELRepo) GetKernelPackages(
) error {
altArch := d.archs[arch]
rver := d.releaseVersions[release+":"+altArch]
if err := utils.RunCMD(ctx, "", "sudo", "subscription-manager", "release", fmt.Sprintf("--set=%s", rver)); err != nil {
binary, args := utils.SudoCMD("subscription-manager", "release", fmt.Sprintf("--set=%s", rver))
if err := utils.RunCMD(ctx, "", binary, args...); err != nil {
return err
}

Expand Down
205 changes: 205 additions & 0 deletions pkg/repo/suse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package repo

import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"log"
"regexp"
"sort"
"strconv"
"strings"
"unicode"

"golang.org/x/sync/errgroup"

"github.com/aquasecurity/btfhub/pkg/job"
"github.com/aquasecurity/btfhub/pkg/kernel"
"github.com/aquasecurity/btfhub/pkg/pkg"
"github.com/aquasecurity/btfhub/pkg/utils"
)

type suseRepo struct {
archs map[string]string
repoAliases map[string]string
}

func NewSUSERepo() Repository {
return &suseRepo{
archs: map[string]string{
"x86_64": "x86_64",
"arm64": "aarch64",
},
repoAliases: map[string]string{},
}
}

func (d *suseRepo) GetKernelPackages(ctx context.Context, dir string, release string, arch string, force bool, jobchan chan<- job.Job) error {
var repos []string

switch release {
case "12.5":
repos = append(repos, fmt.Sprintf("SUSE_Linux_Enterprise_Server_%s:SLES12-SP5-Debuginfo-Pool", arch))
repos = append(repos, fmt.Sprintf("SUSE_Linux_Enterprise_Server_%s:SLES12-SP5-Debuginfo-Updates", arch))
case "15.1":
repos = append(repos, fmt.Sprintf("Basesystem_Module_15_SP1_%s:SLE-Module-Basesystem15-SP1-Debuginfo-Pool", arch))
repos = append(repos, fmt.Sprintf("Basesystem_Module_15_SP1_%s:SLE-Module-Basesystem15-SP1-Debuginfo-Updates", arch))
case "15.2":
repos = append(repos, fmt.Sprintf("Basesystem_Module_%s:SLE-Module-Basesystem15-SP2-Debuginfo-Pool", arch))
repos = append(repos, fmt.Sprintf("Basesystem_Module_%s:SLE-Module-Basesystem15-SP2-Debuginfo-Updates", arch))
case "15.3":
repos = append(repos, fmt.Sprintf("Basesystem_Module_%s:SLE-Module-Basesystem15-SP3-Debuginfo-Pool", arch))
repos = append(repos, fmt.Sprintf("Basesystem_Module_%s:SLE-Module-Basesystem15-SP3-Debuginfo-Updates", arch))
case "15.4":
repos = append(repos, fmt.Sprintf("Basesystem_Module_%s:SLE-Module-Basesystem15-SP4-Debuginfo-Pool", arch))
repos = append(repos, fmt.Sprintf("Basesystem_Module_%s:SLE-Module-Basesystem15-SP4-Debuginfo-Updates", arch))
}
for _, r := range repos {
if _, err := utils.RunZypperCMD(ctx, "modifyrepo", "--enable", r); err != nil {
return err
}
}

if err := d.getRepoAliases(ctx); err != nil {
return fmt.Errorf("repo aliases: %s", err)
}

// packages are named kernel-<type>-debuginfo
// possible types are: default, azure
searchOut, err := zypperSearch(ctx, "kernel-*-debuginfo")
if err != nil {
return err
}

pkgs, err := d.parseZypperPackages(searchOut, arch)
if err != nil {
return fmt.Errorf("parse package listing: %s", err)
}

pkgsByKernelType := make(map[string][]pkg.Package)
for _, p := range pkgs {
ks, ok := pkgsByKernelType[p.Flavor]
if !ok {
ks = make([]pkg.Package, 0, 1)
}
ks = append(ks, p)
pkgsByKernelType[p.Flavor] = ks
}

for kt, ks := range pkgsByKernelType {
sort.Sort(pkg.ByVersion(ks))
log.Printf("DEBUG: %s %s flavor %d kernels\n", arch, kt, len(ks))
}

g, ctx := errgroup.WithContext(ctx)
for kt, ks := range pkgsByKernelType {
ckt := kt
cks := ks
g.Go(func() error {
log.Printf("DEBUG: start kernel type %s %s (%d pkgs)\n", ckt, arch, len(cks))
err := d.processPackages(ctx, dir, cks, force, jobchan)
log.Printf("DEBUG: end kernel type %s %s\n", ckt, arch)
return err
})
}
return g.Wait()
}

func (d *suseRepo) getRepoAliases(ctx context.Context) error {
repos, err := zypperRepos(ctx)
if err != nil {
return err
}
bio := bufio.NewScanner(repos)
for bio.Scan() {
line := bio.Text()
fields := strings.FieldsFunc(line, func(r rune) bool {
return unicode.IsSpace(r) || r == '|'
})
if len(fields) < 3 {
continue
}
// first field must be a number
if _, err := strconv.Atoi(fields[0]); err != nil {
continue
}
alias, name := fields[1], fields[2]
d.repoAliases[name] = alias
}
return bio.Err()
}

func (d *suseRepo) processPackages(ctx context.Context, dir string, pkgs []pkg.Package, force bool, jobchan chan<- job.Job) error {
for i, p := range pkgs {
log.Printf("DEBUG: start pkg %s (%d/%d)\n", p, i+1, len(pkgs))
if err := processPackage(ctx, p, dir, force, jobchan); err != nil {
if errors.Is(err, utils.ErrHasBTF) {
log.Printf("INFO: kernel %s has BTF already, skipping later kernels\n", p)
return nil
}
if errors.Is(err, context.Canceled) {
return nil
}
log.Printf("ERROR: %s: %s\n", p, err)
continue
}
log.Printf("DEBUG: end pkg %s (%d/%d)\n", p, i+1, len(pkgs))
}
return nil
}

func (d *suseRepo) parseZypperPackages(rdr io.Reader, arch string) ([]*pkg.SUSEPackage, error) {
var pkgs []*pkg.SUSEPackage
kre := regexp.MustCompile(`^kernel-([^-]+)-debuginfo$`)
bio := bufio.NewScanner(rdr)
for bio.Scan() {
line := bio.Text()
fields := strings.FieldsFunc(line, func(r rune) bool {
return unicode.IsSpace(r) || r == '|'
})
if len(fields) < 5 {
continue
}
name, ver, pkgarch, repo := fields[0], fields[2], fields[3], fields[4]
if pkgarch != arch {
continue
}
match := kre.FindStringSubmatch(name)
if match != nil {
alias, ok := d.repoAliases[repo]
if !ok {
return nil, fmt.Errorf("unknown repo %s", repo)
}
flavor := match[1]
if flavor == "preempt" {
continue
}

p := &pkg.SUSEPackage{
Name: name,
NameOfFile: fmt.Sprintf("%s-%s", ver, match[1]),
KernelVersion: kernel.NewKernelVersion(ver),
Architecture: pkgarch,
Repo: repo,
Flavor: flavor,
Downloaddir: fmt.Sprintf("/var/cache/zypp/packages/%s/%s", alias, arch),
}
pkgs = append(pkgs, p)
}
}
if err := bio.Err(); err != nil {
return nil, err
}
return pkgs, nil
}

func zypperRepos(ctx context.Context) (*bytes.Buffer, error) {
return utils.RunZypperCMD(ctx, "repos")
}

func zypperSearch(ctx context.Context, pkg string) (*bytes.Buffer, error) {
return utils.RunZypperCMD(ctx, "search", "-s", pkg)
}
Loading
Loading