From 120fcdc56b1acd7c8435ba160aa41e53f54c4527 Mon Sep 17 00:00:00 2001 From: Mikhail Klementev Date: Thu, 18 May 2023 21:37:07 +0000 Subject: [PATCH] feat: initial implementation of distro interface --- distro/centos/centos.go | 52 +++++++++++++++++ distro/debian/debian.go | 95 ++++++++++++++++++++----------- distro/distro.go | 41 +++++++++++++ distro/id.go | 10 +++- distro/oraclelinux/oraclelinux.go | 87 ++++++++++++++++------------ distro/ubuntu/ubuntu.go | 88 ++++++++++++++++++---------- kernel/kernel.go | 28 +++++---- main.go | 5 ++ 8 files changed, 292 insertions(+), 114 deletions(-) diff --git a/distro/centos/centos.go b/distro/centos/centos.go index a5bd5a4..f6a84d8 100644 --- a/distro/centos/centos.go +++ b/distro/centos/centos.go @@ -2,12 +2,64 @@ package centos import ( "fmt" + "strings" + "time" "github.com/rs/zerolog/log" "code.dumpstack.io/tools/out-of-tree/config" + "code.dumpstack.io/tools/out-of-tree/container" + "code.dumpstack.io/tools/out-of-tree/distro" ) +func init() { + releases := []string{"6", "7", "8"} + + for _, release := range releases { + container := "out_of_tree_centos_" + release + + distro.Register(CentOS{ + release: release, + container: container, + }) + } +} + +type CentOS struct { + release string + container string +} + +func (centos CentOS) ID() distro.ID { + return distro.CentOS +} + +func (centos CentOS) Equal(d distro.Distro) bool { + return centos.release == d.Release && distro.CentOS == d.ID +} + +func (centos CentOS) Packages() (pkgs []string, err error) { + c, err := container.New(centos.container, time.Hour) + if err != nil { + return + } + + cmd := "yum search kernel --showduplicates " + + "| grep '^kernel-[0-9]' " + + "| grep -v src " + + "| cut -d ' ' -f 1" + + output, err := c.Run(config.Dir("tmp"), cmd) + if err != nil { + return + } + + for _, pkg := range strings.Fields(output) { + pkgs = append(pkgs, pkg) + } + return +} + func Envs(km config.Target) (envs []string) { return } diff --git a/distro/debian/debian.go b/distro/debian/debian.go index bcd30fc..113ba06 100644 --- a/distro/debian/debian.go +++ b/distro/debian/debian.go @@ -14,10 +14,73 @@ import ( "code.dumpstack.io/tools/out-of-tree/cache" "code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/container" + "code.dumpstack.io/tools/out-of-tree/distro" "code.dumpstack.io/tools/out-of-tree/distro/debian/snapshot" "code.dumpstack.io/tools/out-of-tree/fs" ) +func init() { + releases := []Release{ + Wheezy, + Jessie, + Stretch, + Buster, + Bullseye, + } + + for _, release := range releases { + container := fmt.Sprintf("out_of_tree_debian_%d", release) + + distro.Register(Debian{ + release: release, + container: container, + }) + } +} + +type Debian struct { + release Release + container string +} + +func (d Debian) ID() distro.ID { + return distro.Debian +} + +func (d Debian) Equal(dd distro.Distro) bool { + if dd.ID != distro.Debian { + return false + } + + return releaseFromString(dd.Release) == d.release +} + +func (d Debian) Packages() (packages []string, err error) { + kernels, err := GetKernels() + if err != nil { + log.Error().Err(err).Msg("get kernels") + return + } + + for _, dk := range kernels { + p := strings.Replace(dk.Image.Deb.Name, ".deb", "", -1) + + var kr Release + kr, err = kernelRelease(p) + if err != nil { + log.Warn().Err(err).Msg("") + continue + } + if kr != d.release { + continue + } + + packages = append(packages, p) + } + + return +} + type Release int const ( @@ -111,38 +174,6 @@ func kernelRelease(deb string) (r Release, err error) { return } -func Match(km config.Target) (pkgs []string, err error) { - kernels, err := GetKernels() - if err != nil { - log.Error().Err(err).Msg("get kernels") - return - } - - release := releaseFromString(km.Distro.Release) - - r := regexp.MustCompile(km.Kernel.Regex) - - for _, dk := range kernels { - p := strings.Replace(dk.Image.Deb.Name, ".deb", "", -1) - - var kr Release - kr, err = kernelRelease(p) - if err != nil { - log.Warn().Err(err).Msg("") - continue - } - if kr != release { - continue - } - - if r.MatchString(p) { - pkgs = append(pkgs, p) - } - } - - return -} - func Envs(km config.Target) (envs []string) { envs = append(envs, "DEBIAN_FRONTEND=noninteractive") return diff --git a/distro/distro.go b/distro/distro.go index 7ffb52e..218836c 100644 --- a/distro/distro.go +++ b/distro/distro.go @@ -1,6 +1,47 @@ package distro +import ( + "sync" +) + +var mu sync.Mutex +var distros []distribution + +type distribution interface { + ID() ID + Equal(Distro) bool + Packages() (packages []string, err error) +} + +func Register(d distribution) { + mu.Lock() + defer mu.Unlock() + + distros = append(distros, d) +} + type Distro struct { ID ID Release string } + +func (d Distro) Packages() (packages []string, err error) { + for _, dd := range distros { + if d.ID != None && d.ID != dd.ID() { + continue + } + + if d.Release != "" && !dd.Equal(d) { + continue + } + + var pkgs []string + pkgs, err = dd.Packages() + if err != nil { + return + } + + packages = append(packages, pkgs...) + } + return +} diff --git a/distro/id.go b/distro/id.go index a91c03a..93cf84d 100644 --- a/distro/id.go +++ b/distro/id.go @@ -9,8 +9,9 @@ import ( type ID int const ( + None ID = iota // Ubuntu https://ubuntu.com/ - Ubuntu ID = iota + Ubuntu // CentOS https://www.centos.org/ CentOS // Debian https://www.debian.org/ @@ -20,10 +21,11 @@ const ( ) var IDs = []ID{ - Ubuntu, CentOS, Debian, OracleLinux, + None, Ubuntu, CentOS, Debian, OracleLinux, } var nameStrings = [...]string{ + "", "Ubuntu", "CentOS", "Debian", @@ -50,8 +52,10 @@ func (id *ID) UnmarshalTOML(data []byte) (err error) { *id = Debian } else if strings.EqualFold(name, "OracleLinux") { *id = OracleLinux + } else if name != "" { + err = fmt.Errorf("distro %s is not supported", name) } else { - err = fmt.Errorf("distro %s is unsupported", name) + *id = None } return } diff --git a/distro/oraclelinux/oraclelinux.go b/distro/oraclelinux/oraclelinux.go index 6901802..6678165 100644 --- a/distro/oraclelinux/oraclelinux.go +++ b/distro/oraclelinux/oraclelinux.go @@ -2,7 +2,6 @@ package oraclelinux import ( "fmt" - "regexp" "strings" "time" @@ -10,8 +9,58 @@ import ( "code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/container" + "code.dumpstack.io/tools/out-of-tree/distro" ) +func init() { + releases := []string{"6", "7", "8", "9"} + + for _, release := range releases { + container := "out_of_tree_oraclelinux_" + release + + distro.Register(OracleLinux{ + release: release, + container: container, + }) + } +} + +type OracleLinux struct { + release string + container string +} + +func (ol OracleLinux) ID() distro.ID { + return distro.OracleLinux +} + +func (ol OracleLinux) Equal(d distro.Distro) bool { + return ol.release == d.Release && distro.OracleLinux == d.ID +} + +func (ol OracleLinux) Packages() (pkgs []string, err error) { + c, err := container.New(ol.container, time.Hour) + if err != nil { + return + } + + cmd := "yum search kernel --showduplicates " + + "| grep '^kernel-[0-9]\\|^kernel-uek-[0-9]' " + + "| grep -v src " + + "| cut -d ' ' -f 1" + + output, err := c.Run(config.Dir("tmp"), cmd) + if err != nil { + return + } + + for _, pkg := range strings.Fields(output) { + pkgs = append(pkgs, pkg) + } + + return +} + func Envs(km config.Target) (envs []string) { return } @@ -40,42 +89,6 @@ func Runs(km config.Target) (commands []string) { return } -func Match(km config.Target) (pkgs []string, err error) { - // FIXME timeout should be in global out-of-tree config - c, err := container.New(km.DockerName(), time.Hour) - if err != nil { - return - } - - cmd := "yum search kernel --showduplicates " + - "| grep '^kernel-[0-9]\\|^kernel-uek-[0-9]' " + - "| grep -v src " + - "| cut -d ' ' -f 1" - - output, err := c.Run(config.Dir("tmp"), cmd) - if err != nil { - return - } - - r, err := regexp.Compile("kernel-" + km.Kernel.Regex) - if err != nil { - return - } - - for _, pkg := range strings.Fields(output) { - if r.MatchString(pkg) || strings.Contains(pkg, km.Kernel.Regex) { - log.Trace().Msg(pkg) - pkgs = append(pkgs, pkg) - } - } - - if len(pkgs) == 0 { - log.Warn().Msg("no packages matched") - } - - return -} - func Install(km config.Target, pkgname string, headers bool) (commands []string, err error) { var headerspkg string if headers { diff --git a/distro/ubuntu/ubuntu.go b/distro/ubuntu/ubuntu.go index b4952a9..8f635b9 100644 --- a/distro/ubuntu/ubuntu.go +++ b/distro/ubuntu/ubuntu.go @@ -2,14 +2,70 @@ package ubuntu import ( "fmt" - "regexp" "strings" "time" "code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/container" + "code.dumpstack.io/tools/out-of-tree/distro" ) +func init() { + releases := []string{ + "12.04", + "14.04", + "16.04", + "18.04", + "20.04", + "22.04", + } + + for _, release := range releases { + container := "out_of_tree_ubuntu_" + release + container = strings.Replace(container, ".", "__", -1) + + distro.Register(Ubuntu{ + release: release, + container: container, + }) + } +} + +type Ubuntu struct { + release string + container string +} + +func (u Ubuntu) ID() distro.ID { + return distro.Ubuntu +} + +func (u Ubuntu) Equal(d distro.Distro) bool { + return u.release == d.Release && distro.Ubuntu == d.ID +} + +func (u Ubuntu) Packages() (pkgs []string, err error) { + c, err := container.New(u.container, time.Hour) + if err != nil { + return + } + + cmd := "apt-cache search " + + "--names-only '^linux-image-[0-9\\.\\-]*-generic' " + + "| awk '{ print $1 }'" + + output, err := c.Run(config.Dir("tmp"), cmd) + if err != nil { + return + } + + for _, pkg := range strings.Fields(output) { + pkgs = append(pkgs, pkg) + } + + return +} + func Envs(km config.Target) (envs []string) { envs = append(envs, "DEBIAN_FRONTEND=noninteractive") return @@ -53,36 +109,6 @@ func Runs(km config.Target) (commands []string) { return } -func Match(km config.Target) (pkgs []string, err error) { - // FIXME timeout should be in global out-of-tree config - c, err := container.New(km.DockerName(), time.Hour) - if err != nil { - return - } - - cmd := "apt-cache search " + - "--names-only '^linux-image-[0-9\\.\\-]*-generic' " + - "| awk '{ print $1 }'" - - output, err := c.Run(config.Dir("tmp"), cmd) - if err != nil { - return - } - - r, err := regexp.Compile("linux-image-" + km.Kernel.Regex) - if err != nil { - return - } - - for _, pkg := range strings.Fields(output) { - if r.MatchString(pkg) || strings.Contains(pkg, km.Kernel.Regex) { - pkgs = append(pkgs, pkg) - } - } - - return -} - func Install(km config.Target, pkgname string, headers bool) (commands []string, err error) { var headerspkg string diff --git a/kernel/kernel.go b/kernel/kernel.go index 29be9a4..0e5b499 100644 --- a/kernel/kernel.go +++ b/kernel/kernel.go @@ -13,6 +13,7 @@ import ( "os/exec" "os/signal" "os/user" + "regexp" "runtime" "strings" "time" @@ -31,18 +32,23 @@ import ( "code.dumpstack.io/tools/out-of-tree/fs" ) -func MatchPackages(km config.Target) (pkgs []string, err error) { - // TODO interface for kernels match - switch km.Distro.ID { - case distro.Ubuntu: - pkgs, err = ubuntu.Match(km) - case distro.OracleLinux, distro.CentOS: - pkgs, err = oraclelinux.Match(km) - case distro.Debian: - pkgs, err = debian.Match(km) - default: - err = fmt.Errorf("%s not yet supported", km.Distro.ID.String()) +func MatchPackages(km config.Target) (packages []string, err error) { + pkgs, err := km.Distro.Packages() + if err != nil { + return } + + r, err := regexp.Compile(km.Kernel.Regex) + if err != nil { + return + } + + for _, pkg := range pkgs { + if r.MatchString(pkg) { + packages = append(packages, pkg) + } + } + return } diff --git a/main.go b/main.go index f352705..bc7b5e0 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,11 @@ import ( "github.com/alecthomas/kong" + _ "code.dumpstack.io/tools/out-of-tree/distro/centos" + _ "code.dumpstack.io/tools/out-of-tree/distro/debian" + _ "code.dumpstack.io/tools/out-of-tree/distro/oraclelinux" + _ "code.dumpstack.io/tools/out-of-tree/distro/ubuntu" + "code.dumpstack.io/tools/out-of-tree/cache" "code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/container"