refactor: move kernel functions to submodule
This commit is contained in:
		| @@ -1,7 +0,0 @@ | ||||
| // Copyright 2019 Mikhail Klementev. All rights reserved. | ||||
| // Use of this source code is governed by a AGPLv3 license | ||||
| // (or later) that can be found in the LICENSE file. | ||||
|  | ||||
| package main | ||||
|  | ||||
| const imagesBaseURL = "https://out-of-tree.fra1.digitaloceanspaces.com/1.0.0/" | ||||
							
								
								
									
										52
									
								
								images.go
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								images.go
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| // Copyright 2019 Mikhail Klementev. All rights reserved. | ||||
| // Copyright 2023 Mikhail Klementev. All rights reserved. | ||||
| // Use of this source code is governed by a AGPLv3 license | ||||
| // (or later) that can be found in the LICENSE file. | ||||
|  | ||||
| @@ -7,17 +7,11 @@ package main | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"os/user" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/cavaliergopher/grab/v3" | ||||
| 	"github.com/rs/zerolog/log" | ||||
|  | ||||
| 	"code.dumpstack.io/tools/out-of-tree/config" | ||||
| 	"code.dumpstack.io/tools/out-of-tree/fs" | ||||
| 	"code.dumpstack.io/tools/out-of-tree/qemu" | ||||
| @@ -126,47 +120,3 @@ func (cmd *ImageEditCmd) Run(g *Globals) (err error) { | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func unpackTar(archive, destination string) (err error) { | ||||
| 	// NOTE: If you're change anything in tar command please check also | ||||
| 	// BSD tar (or if you're using macOS, do not forget to check GNU Tar) | ||||
| 	// Also make sure that sparse files are extracting correctly | ||||
| 	cmd := exec.Command("tar", "-Sxf", archive) | ||||
| 	cmd.Dir = destination + "/" | ||||
|  | ||||
| 	log.Debug().Msgf("%v", cmd) | ||||
|  | ||||
| 	rawOutput, err := cmd.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("%v: %s", err, rawOutput) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func downloadImage(path, file string) (err error) { | ||||
| 	tmp, err := ioutil.TempDir(tempDirBase, "out-of-tree_") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer os.RemoveAll(tmp) | ||||
|  | ||||
| 	fileurl, err := url.JoinPath(imagesBaseURL, file+".tar.gz") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp, err := grab.Get(tmp, fileurl) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("Cannot download %s. It looks like you need "+ | ||||
| 			"to generate it manually and place it "+ | ||||
| 			"to ~/.out-of-tree/images/. "+ | ||||
| 			"Check documentation for additional information.", | ||||
| 			fileurl) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = unpackTar(resp.Filename, path) | ||||
| 	return | ||||
| } | ||||
|   | ||||
| @@ -1,29 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
|  | ||||
| 	"code.dumpstack.io/tools/out-of-tree/fs" | ||||
| ) | ||||
|  | ||||
| func TestDownloadImage(t *testing.T) { | ||||
| 	tmp, err := ioutil.TempDir("", "out-of-tree_") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer os.RemoveAll(tmp) | ||||
|  | ||||
| 	file := "out_of_tree_ubuntu_12__04.img" | ||||
|  | ||||
| 	err = downloadImage(tmp, file) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if !fs.PathExists(filepath.Join(tmp, file)) { | ||||
| 		t.Fatalf("%s does not exist", file) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										701
									
								
								kernel.go
									
									
									
									
									
								
							
							
						
						
									
										701
									
								
								kernel.go
									
									
									
									
									
								
							| @@ -7,24 +7,13 @@ package main | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"math" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"os/signal" | ||||
| 	"os/user" | ||||
| 	"regexp" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/naoina/toml" | ||||
| 	"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/fs" | ||||
| 	"code.dumpstack.io/tools/out-of-tree/kernel" | ||||
| ) | ||||
|  | ||||
| type KernelCmd struct { | ||||
| @@ -80,12 +69,12 @@ func (cmd *KernelListRemoteCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error | ||||
| 		ReleaseMask:   ".*", | ||||
| 	} | ||||
|  | ||||
| 	_, err = genRootfsImage(container.Image{Name: km.DockerName()}, false) | ||||
| 	_, err = kernel.GenRootfsImage(container.Image{Name: km.DockerName()}, false) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = generateBaseDockerImage( | ||||
| 	err = kernel.GenerateBaseDockerImage( | ||||
| 		g.Config.Docker.Registry, | ||||
| 		g.Config.Docker.Commands, | ||||
| 		km, | ||||
| @@ -95,7 +84,8 @@ func (cmd *KernelListRemoteCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pkgs, err := matchPackages(km) | ||||
| 	pkgs, err := kernel.MatchPackages(km) | ||||
| 	// error check skipped on purpose | ||||
|  | ||||
| 	for _, k := range pkgs { | ||||
| 		fmt.Println(k) | ||||
| @@ -115,7 +105,7 @@ func (cmd KernelAutogenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 	} | ||||
|  | ||||
| 	shutdown := false | ||||
| 	setSigintHandler(&shutdown) | ||||
| 	kernel.SetSigintHandler(&shutdown) | ||||
|  | ||||
| 	for _, sk := range ka.SupportedKernels { | ||||
| 		if sk.DistroRelease == "" { | ||||
| @@ -123,7 +113,7 @@ func (cmd KernelAutogenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		err = generateKernels(sk, | ||||
| 		err = kernel.GenerateKernels(sk, | ||||
| 			g.Config.Docker.Registry, | ||||
| 			g.Config.Docker.Commands, | ||||
| 			cmd.Max, kernelCmd.Retries, | ||||
| @@ -142,7 +132,7 @@ func (cmd KernelAutogenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| 	return kernel.UpdateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| } | ||||
|  | ||||
| type KernelGenallCmd struct { | ||||
| @@ -157,14 +147,14 @@ func (cmd *KernelGenallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 	} | ||||
|  | ||||
| 	shutdown := false | ||||
| 	setSigintHandler(&shutdown) | ||||
| 	kernel.SetSigintHandler(&shutdown) | ||||
|  | ||||
| 	km := config.KernelMask{ | ||||
| 		DistroType:    distroType, | ||||
| 		DistroRelease: cmd.Ver, | ||||
| 		ReleaseMask:   ".*", | ||||
| 	} | ||||
| 	err = generateKernels(km, | ||||
| 	err = kernel.GenerateKernels(km, | ||||
| 		g.Config.Docker.Registry, | ||||
| 		g.Config.Docker.Commands, | ||||
| 		math.MaxUint32, kernelCmd.Retries, | ||||
| @@ -179,7 +169,7 @@ func (cmd *KernelGenallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| 	return kernel.UpdateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| } | ||||
|  | ||||
| type KernelInstallCmd struct { | ||||
| @@ -195,14 +185,14 @@ func (cmd *KernelInstallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 	} | ||||
|  | ||||
| 	shutdown := false | ||||
| 	setSigintHandler(&shutdown) | ||||
| 	kernel.SetSigintHandler(&shutdown) | ||||
|  | ||||
| 	km := config.KernelMask{ | ||||
| 		DistroType:    distroType, | ||||
| 		DistroRelease: cmd.Ver, | ||||
| 		ReleaseMask:   cmd.Kernel, | ||||
| 	} | ||||
| 	err = generateKernels(km, | ||||
| 	err = kernel.GenerateKernels(km, | ||||
| 		g.Config.Docker.Registry, | ||||
| 		g.Config.Docker.Commands, | ||||
| 		math.MaxUint32, kernelCmd.Retries, | ||||
| @@ -217,672 +207,11 @@ func (cmd *KernelInstallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| 	return kernel.UpdateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| } | ||||
|  | ||||
| type KernelConfigRegenCmd struct{} | ||||
|  | ||||
| func (cmd *KernelConfigRegenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) { | ||||
| 	return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| } | ||||
|  | ||||
| func matchDebImagePkg(containerName, mask string) (pkgs []string, err error) { | ||||
|  | ||||
| 	cmd := "apt-cache search --names-only '^linux-image-[0-9\\.\\-]*-generic' | awk '{ print $1 }'" | ||||
|  | ||||
| 	// FIXME timeout should be in global out-of-tree config | ||||
| 	c, err := container.New(containerName, time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	output, err := c.Run(tempDirBase, cmd) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	r, err := regexp.Compile("linux-image-" + mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, pkg := range strings.Fields(output) { | ||||
| 		if r.MatchString(pkg) || strings.Contains(pkg, mask) { | ||||
| 			pkgs = append(pkgs, pkg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func matchOracleLinuxPkg(containerName, mask string) ( | ||||
| 	pkgs []string, err error) { | ||||
|  | ||||
| 	cmd := "yum search kernel --showduplicates " + | ||||
| 		"| grep '^kernel-[0-9]\\|^kernel-uek-[0-9]' " + | ||||
| 		"| grep -v src " + | ||||
| 		"| cut -d ' ' -f 1" | ||||
|  | ||||
| 	// FIXME timeout should be in global out-of-tree config | ||||
| 	c, err := container.New(containerName, time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	output, err := c.Run(tempDirBase, cmd) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	r, err := regexp.Compile("kernel-" + mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, pkg := range strings.Fields(output) { | ||||
| 		if r.MatchString(pkg) || strings.Contains(pkg, mask) { | ||||
| 			log.Trace().Msg(pkg) | ||||
| 			pkgs = append(pkgs, pkg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(pkgs) == 0 { | ||||
| 		log.Warn().Msg("no packages matched") | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func matchPackages(km config.KernelMask) (pkgs []string, err error) { | ||||
| 	switch km.DistroType { | ||||
| 	case config.Ubuntu: | ||||
| 		pkgs, err = matchDebImagePkg(km.DockerName(), km.ReleaseMask) | ||||
| 	case config.OracleLinux, config.CentOS: | ||||
| 		pkgs, err = matchOracleLinuxPkg(km.DockerName(), km.ReleaseMask) | ||||
| 	default: | ||||
| 		err = fmt.Errorf("%s not yet supported", km.DistroType.String()) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func dockerImagePath(sk config.KernelMask) (path string, err error) { | ||||
| 	usr, err := user.Current() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	path = usr.HomeDir + "/.out-of-tree/containers/" | ||||
| 	path += sk.DistroType.String() + "/" + sk.DistroRelease | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func vsyscallAvailable() (available bool, err error) { | ||||
| 	if runtime.GOOS != "linux" { | ||||
| 		// Docker for non-Linux systems is not using the host | ||||
| 		// kernel but uses kernel inside a virtual machine, so | ||||
| 		// it builds by the Docker team with vsyscall support. | ||||
| 		available = true | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	buf, err := ioutil.ReadFile("/proc/self/maps") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	available = strings.Contains(string(buf), "[vsyscall]") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func generateBaseDockerImage(registry string, commands []config.DockerCommand, | ||||
| 	sk config.KernelMask, forceUpdate bool) (err error) { | ||||
|  | ||||
| 	imagePath, err := dockerImagePath(sk) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	dockerPath := imagePath + "/Dockerfile" | ||||
|  | ||||
| 	d := "# BASE\n" | ||||
|  | ||||
| 	// TODO move as function to container.go | ||||
| 	cmd := exec.Command(container.Runtime, "images", "-q", sk.DockerName()) | ||||
| 	log.Debug().Msgf("run %v", cmd) | ||||
|  | ||||
| 	rawOutput, err := cmd.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if fs.PathExists(dockerPath) && string(rawOutput) != "" { | ||||
| 		log.Info().Msgf("Base image for %s:%s found", | ||||
| 			sk.DistroType.String(), sk.DistroRelease) | ||||
| 		if !forceUpdate { | ||||
| 			return | ||||
| 		} else { | ||||
| 			log.Info().Msgf("Update Containerfile") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	log.Info().Msgf("Base image for %s:%s not found, start generating", | ||||
| 		sk.DistroType.String(), sk.DistroRelease) | ||||
| 	os.MkdirAll(imagePath, os.ModePerm) | ||||
|  | ||||
| 	d += "FROM " | ||||
| 	if registry != "" { | ||||
| 		d += registry + "/" | ||||
| 	} | ||||
|  | ||||
| 	d += fmt.Sprintf("%s:%s\n", | ||||
| 		strings.ToLower(sk.DistroType.String()), | ||||
| 		sk.DistroRelease, | ||||
| 	) | ||||
|  | ||||
| 	for _, c := range commands { | ||||
| 		d += "RUN " + c.Command + "\n" | ||||
| 	} | ||||
|  | ||||
| 	switch sk.DistroType { | ||||
| 	case config.Ubuntu: | ||||
| 		if sk.DistroRelease < "14.04" { | ||||
| 			d += "RUN sed -i 's/archive.ubuntu.com/old-releases.ubuntu.com/' /etc/apt/sources.list\n" | ||||
| 		} | ||||
| 		d += "ENV DEBIAN_FRONTEND=noninteractive\n" | ||||
| 		d += "RUN apt-get update\n" | ||||
| 		d += "RUN apt-get install -y build-essential libelf-dev\n" | ||||
| 		d += "RUN apt-get install -y wget git\n" | ||||
| 		// Install a single kernel and headers to ensure all dependencies are cached | ||||
| 		if sk.DistroRelease >= "14.04" { | ||||
| 			d += "RUN export PKGNAME=$(apt-cache search --names-only '^linux-headers-[0-9\\.\\-]*-generic' | awk '{ print $1 }' | head -n 1); " + | ||||
| 				"apt-get install -y $PKGNAME $(echo $PKGNAME | sed 's/headers/image/'); " + | ||||
| 				"apt-get remove -y $PKGNAME $(echo $PKGNAME | sed 's/headers/image/')\n" | ||||
| 			d += "RUN apt-get install -y libseccomp-dev\n" | ||||
| 		} | ||||
| 		d += "RUN mkdir -p /lib/modules\n" | ||||
| 	case config.CentOS: | ||||
| 		var repos []string | ||||
|  | ||||
| 		switch sk.DistroRelease { | ||||
| 		case "6": | ||||
| 			repofmt := "[6.%d-%s]\\nbaseurl=https://vault.centos.org/6.%d/%s/$basearch/\\ngpgcheck=0" | ||||
| 			for i := 0; i <= 10; i++ { | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, i, "os", i, "os")) | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, i, "updates", i, "updates")) | ||||
| 			} | ||||
| 			d += "RUN rm /etc/yum.repos.d/*\n" | ||||
| 		case "7": | ||||
| 			repofmt := "[%s-%s]\\nbaseurl=https://vault.centos.org/%s/%s/$basearch/\\ngpgcheck=0" | ||||
| 			for _, ver := range []string{ | ||||
| 				"7.0.1406", "7.1.1503", "7.2.1511", | ||||
| 				"7.3.1611", "7.4.1708", "7.5.1804", | ||||
| 				"7.6.1810", "7.7.1908", "7.8.2003", | ||||
| 			} { | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, ver, "os", ver, "os")) | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, ver, "updates", ver, "updates")) | ||||
| 			} | ||||
|  | ||||
| 			// FIXME http/gpgcheck=0 | ||||
| 			repofmt = "[%s-%s]\\nbaseurl=http://mirror.centos.org/centos-7/%s/%s/$basearch/\\ngpgcheck=0" | ||||
| 			repos = append(repos, fmt.Sprintf(repofmt, "7.9.2009", "os", "7.9.2009", "os")) | ||||
| 			repos = append(repos, fmt.Sprintf(repofmt, "7.9.2009", "updates", "7.9.2009", "updates")) | ||||
| 		case "8": | ||||
| 			repofmt := "[%s]\\nbaseurl=https://vault.centos.org/%s/BaseOS/$basearch/os/\\ngpgcheck=0" | ||||
|  | ||||
| 			for _, ver := range []string{ | ||||
| 				"8.0.1905", "8.1.1911", "8.2.2004", | ||||
| 				"8.3.2011", "8.4.2105", "8.5.2111", | ||||
| 			} { | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, ver, ver)) | ||||
| 			} | ||||
| 		default: | ||||
| 			err = fmt.Errorf("no support for %s %s", sk.DistroType, sk.DistroRelease) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		d += "RUN sed -i 's/enabled=1/enabled=0/' /etc/yum.repos.d/* || true\n" | ||||
|  | ||||
| 		for _, repo := range repos { | ||||
| 			d += fmt.Sprintf("RUN echo -e '%s' >> /etc/yum.repos.d/oot.repo\n", repo) | ||||
| 		} | ||||
|  | ||||
| 		// do not remove old kernels | ||||
| 		d += "RUN sed -i 's;installonly_limit=;installonly_limit=100500;' /etc/yum.conf\n" | ||||
| 		d += "RUN yum -y update\n" | ||||
|  | ||||
| 		d += "RUN yum -y groupinstall 'Development Tools'\n" | ||||
|  | ||||
| 		if sk.DistroRelease < "8" { | ||||
| 			d += "RUN yum -y install deltarpm\n" | ||||
| 		} else { | ||||
| 			d += "RUN yum -y install grub2-tools-minimal " + | ||||
| 				"elfutils-libelf-devel\n" | ||||
| 		} | ||||
|  | ||||
| 		var flags string | ||||
| 		if sk.DistroRelease >= "8" { | ||||
| 			flags = "--noautoremove" | ||||
| 		} | ||||
|  | ||||
| 		// Cache kernel package dependencies | ||||
| 		d += "RUN export PKGNAME=$(yum search kernel-devel --showduplicates | grep '^kernel-devel' | cut -d ' ' -f 1 | head -n 1); " + | ||||
| 			"yum -y install $PKGNAME $(echo $PKGNAME | sed 's/-devel//'); " + | ||||
| 			fmt.Sprintf("yum -y remove $PKGNAME "+ | ||||
| 				"$(echo $PKGNAME | sed 's/-devel//') "+ | ||||
| 				"$(echo $PKGNAME | sed 's/-devel/-modules/') "+ | ||||
| 				"$(echo $PKGNAME | sed 's/-devel/-core/') %s\n", flags) | ||||
| 	case config.OracleLinux: | ||||
| 		if sk.DistroRelease < "6" { | ||||
| 			err = fmt.Errorf("no support for pre-EL6") | ||||
| 			return | ||||
| 		} | ||||
| 		d += "RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*\n" | ||||
| 		d += "RUN sed -i 's;installonly_limit=;installonly_limit=100500;' /etc/yum.conf /etc/dnf/dnf.conf || true\n" | ||||
| 		d += "RUN yum -y update\n" | ||||
| 		d += "RUN yum -y groupinstall 'Development Tools'\n" | ||||
| 		packages := "linux-firmware grubby" | ||||
| 		if sk.DistroRelease <= "7" { | ||||
| 			packages += " libdtrace-ctf" | ||||
| 		} | ||||
| 		d += fmt.Sprintf("RUN yum -y install %s\n", packages) | ||||
| 	default: | ||||
| 		err = fmt.Errorf("%s not yet supported", sk.DistroType.String()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	d += "# END BASE\n\n" | ||||
|  | ||||
| 	err = ioutil.WriteFile(dockerPath, []byte(d), 0644) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c, err := container.New(sk.DockerName(), time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	output, err := c.Build(imagePath) | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err).Msgf("Base image for %s:%s generating error", | ||||
| 			sk.DistroType.String(), sk.DistroRelease) | ||||
| 		log.Fatal().Msg(output) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Info().Msgf("Base image for %s:%s generating success", | ||||
| 		sk.DistroType.String(), sk.DistroRelease) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (err error) { | ||||
| 	slog := log.With(). | ||||
| 		Str("distro_type", sk.DistroType.String()). | ||||
| 		Str("distro_release", sk.DistroRelease). | ||||
| 		Str("pkg", pkgname). | ||||
| 		Logger() | ||||
|  | ||||
| 	c, err := container.New(sk.DockerName(), time.Hour) // TODO conf | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	moddirs, err := ioutil.ReadDir(c.Volumes.LibModules) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, krel := range moddirs { | ||||
| 		if strings.Contains(pkgname, krel.Name()) { | ||||
| 			if force { | ||||
| 				slog.Info().Msg("Reinstall") | ||||
| 			} else { | ||||
| 				slog.Info().Msg("Already installed") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	volumes := c.Volumes | ||||
|  | ||||
| 	c.Volumes.LibModules = "" | ||||
| 	c.Volumes.UsrSrc = "" | ||||
| 	c.Volumes.Boot = "" | ||||
|  | ||||
| 	slog.Debug().Msgf("Installing kernel") | ||||
|  | ||||
| 	cmd := "true" | ||||
|  | ||||
| 	switch sk.DistroType { | ||||
| 	case config.Ubuntu: | ||||
| 		var headerspkg string | ||||
| 		if headers { | ||||
| 			headerspkg = strings.Replace(pkgname, "image", "headers", -1) | ||||
| 		} | ||||
|  | ||||
| 		cmd += fmt.Sprintf(" && apt-get install -y %s %s", pkgname, headerspkg) | ||||
| 	case config.OracleLinux, config.CentOS: | ||||
| 		var headerspkg string | ||||
| 		if headers { | ||||
| 			if strings.Contains(pkgname, "uek") { | ||||
| 				headerspkg = strings.Replace(pkgname, | ||||
| 					"kernel-uek", "kernel-uek-devel", -1) | ||||
| 			} else { | ||||
| 				headerspkg = strings.Replace(pkgname, | ||||
| 					"kernel", "kernel-devel", -1) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		cmd += fmt.Sprintf(" && yum -y install %s %s", pkgname, headerspkg) | ||||
|  | ||||
| 		var version string | ||||
| 		if strings.Contains(pkgname, "uek") { | ||||
| 			version = strings.Replace(pkgname, "kernel-uek-", "", -1) | ||||
| 		} else { | ||||
| 			version = strings.Replace(pkgname, "kernel-", "", -1) | ||||
| 		} | ||||
|  | ||||
| 		if sk.DistroRelease <= "7" { | ||||
| 			cmd += fmt.Sprintf(" && dracut -v --add-drivers 'e1000 ext4' -f "+ | ||||
| 				"/boot/initramfs-%s.img %s", version, version) | ||||
| 		} else { | ||||
| 			cmd += fmt.Sprintf(" && dracut -v --add-drivers 'ata_piix libata' --force-drivers 'e1000 ext4 sd_mod' -f "+ | ||||
| 				"/boot/initramfs-%s.img %s", version, version) | ||||
| 		} | ||||
| 	default: | ||||
| 		err = fmt.Errorf("%s not yet supported", sk.DistroType.String()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c.Args = append(c.Args, "-v", volumes.LibModules+":/target/lib/modules") | ||||
| 	c.Args = append(c.Args, "-v", volumes.UsrSrc+":/target/usr/src") | ||||
| 	c.Args = append(c.Args, "-v", volumes.Boot+":/target/boot") | ||||
|  | ||||
| 	cmd += " && cp -r /boot /target/" | ||||
| 	cmd += " && cp -r /lib/modules /target/lib/" | ||||
| 	cmd += " && cp -r /usr/src /target/usr/" | ||||
|  | ||||
| 	_, err = c.Run("", cmd) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	slog.Debug().Msgf("Success") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func findKernelFile(files []os.FileInfo, kname string) (name string, err error) { | ||||
| 	for _, file := range files { | ||||
| 		if strings.HasPrefix(file.Name(), "vmlinuz") { | ||||
| 			if strings.Contains(file.Name(), kname) { | ||||
| 				name = file.Name() | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = errors.New("cannot find kernel") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func findInitrdFile(files []os.FileInfo, kname string) (name string, err error) { | ||||
| 	for _, file := range files { | ||||
| 		if strings.HasPrefix(file.Name(), "initrd") || | ||||
| 			strings.HasPrefix(file.Name(), "initramfs") { | ||||
|  | ||||
| 			if strings.Contains(file.Name(), kname) { | ||||
| 				name = file.Name() | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = errors.New("cannot find kernel") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func genRootfsImage(d container.Image, download bool) (rootfs string, err error) { | ||||
| 	usr, err := user.Current() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	imageFile := d.Name + ".img" | ||||
|  | ||||
| 	imagesPath := usr.HomeDir + "/.out-of-tree/images/" | ||||
| 	os.MkdirAll(imagesPath, os.ModePerm) | ||||
|  | ||||
| 	rootfs = imagesPath + imageFile | ||||
| 	if !fs.PathExists(rootfs) { | ||||
| 		if download { | ||||
| 			log.Info().Msgf("%v not available, start download", imageFile) | ||||
| 			err = downloadImage(imagesPath, imageFile) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func updateKernelsCfg(host, download bool) (err error) { | ||||
| 	newkcfg := config.KernelConfig{} | ||||
|  | ||||
| 	if host { | ||||
| 		// Get host kernels | ||||
| 		newkcfg, err = genHostKernels(download) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Get docker kernels | ||||
| 	dockerImages, err := container.Images() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range dockerImages { | ||||
| 		err = listContainersKernels(d, &newkcfg, download) | ||||
| 		if err != nil { | ||||
| 			log.Print("gen kernels", d.Name, ":", err) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	stripkcfg := config.KernelConfig{} | ||||
| 	for _, nk := range newkcfg.Kernels { | ||||
| 		if !hasKernel(nk, stripkcfg) { | ||||
| 			stripkcfg.Kernels = append(stripkcfg.Kernels, nk) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	buf, err := toml.Marshal(&stripkcfg) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	buf = append([]byte("# Autogenerated\n# DO NOT EDIT\n\n"), buf...) | ||||
|  | ||||
| 	usr, err := user.Current() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// TODO move all cfg path values to one provider | ||||
| 	kernelsCfgPath := usr.HomeDir + "/.out-of-tree/kernels.toml" | ||||
| 	err = ioutil.WriteFile(kernelsCfgPath, buf, 0644) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Info().Msgf("%s is successfully updated", kernelsCfgPath) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func listContainersKernels(dii container.Image, newkcfg *config.KernelConfig, | ||||
| 	download bool) (err error) { | ||||
|  | ||||
| 	rootfs, err := genRootfsImage(dii, download) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c, err := container.New(dii.Name, time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	moddirs, err := ioutil.ReadDir(c.Volumes.LibModules) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	bootfiles, err := ioutil.ReadDir(c.Volumes.Boot) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, krel := range moddirs { | ||||
| 		log.Debug().Msgf("generate config entry for %s", krel.Name()) | ||||
|  | ||||
| 		var kernelFile, initrdFile string | ||||
| 		kernelFile, err = findKernelFile(bootfiles, krel.Name()) | ||||
| 		if err != nil { | ||||
| 			log.Warn().Msgf("cannot find kernel %s", krel.Name()) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		initrdFile, err = findInitrdFile(bootfiles, krel.Name()) | ||||
| 		if err != nil { | ||||
| 			log.Warn().Msgf("cannot find initrd %s", krel.Name()) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		ki := config.KernelInfo{ | ||||
| 			DistroType:    dii.DistroType, | ||||
| 			DistroRelease: dii.DistroRelease, | ||||
| 			KernelRelease: krel.Name(), | ||||
| 			ContainerName: dii.Name, | ||||
|  | ||||
| 			KernelPath:  c.Volumes.Boot + "/" + kernelFile, | ||||
| 			InitrdPath:  c.Volumes.Boot + "/" + initrdFile, | ||||
| 			ModulesPath: c.Volumes.LibModules + "/" + krel.Name(), | ||||
|  | ||||
| 			RootFS: rootfs, | ||||
| 		} | ||||
| 		newkcfg.Kernels = append(newkcfg.Kernels, ki) | ||||
| 	} | ||||
|  | ||||
| 	for _, cmd := range []string{ | ||||
| 		"find /boot -type f -exec chmod a+r {} \\;", | ||||
| 	} { | ||||
| 		_, err = c.Run(tempDirBase, cmd) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func hasKernel(ki config.KernelInfo, kcfg config.KernelConfig) bool { | ||||
| 	for _, sk := range kcfg.Kernels { | ||||
| 		if sk == ki { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func shuffleStrings(a []string) []string { | ||||
| 	// Fisher–Yates shuffle | ||||
| 	for i := len(a) - 1; i > 0; i-- { | ||||
| 		j := rand.Intn(i + 1) | ||||
| 		a[i], a[j] = a[j], a[i] | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| func setSigintHandler(variable *bool) { | ||||
| 	c := make(chan os.Signal, 1) | ||||
| 	signal.Notify(c, os.Interrupt) | ||||
| 	go func() { | ||||
| 		counter := 0 | ||||
| 		for _ = range c { | ||||
| 			if counter == 0 { | ||||
| 				*variable = true | ||||
| 				log.Warn().Msg("shutdown requested, finishing work") | ||||
| 				log.Info().Msg("^C a couple of times more for an unsafe exit") | ||||
| 			} else if counter >= 3 { | ||||
| 				log.Fatal().Msg("unsafe exit") | ||||
| 			} | ||||
|  | ||||
| 			counter += 1 | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| } | ||||
|  | ||||
| // FIXME too many parameters | ||||
| func generateKernels(km config.KernelMask, registry string, | ||||
| 	commands []config.DockerCommand, max, retries int64, | ||||
| 	download, force, headers, shuffle, update bool, | ||||
| 	shutdown *bool) (err error) { | ||||
|  | ||||
| 	log.Info().Msgf("Generating for kernel mask %v", km) | ||||
|  | ||||
| 	_, err = genRootfsImage(container.Image{Name: km.DockerName()}, | ||||
| 		download) | ||||
| 	if err != nil || *shutdown { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = generateBaseDockerImage(registry, commands, km, update) | ||||
| 	if err != nil || *shutdown { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pkgs, err := matchPackages(km) | ||||
| 	if err != nil || *shutdown { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if shuffle { | ||||
| 		pkgs = shuffleStrings(pkgs) | ||||
| 	} | ||||
| 	for i, pkg := range pkgs { | ||||
| 		if max <= 0 { | ||||
| 			log.Print("Max is reached") | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		if *shutdown { | ||||
| 			err = nil | ||||
| 			return | ||||
| 		} | ||||
| 		log.Info().Msgf("%d/%d %s", i+1, len(pkgs), pkg) | ||||
|  | ||||
| 		var attempt int64 | ||||
| 		for { | ||||
| 			attempt++ | ||||
|  | ||||
| 			if *shutdown { | ||||
| 				err = nil | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			err = installKernel(km, pkg, force, headers) | ||||
| 			if err == nil { | ||||
| 				max-- | ||||
| 				break | ||||
| 			} else if attempt >= retries { | ||||
| 				log.Error().Err(err).Msg("install kernel") | ||||
| 				log.Debug().Msg("skip") | ||||
| 				break | ||||
| 			} else { | ||||
| 				log.Warn().Err(err).Msg("install kernel") | ||||
| 				time.Sleep(time.Second) | ||||
| 				log.Info().Msg("retry") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| 	return kernel.UpdateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload) | ||||
| } | ||||
|   | ||||
							
								
								
									
										689
									
								
								kernel/kernel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										689
									
								
								kernel/kernel.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,689 @@ | ||||
| // Copyright 2023 Mikhail Klementev. All rights reserved. | ||||
| // Use of this source code is governed by a AGPLv3 license | ||||
| // (or later) that can be found in the LICENSE file. | ||||
|  | ||||
| package kernel | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"os/signal" | ||||
| 	"os/user" | ||||
| 	"regexp" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/naoina/toml" | ||||
| 	"github.com/rs/zerolog/log" | ||||
|  | ||||
| 	"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/fs" | ||||
| ) | ||||
|  | ||||
| func matchDebImagePkg(containerName, mask string) (pkgs []string, err error) { | ||||
|  | ||||
| 	cmd := "apt-cache search --names-only '^linux-image-[0-9\\.\\-]*-generic' | awk '{ print $1 }'" | ||||
|  | ||||
| 	// FIXME timeout should be in global out-of-tree config | ||||
| 	c, err := container.New(containerName, time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	output, err := c.Run(config.Dir("tmp"), cmd) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	r, err := regexp.Compile("linux-image-" + mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, pkg := range strings.Fields(output) { | ||||
| 		if r.MatchString(pkg) || strings.Contains(pkg, mask) { | ||||
| 			pkgs = append(pkgs, pkg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func matchOracleLinuxPkg(containerName, mask string) ( | ||||
| 	pkgs []string, err error) { | ||||
|  | ||||
| 	cmd := "yum search kernel --showduplicates " + | ||||
| 		"| grep '^kernel-[0-9]\\|^kernel-uek-[0-9]' " + | ||||
| 		"| grep -v src " + | ||||
| 		"| cut -d ' ' -f 1" | ||||
|  | ||||
| 	// FIXME timeout should be in global out-of-tree config | ||||
| 	c, err := container.New(containerName, time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	output, err := c.Run(config.Dir("tmp"), cmd) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	r, err := regexp.Compile("kernel-" + mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, pkg := range strings.Fields(output) { | ||||
| 		if r.MatchString(pkg) || strings.Contains(pkg, mask) { | ||||
| 			log.Trace().Msg(pkg) | ||||
| 			pkgs = append(pkgs, pkg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(pkgs) == 0 { | ||||
| 		log.Warn().Msg("no packages matched") | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func MatchPackages(km config.KernelMask) (pkgs []string, err error) { | ||||
| 	switch km.DistroType { | ||||
| 	case config.Ubuntu: | ||||
| 		pkgs, err = matchDebImagePkg(km.DockerName(), km.ReleaseMask) | ||||
| 	case config.OracleLinux, config.CentOS: | ||||
| 		pkgs, err = matchOracleLinuxPkg(km.DockerName(), km.ReleaseMask) | ||||
| 	default: | ||||
| 		err = fmt.Errorf("%s not yet supported", km.DistroType.String()) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func dockerImagePath(sk config.KernelMask) (path string, err error) { | ||||
| 	usr, err := user.Current() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	path = usr.HomeDir + "/.out-of-tree/containers/" | ||||
| 	path += sk.DistroType.String() + "/" + sk.DistroRelease | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func vsyscallAvailable() (available bool, err error) { | ||||
| 	if runtime.GOOS != "linux" { | ||||
| 		// Docker for non-Linux systems is not using the host | ||||
| 		// kernel but uses kernel inside a virtual machine, so | ||||
| 		// it builds by the Docker team with vsyscall support. | ||||
| 		available = true | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	buf, err := ioutil.ReadFile("/proc/self/maps") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	available = strings.Contains(string(buf), "[vsyscall]") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func GenerateBaseDockerImage(registry string, commands []config.DockerCommand, | ||||
| 	sk config.KernelMask, forceUpdate bool) (err error) { | ||||
|  | ||||
| 	imagePath, err := dockerImagePath(sk) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	dockerPath := imagePath + "/Dockerfile" | ||||
|  | ||||
| 	d := "# BASE\n" | ||||
|  | ||||
| 	// TODO move as function to container.go | ||||
| 	cmd := exec.Command(container.Runtime, "images", "-q", sk.DockerName()) | ||||
| 	log.Debug().Msgf("run %v", cmd) | ||||
|  | ||||
| 	rawOutput, err := cmd.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if fs.PathExists(dockerPath) && string(rawOutput) != "" { | ||||
| 		log.Info().Msgf("Base image for %s:%s found", | ||||
| 			sk.DistroType.String(), sk.DistroRelease) | ||||
| 		if !forceUpdate { | ||||
| 			return | ||||
| 		} else { | ||||
| 			log.Info().Msgf("Update Containerfile") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	log.Info().Msgf("Base image for %s:%s not found, start generating", | ||||
| 		sk.DistroType.String(), sk.DistroRelease) | ||||
| 	os.MkdirAll(imagePath, os.ModePerm) | ||||
|  | ||||
| 	d += "FROM " | ||||
| 	if registry != "" { | ||||
| 		d += registry + "/" | ||||
| 	} | ||||
|  | ||||
| 	d += fmt.Sprintf("%s:%s\n", | ||||
| 		strings.ToLower(sk.DistroType.String()), | ||||
| 		sk.DistroRelease, | ||||
| 	) | ||||
|  | ||||
| 	for _, c := range commands { | ||||
| 		d += "RUN " + c.Command + "\n" | ||||
| 	} | ||||
|  | ||||
| 	switch sk.DistroType { | ||||
| 	case config.Ubuntu: | ||||
| 		if sk.DistroRelease < "14.04" { | ||||
| 			d += "RUN sed -i 's/archive.ubuntu.com/old-releases.ubuntu.com/' /etc/apt/sources.list\n" | ||||
| 		} | ||||
| 		d += "ENV DEBIAN_FRONTEND=noninteractive\n" | ||||
| 		d += "RUN apt-get update\n" | ||||
| 		d += "RUN apt-get install -y build-essential libelf-dev\n" | ||||
| 		d += "RUN apt-get install -y wget git\n" | ||||
| 		// Install a single kernel and headers to ensure all dependencies are cached | ||||
| 		if sk.DistroRelease >= "14.04" { | ||||
| 			d += "RUN export PKGNAME=$(apt-cache search --names-only '^linux-headers-[0-9\\.\\-]*-generic' | awk '{ print $1 }' | head -n 1); " + | ||||
| 				"apt-get install -y $PKGNAME $(echo $PKGNAME | sed 's/headers/image/'); " + | ||||
| 				"apt-get remove -y $PKGNAME $(echo $PKGNAME | sed 's/headers/image/')\n" | ||||
| 			d += "RUN apt-get install -y libseccomp-dev\n" | ||||
| 		} | ||||
| 		d += "RUN mkdir -p /lib/modules\n" | ||||
| 	case config.CentOS: | ||||
| 		var repos []string | ||||
|  | ||||
| 		switch sk.DistroRelease { | ||||
| 		case "6": | ||||
| 			repofmt := "[6.%d-%s]\\nbaseurl=https://vault.centos.org/6.%d/%s/$basearch/\\ngpgcheck=0" | ||||
| 			for i := 0; i <= 10; i++ { | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, i, "os", i, "os")) | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, i, "updates", i, "updates")) | ||||
| 			} | ||||
| 			d += "RUN rm /etc/yum.repos.d/*\n" | ||||
| 		case "7": | ||||
| 			repofmt := "[%s-%s]\\nbaseurl=https://vault.centos.org/%s/%s/$basearch/\\ngpgcheck=0" | ||||
| 			for _, ver := range []string{ | ||||
| 				"7.0.1406", "7.1.1503", "7.2.1511", | ||||
| 				"7.3.1611", "7.4.1708", "7.5.1804", | ||||
| 				"7.6.1810", "7.7.1908", "7.8.2003", | ||||
| 			} { | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, ver, "os", ver, "os")) | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, ver, "updates", ver, "updates")) | ||||
| 			} | ||||
|  | ||||
| 			// FIXME http/gpgcheck=0 | ||||
| 			repofmt = "[%s-%s]\\nbaseurl=http://mirror.centos.org/centos-7/%s/%s/$basearch/\\ngpgcheck=0" | ||||
| 			repos = append(repos, fmt.Sprintf(repofmt, "7.9.2009", "os", "7.9.2009", "os")) | ||||
| 			repos = append(repos, fmt.Sprintf(repofmt, "7.9.2009", "updates", "7.9.2009", "updates")) | ||||
| 		case "8": | ||||
| 			repofmt := "[%s]\\nbaseurl=https://vault.centos.org/%s/BaseOS/$basearch/os/\\ngpgcheck=0" | ||||
|  | ||||
| 			for _, ver := range []string{ | ||||
| 				"8.0.1905", "8.1.1911", "8.2.2004", | ||||
| 				"8.3.2011", "8.4.2105", "8.5.2111", | ||||
| 			} { | ||||
| 				repos = append(repos, fmt.Sprintf(repofmt, ver, ver)) | ||||
| 			} | ||||
| 		default: | ||||
| 			err = fmt.Errorf("no support for %s %s", sk.DistroType, sk.DistroRelease) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		d += "RUN sed -i 's/enabled=1/enabled=0/' /etc/yum.repos.d/* || true\n" | ||||
|  | ||||
| 		for _, repo := range repos { | ||||
| 			d += fmt.Sprintf("RUN echo -e '%s' >> /etc/yum.repos.d/oot.repo\n", repo) | ||||
| 		} | ||||
|  | ||||
| 		// do not remove old kernels | ||||
| 		d += "RUN sed -i 's;installonly_limit=;installonly_limit=100500;' /etc/yum.conf\n" | ||||
| 		d += "RUN yum -y update\n" | ||||
|  | ||||
| 		d += "RUN yum -y groupinstall 'Development Tools'\n" | ||||
|  | ||||
| 		if sk.DistroRelease < "8" { | ||||
| 			d += "RUN yum -y install deltarpm\n" | ||||
| 		} else { | ||||
| 			d += "RUN yum -y install grub2-tools-minimal " + | ||||
| 				"elfutils-libelf-devel\n" | ||||
| 		} | ||||
|  | ||||
| 		var flags string | ||||
| 		if sk.DistroRelease >= "8" { | ||||
| 			flags = "--noautoremove" | ||||
| 		} | ||||
|  | ||||
| 		// Cache kernel package dependencies | ||||
| 		d += "RUN export PKGNAME=$(yum search kernel-devel --showduplicates | grep '^kernel-devel' | cut -d ' ' -f 1 | head -n 1); " + | ||||
| 			"yum -y install $PKGNAME $(echo $PKGNAME | sed 's/-devel//'); " + | ||||
| 			fmt.Sprintf("yum -y remove $PKGNAME "+ | ||||
| 				"$(echo $PKGNAME | sed 's/-devel//') "+ | ||||
| 				"$(echo $PKGNAME | sed 's/-devel/-modules/') "+ | ||||
| 				"$(echo $PKGNAME | sed 's/-devel/-core/') %s\n", flags) | ||||
| 	case config.OracleLinux: | ||||
| 		if sk.DistroRelease < "6" { | ||||
| 			err = fmt.Errorf("no support for pre-EL6") | ||||
| 			return | ||||
| 		} | ||||
| 		d += "RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*\n" | ||||
| 		d += "RUN sed -i 's;installonly_limit=;installonly_limit=100500;' /etc/yum.conf /etc/dnf/dnf.conf || true\n" | ||||
| 		d += "RUN yum -y update\n" | ||||
| 		d += "RUN yum -y groupinstall 'Development Tools'\n" | ||||
| 		packages := "linux-firmware grubby" | ||||
| 		if sk.DistroRelease <= "7" { | ||||
| 			packages += " libdtrace-ctf" | ||||
| 		} | ||||
| 		d += fmt.Sprintf("RUN yum -y install %s\n", packages) | ||||
| 	default: | ||||
| 		err = fmt.Errorf("%s not yet supported", sk.DistroType.String()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	d += "# END BASE\n\n" | ||||
|  | ||||
| 	err = ioutil.WriteFile(dockerPath, []byte(d), 0644) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c, err := container.New(sk.DockerName(), time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	output, err := c.Build(imagePath) | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err).Msgf("Base image for %s:%s generating error", | ||||
| 			sk.DistroType.String(), sk.DistroRelease) | ||||
| 		log.Fatal().Msg(output) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Info().Msgf("Base image for %s:%s generating success", | ||||
| 		sk.DistroType.String(), sk.DistroRelease) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func installKernel(sk config.KernelMask, pkgname string, force, headers bool) (err error) { | ||||
| 	slog := log.With(). | ||||
| 		Str("distro_type", sk.DistroType.String()). | ||||
| 		Str("distro_release", sk.DistroRelease). | ||||
| 		Str("pkg", pkgname). | ||||
| 		Logger() | ||||
|  | ||||
| 	c, err := container.New(sk.DockerName(), time.Hour) // TODO conf | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	moddirs, err := ioutil.ReadDir(c.Volumes.LibModules) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, krel := range moddirs { | ||||
| 		if strings.Contains(pkgname, krel.Name()) { | ||||
| 			if force { | ||||
| 				slog.Info().Msg("Reinstall") | ||||
| 			} else { | ||||
| 				slog.Info().Msg("Already installed") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	volumes := c.Volumes | ||||
|  | ||||
| 	c.Volumes.LibModules = "" | ||||
| 	c.Volumes.UsrSrc = "" | ||||
| 	c.Volumes.Boot = "" | ||||
|  | ||||
| 	slog.Debug().Msgf("Installing kernel") | ||||
|  | ||||
| 	cmd := "true" | ||||
|  | ||||
| 	switch sk.DistroType { | ||||
| 	case config.Ubuntu: | ||||
| 		var headerspkg string | ||||
| 		if headers { | ||||
| 			headerspkg = strings.Replace(pkgname, "image", "headers", -1) | ||||
| 		} | ||||
|  | ||||
| 		cmd += fmt.Sprintf(" && apt-get install -y %s %s", pkgname, headerspkg) | ||||
| 	case config.OracleLinux, config.CentOS: | ||||
| 		var headerspkg string | ||||
| 		if headers { | ||||
| 			if strings.Contains(pkgname, "uek") { | ||||
| 				headerspkg = strings.Replace(pkgname, | ||||
| 					"kernel-uek", "kernel-uek-devel", -1) | ||||
| 			} else { | ||||
| 				headerspkg = strings.Replace(pkgname, | ||||
| 					"kernel", "kernel-devel", -1) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		cmd += fmt.Sprintf(" && yum -y install %s %s", pkgname, headerspkg) | ||||
|  | ||||
| 		var version string | ||||
| 		if strings.Contains(pkgname, "uek") { | ||||
| 			version = strings.Replace(pkgname, "kernel-uek-", "", -1) | ||||
| 		} else { | ||||
| 			version = strings.Replace(pkgname, "kernel-", "", -1) | ||||
| 		} | ||||
|  | ||||
| 		if sk.DistroRelease <= "7" { | ||||
| 			cmd += fmt.Sprintf(" && dracut -v --add-drivers 'e1000 ext4' -f "+ | ||||
| 				"/boot/initramfs-%s.img %s", version, version) | ||||
| 		} else { | ||||
| 			cmd += fmt.Sprintf(" && dracut -v --add-drivers 'ata_piix libata' --force-drivers 'e1000 ext4 sd_mod' -f "+ | ||||
| 				"/boot/initramfs-%s.img %s", version, version) | ||||
| 		} | ||||
| 	default: | ||||
| 		err = fmt.Errorf("%s not yet supported", sk.DistroType.String()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c.Args = append(c.Args, "-v", volumes.LibModules+":/target/lib/modules") | ||||
| 	c.Args = append(c.Args, "-v", volumes.UsrSrc+":/target/usr/src") | ||||
| 	c.Args = append(c.Args, "-v", volumes.Boot+":/target/boot") | ||||
|  | ||||
| 	cmd += " && cp -r /boot /target/" | ||||
| 	cmd += " && cp -r /lib/modules /target/lib/" | ||||
| 	cmd += " && cp -r /usr/src /target/usr/" | ||||
|  | ||||
| 	_, err = c.Run("", cmd) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	slog.Debug().Msgf("Success") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func findKernelFile(files []os.FileInfo, kname string) (name string, err error) { | ||||
| 	for _, file := range files { | ||||
| 		if strings.HasPrefix(file.Name(), "vmlinuz") { | ||||
| 			if strings.Contains(file.Name(), kname) { | ||||
| 				name = file.Name() | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = errors.New("cannot find kernel") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func findInitrdFile(files []os.FileInfo, kname string) (name string, err error) { | ||||
| 	for _, file := range files { | ||||
| 		if strings.HasPrefix(file.Name(), "initrd") || | ||||
| 			strings.HasPrefix(file.Name(), "initramfs") { | ||||
|  | ||||
| 			if strings.Contains(file.Name(), kname) { | ||||
| 				name = file.Name() | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = errors.New("cannot find kernel") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func GenRootfsImage(d container.Image, download bool) (rootfs string, err error) { | ||||
| 	usr, err := user.Current() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	imageFile := d.Name + ".img" | ||||
|  | ||||
| 	imagesPath := usr.HomeDir + "/.out-of-tree/images/" | ||||
| 	os.MkdirAll(imagesPath, os.ModePerm) | ||||
|  | ||||
| 	rootfs = imagesPath + imageFile | ||||
| 	if !fs.PathExists(rootfs) { | ||||
| 		if download { | ||||
| 			log.Info().Msgf("%v not available, start download", imageFile) | ||||
| 			err = cache.DownloadQemuImage(imagesPath, imageFile) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func UpdateKernelsCfg(host, download bool) (err error) { | ||||
| 	newkcfg := config.KernelConfig{} | ||||
|  | ||||
| 	if host { | ||||
| 		// Get host kernels | ||||
| 		newkcfg, err = genHostKernels(download) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Get docker kernels | ||||
| 	dockerImages, err := container.Images() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range dockerImages { | ||||
| 		err = listContainersKernels(d, &newkcfg, download) | ||||
| 		if err != nil { | ||||
| 			log.Print("gen kernels", d.Name, ":", err) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	stripkcfg := config.KernelConfig{} | ||||
| 	for _, nk := range newkcfg.Kernels { | ||||
| 		if !hasKernel(nk, stripkcfg) { | ||||
| 			stripkcfg.Kernels = append(stripkcfg.Kernels, nk) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	buf, err := toml.Marshal(&stripkcfg) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	buf = append([]byte("# Autogenerated\n# DO NOT EDIT\n\n"), buf...) | ||||
|  | ||||
| 	usr, err := user.Current() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// TODO move all cfg path values to one provider | ||||
| 	kernelsCfgPath := usr.HomeDir + "/.out-of-tree/kernels.toml" | ||||
| 	err = ioutil.WriteFile(kernelsCfgPath, buf, 0644) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Info().Msgf("%s is successfully updated", kernelsCfgPath) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func listContainersKernels(dii container.Image, newkcfg *config.KernelConfig, | ||||
| 	download bool) (err error) { | ||||
|  | ||||
| 	rootfs, err := GenRootfsImage(dii, download) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c, err := container.New(dii.Name, time.Hour) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	moddirs, err := ioutil.ReadDir(c.Volumes.LibModules) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	bootfiles, err := ioutil.ReadDir(c.Volumes.Boot) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, krel := range moddirs { | ||||
| 		log.Debug().Msgf("generate config entry for %s", krel.Name()) | ||||
|  | ||||
| 		var kernelFile, initrdFile string | ||||
| 		kernelFile, err = findKernelFile(bootfiles, krel.Name()) | ||||
| 		if err != nil { | ||||
| 			log.Warn().Msgf("cannot find kernel %s", krel.Name()) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		initrdFile, err = findInitrdFile(bootfiles, krel.Name()) | ||||
| 		if err != nil { | ||||
| 			log.Warn().Msgf("cannot find initrd %s", krel.Name()) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		ki := config.KernelInfo{ | ||||
| 			DistroType:    dii.DistroType, | ||||
| 			DistroRelease: dii.DistroRelease, | ||||
| 			KernelRelease: krel.Name(), | ||||
| 			ContainerName: dii.Name, | ||||
|  | ||||
| 			KernelPath:  c.Volumes.Boot + "/" + kernelFile, | ||||
| 			InitrdPath:  c.Volumes.Boot + "/" + initrdFile, | ||||
| 			ModulesPath: c.Volumes.LibModules + "/" + krel.Name(), | ||||
|  | ||||
| 			RootFS: rootfs, | ||||
| 		} | ||||
| 		newkcfg.Kernels = append(newkcfg.Kernels, ki) | ||||
| 	} | ||||
|  | ||||
| 	for _, cmd := range []string{ | ||||
| 		"find /boot -type f -exec chmod a+r {} \\;", | ||||
| 	} { | ||||
| 		_, err = c.Run(config.Dir("tmp"), cmd) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func hasKernel(ki config.KernelInfo, kcfg config.KernelConfig) bool { | ||||
| 	for _, sk := range kcfg.Kernels { | ||||
| 		if sk == ki { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func shuffleStrings(a []string) []string { | ||||
| 	// Fisher–Yates shuffle | ||||
| 	for i := len(a) - 1; i > 0; i-- { | ||||
| 		j := rand.Intn(i + 1) | ||||
| 		a[i], a[j] = a[j], a[i] | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| func SetSigintHandler(variable *bool) { | ||||
| 	c := make(chan os.Signal, 1) | ||||
| 	signal.Notify(c, os.Interrupt) | ||||
| 	go func() { | ||||
| 		counter := 0 | ||||
| 		for _ = range c { | ||||
| 			if counter == 0 { | ||||
| 				*variable = true | ||||
| 				log.Warn().Msg("shutdown requested, finishing work") | ||||
| 				log.Info().Msg("^C a couple of times more for an unsafe exit") | ||||
| 			} else if counter >= 3 { | ||||
| 				log.Fatal().Msg("unsafe exit") | ||||
| 			} | ||||
|  | ||||
| 			counter += 1 | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| } | ||||
|  | ||||
| // FIXME too many parameters | ||||
| func GenerateKernels(km config.KernelMask, registry string, | ||||
| 	commands []config.DockerCommand, max, retries int64, | ||||
| 	download, force, headers, shuffle, update bool, | ||||
| 	shutdown *bool) (err error) { | ||||
|  | ||||
| 	log.Info().Msgf("Generating for kernel mask %v", km) | ||||
|  | ||||
| 	_, err = GenRootfsImage(container.Image{Name: km.DockerName()}, | ||||
| 		download) | ||||
| 	if err != nil || *shutdown { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = GenerateBaseDockerImage(registry, commands, km, update) | ||||
| 	if err != nil || *shutdown { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pkgs, err := MatchPackages(km) | ||||
| 	if err != nil || *shutdown { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if shuffle { | ||||
| 		pkgs = shuffleStrings(pkgs) | ||||
| 	} | ||||
| 	for i, pkg := range pkgs { | ||||
| 		if max <= 0 { | ||||
| 			log.Print("Max is reached") | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		if *shutdown { | ||||
| 			err = nil | ||||
| 			return | ||||
| 		} | ||||
| 		log.Info().Msgf("%d/%d %s", i+1, len(pkgs), pkg) | ||||
|  | ||||
| 		var attempt int64 | ||||
| 		for { | ||||
| 			attempt++ | ||||
|  | ||||
| 			if *shutdown { | ||||
| 				err = nil | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			err = installKernel(km, pkg, force, headers) | ||||
| 			if err == nil { | ||||
| 				max-- | ||||
| 				break | ||||
| 			} else if attempt >= retries { | ||||
| 				log.Error().Err(err).Msg("install kernel") | ||||
| 				log.Debug().Msg("skip") | ||||
| 				break | ||||
| 			} else { | ||||
| 				log.Warn().Err(err).Msg("install kernel") | ||||
| 				time.Sleep(time.Second) | ||||
| 				log.Info().Msg("retry") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
| @@ -5,7 +5,7 @@ | ||||
| //go:build linux | ||||
| // +build linux | ||||
| 
 | ||||
| package main | ||||
| package kernel | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| @@ -52,7 +52,7 @@ func genHostKernels(download bool) (kcfg config.KernelConfig, err error) { | ||||
| 		}.DockerName(), | ||||
| 	} | ||||
| 
 | ||||
| 	rootfs, err := genRootfsImage(dii, download) | ||||
| 	rootfs, err := GenRootfsImage(dii, download) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| @@ -1,10 +1,11 @@ | ||||
| // Copyright 2018 Mikhail Klementev. All rights reserved. | ||||
| // Copyright 2023 Mikhail Klementev. All rights reserved. | ||||
| // Use of this source code is governed by a AGPLv3 license | ||||
| // (or later) that can be found in the LICENSE file. | ||||
| 
 | ||||
| //go:build darwin | ||||
| // +build darwin | ||||
| 
 | ||||
| package main | ||||
| package kernel | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
		Reference in New Issue
	
	Block a user