Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
24
tools/package_incus_test/README.md
Normal file
24
tools/package_incus_test/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Test Package Files with LXD
|
||||
|
||||
Used to test the RPM and DEB packages using LXD across a variety of
|
||||
distributions.
|
||||
|
||||
The image will add the InfluxData repo, install Telegraf, and ensure the
|
||||
service is running. At that point the new package will get installed and
|
||||
ensure the service is still running.
|
||||
|
||||
Any issues or errors will cause the test to fail.
|
||||
|
||||
## CLI
|
||||
|
||||
To test an RPM or DEB with a specific image:
|
||||
|
||||
```sh
|
||||
./package-test-lxd --package telegraf_1.21.4-1_amd64.deb --image debian/bullseye
|
||||
```
|
||||
|
||||
To test an RPM or a DEB with a whole set of images:
|
||||
|
||||
```sh
|
||||
./package-test-lxd --package telegraf_1.21.4-1_amd64.deb
|
||||
```
|
361
tools/package_incus_test/container.go
Normal file
361
tools/package_incus_test/container.go
Normal file
|
@ -0,0 +1,361 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const influxDataRPMRepo = `[influxdata]
|
||||
name = InfluxData Repository - Stable
|
||||
baseurl = https://repos.influxdata.com/stable/x86_64/main
|
||||
enabled = 1
|
||||
gpgcheck = 1
|
||||
gpgkey = https://repos.influxdata.com/influxdata-archive_compat.key
|
||||
`
|
||||
|
||||
type Container struct {
|
||||
Name string
|
||||
|
||||
client IncusClient
|
||||
packageManager string
|
||||
}
|
||||
|
||||
// create container with given name and image
|
||||
func (c *Container) Create(image string) error {
|
||||
if c.Name == "" {
|
||||
return errors.New("unable to create container: no name given")
|
||||
}
|
||||
|
||||
c.client = IncusClient{}
|
||||
err := c.client.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to incus: %w", err)
|
||||
}
|
||||
|
||||
err = c.client.Create(c.Name, "images", image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create instance: %w", err)
|
||||
}
|
||||
|
||||
// at this point the container is created, so on any error during setup
|
||||
// we want to delete it as well
|
||||
err = c.client.Start(c.Name)
|
||||
if err != nil {
|
||||
c.Delete()
|
||||
return fmt.Errorf("failed to start instance: %w", err)
|
||||
}
|
||||
|
||||
if err := c.detectPackageManager(); err != nil {
|
||||
c.Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.waitForNetwork(); err != nil {
|
||||
c.Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.setupRepo(); err != nil {
|
||||
c.Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete the container
|
||||
func (c *Container) Delete() {
|
||||
//nolint:errcheck // cleaning up state so no need to check for error
|
||||
c.client.Stop(c.Name)
|
||||
//nolint:errcheck // cleaning up state so no need to check for error
|
||||
c.client.Delete(c.Name)
|
||||
}
|
||||
|
||||
// installs the package from configured repos
|
||||
func (c *Container) Install(packageName ...string) error {
|
||||
var cmd []string
|
||||
switch c.packageManager {
|
||||
case "apt":
|
||||
cmd = append([]string{"apt-get", "install", "--yes"}, packageName...)
|
||||
case "yum":
|
||||
cmd = append([]string{"yum", "install", "-y"}, packageName...)
|
||||
case "dnf":
|
||||
cmd = append([]string{"dnf", "install", "-y"}, packageName...)
|
||||
case "zypper":
|
||||
cmd = append([]string{"zypper", "--gpg-auto-import-keys", "install", "-y"}, packageName...)
|
||||
}
|
||||
|
||||
err := c.client.Exec(c.Name, cmd...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) CheckStatus(serviceName string) error {
|
||||
// push a valid config first, then start the service
|
||||
err := c.client.Exec(
|
||||
c.Name,
|
||||
"bash",
|
||||
"-c",
|
||||
"--",
|
||||
"echo '[[inputs.cpu]]\n[[outputs.file]]' | "+
|
||||
"tee /etc/telegraf/telegraf.conf",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(
|
||||
c.Name,
|
||||
"bash",
|
||||
"-c",
|
||||
"--",
|
||||
"ls -la /etc/telegraf/",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(c.Name, "systemctl", "start", serviceName)
|
||||
if err != nil {
|
||||
//nolint:errcheck // cleaning up state so no need to check for error
|
||||
c.client.Exec(c.Name, "systemctl", "status", serviceName)
|
||||
//nolint:errcheck // cleaning up state so no need to check for error
|
||||
c.client.Exec(c.Name, "journalctl", "--no-pager", "--unit", serviceName)
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(c.Name, "systemctl", "status", serviceName)
|
||||
if err != nil {
|
||||
//nolint:errcheck // cleaning up state so no need to check for error
|
||||
c.client.Exec(c.Name, "journalctl", "--no-pager", "--unit", serviceName)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) UploadAndInstall(filename string) error {
|
||||
basename := filepath.Base(filename)
|
||||
destination := "/root/" + basename
|
||||
|
||||
if err := c.client.Push(c.Name, filename, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Install(destination)
|
||||
}
|
||||
|
||||
// Push key and config and update
|
||||
func (c *Container) configureApt() error {
|
||||
err := c.client.Exec(c.Name, "apt-get", "update")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.Install("ca-certificates", "gpg", "wget")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(c.Name, "wget", "https://repos.influxdata.com/influxdata-archive_compat.key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(
|
||||
c.Name,
|
||||
"bash",
|
||||
"-c",
|
||||
"--",
|
||||
"echo '393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c influxdata-archive_compat.key' | "+
|
||||
"sha256sum -c && cat influxdata-archive_compat.key | gpg --dearmor | "+
|
||||
"sudo tee /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg > /dev/null",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(
|
||||
c.Name,
|
||||
"bash",
|
||||
"-c",
|
||||
"--",
|
||||
"echo 'deb [signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg] https://repos.influxdata.com/debian stable main' | "+
|
||||
"tee /etc/apt/sources.list.d/influxdata.list",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(
|
||||
c.Name,
|
||||
"bash", "-c", "--",
|
||||
"cat /etc/apt/sources.list.d/influxdata.list",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(c.Name, "apt-get", "update")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create config and update yum
|
||||
func (c *Container) configureYum() error {
|
||||
err := c.client.Exec(
|
||||
c.Name,
|
||||
"bash", "-c", "--",
|
||||
fmt.Sprintf("echo -e %q > /etc/yum.repos.d/influxdata.repo", influxDataRPMRepo),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(
|
||||
c.Name,
|
||||
"bash", "-c", "--",
|
||||
"cat /etc/yum.repos.d/influxdata.repo",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// will return a non-zero return code if there are packages to update
|
||||
return c.client.Exec(c.Name, "bash", "-c", "yum check-update || true")
|
||||
}
|
||||
|
||||
// Create config and update dnf
|
||||
func (c *Container) configureDnf() error {
|
||||
err := c.client.Exec(
|
||||
c.Name,
|
||||
"bash", "-c", "--",
|
||||
fmt.Sprintf("echo -e %q > /etc/yum.repos.d/influxdata.repo", influxDataRPMRepo),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(
|
||||
c.Name,
|
||||
"bash", "-c", "--",
|
||||
"cat /etc/yum.repos.d/influxdata.repo",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// will return a non-zero return code if there are packages to update
|
||||
return c.client.Exec(c.Name, "bash", "-c", "dnf check-update || true")
|
||||
}
|
||||
|
||||
// Create config and update zypper
|
||||
func (c *Container) configureZypper() error {
|
||||
err := c.client.Exec(
|
||||
c.Name,
|
||||
"bash", "-c", "--",
|
||||
fmt.Sprintf("echo -e %q > /etc/zypp/repos.d/influxdata.repo", influxDataRPMRepo),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.client.Exec(
|
||||
c.Name,
|
||||
"bash", "-c", "--",
|
||||
"cat /etc/zypp/repos.d/influxdata.repo",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.client.Exec(c.Name, "zypper", "--no-gpg-checks", "refresh")
|
||||
}
|
||||
|
||||
// Determine if the system uses yum or apt for software
|
||||
func (c *Container) detectPackageManager() error {
|
||||
// Different options required across the distros as apt returns -1 when
|
||||
// run with no options. yum is listed last to prefer the newer
|
||||
// options first.
|
||||
err := c.client.Exec(c.Name, "which", "apt")
|
||||
if err == nil {
|
||||
c.packageManager = "apt"
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.client.Exec(c.Name, "dnf", "--version")
|
||||
if err == nil {
|
||||
c.packageManager = "dnf"
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.client.Exec(c.Name, "yum", "version")
|
||||
if err == nil {
|
||||
c.packageManager = "yum"
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.client.Exec(c.Name, "which", "zypper")
|
||||
if err == nil {
|
||||
c.packageManager = "zypper"
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("unable to determine package manager")
|
||||
}
|
||||
|
||||
// Configure the system with InfluxData repo
|
||||
func (c *Container) setupRepo() error {
|
||||
if c.packageManager == "apt" {
|
||||
if err := c.configureApt(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if c.packageManager == "yum" {
|
||||
if err := c.configureYum(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if c.packageManager == "zypper" {
|
||||
if err := c.configureZypper(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if c.packageManager == "dnf" {
|
||||
if err := c.configureDnf(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for the network to come up on a container
|
||||
func (c *Container) waitForNetwork() error {
|
||||
var exponentialBackoffCeilingSecs int64 = 128
|
||||
|
||||
attempts := 0
|
||||
for {
|
||||
if err := c.client.Exec(c.Name, "getent", "hosts", "influxdata.com"); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// uses exponetnial backoff to try after 1, 2, 4, 8, 16, etc. seconds
|
||||
delaySecs := int64(math.Pow(2, float64(attempts)))
|
||||
if delaySecs > exponentialBackoffCeilingSecs {
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("waiting for network, sleeping for %d second(s)\n", delaySecs)
|
||||
time.Sleep(time.Duration(delaySecs) * time.Second)
|
||||
attempts++
|
||||
}
|
||||
|
||||
return errors.New("timeout reached waiting for network on container")
|
||||
}
|
195
tools/package_incus_test/incus.go
Normal file
195
tools/package_incus_test/incus.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
incus "github.com/lxc/incus/v6/client"
|
||||
"github.com/lxc/incus/v6/shared/api"
|
||||
)
|
||||
|
||||
var (
|
||||
timeout = 120
|
||||
killTimeout = 5
|
||||
)
|
||||
|
||||
type BytesBuffer struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (*BytesBuffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type IncusClient struct {
|
||||
Client incus.InstanceServer
|
||||
}
|
||||
|
||||
// Connect to the LXD socket.
|
||||
func (c *IncusClient) Connect() error {
|
||||
client, err := incus.ConnectIncusUnix("", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Client = client
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a container using a specific remote and alias.
|
||||
func (c *IncusClient) Create(name, remote, alias string) error {
|
||||
fmt.Printf("creating %s with %s:%s\n", name, remote, alias)
|
||||
|
||||
if c.Client == nil {
|
||||
err := c.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
server := ""
|
||||
switch remote {
|
||||
case "images":
|
||||
server = "https://images.linuxcontainers.org"
|
||||
case "ubuntu":
|
||||
server = "https://cloud-images.ubuntu.com/releases"
|
||||
case "ubuntu-daily":
|
||||
server = "https://cloud-images.ubuntu.com/daily"
|
||||
default:
|
||||
return fmt.Errorf("unknown remote: %s", remote)
|
||||
}
|
||||
|
||||
req := api.InstancesPost{
|
||||
Name: name,
|
||||
Source: api.InstanceSource{
|
||||
Type: "image",
|
||||
Mode: "pull",
|
||||
Protocol: "simplestreams",
|
||||
Server: server,
|
||||
Alias: alias,
|
||||
},
|
||||
}
|
||||
|
||||
// Get LXD to create the container (background operation)
|
||||
op, err := c.Client.CreateInstance(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the operation to complete
|
||||
err = op.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the given container.
|
||||
func (c *IncusClient) Delete(name string) error {
|
||||
fmt.Println("deleting", name)
|
||||
op, err := c.Client.DeleteInstance(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return op.Wait()
|
||||
}
|
||||
|
||||
// Run a command returning result struct. Will kill commands that take longer
|
||||
// than 120 seconds.
|
||||
func (c *IncusClient) Exec(name string, command ...string) error {
|
||||
fmt.Printf("$ %s\n", strings.Join(command, " "))
|
||||
|
||||
cmd := []string{"/usr/bin/timeout", "-k", strconv.Itoa(killTimeout), strconv.Itoa(timeout)}
|
||||
cmd = append(cmd, command...)
|
||||
req := api.InstanceExecPost{
|
||||
Command: cmd,
|
||||
WaitForWS: true,
|
||||
}
|
||||
|
||||
output := &BytesBuffer{bytes.NewBuffer(nil)}
|
||||
|
||||
args := incus.InstanceExecArgs{
|
||||
Stdout: output,
|
||||
Stderr: output,
|
||||
DataDone: make(chan bool),
|
||||
}
|
||||
|
||||
op, err := c.Client.ExecInstance(name, req, &args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = op.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for any remaining I/O to be flushed
|
||||
<-args.DataDone
|
||||
|
||||
// get the return code
|
||||
opAPI := op.Get()
|
||||
rc := int(opAPI.Metadata["return"].(float64))
|
||||
|
||||
if rc != 0 {
|
||||
return errors.New(output.String())
|
||||
}
|
||||
|
||||
fmt.Println(output.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Push file to container.
|
||||
func (c *IncusClient) Push(name, src, dst string) error {
|
||||
fmt.Printf("cp %s %s%s\n", src, name, dst)
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading %s: %w", src, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return c.Client.CreateInstanceFile(name, dst, incus.InstanceFileArgs{
|
||||
Content: f,
|
||||
Mode: 0644,
|
||||
})
|
||||
}
|
||||
|
||||
// Start the given container.
|
||||
func (c *IncusClient) Start(name string) error {
|
||||
fmt.Println("starting", name)
|
||||
reqState := api.InstanceStatePut{
|
||||
Action: "start",
|
||||
Timeout: -1,
|
||||
}
|
||||
|
||||
op, err := c.Client.UpdateInstanceState(name, reqState, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return op.Wait()
|
||||
}
|
||||
|
||||
// Stop the given container.
|
||||
func (c *IncusClient) Stop(name string) error {
|
||||
fmt.Println("stopping", name)
|
||||
reqState := api.InstanceStatePut{
|
||||
Action: "stop",
|
||||
Force: true,
|
||||
Timeout: 10,
|
||||
}
|
||||
|
||||
op, err := c.Client.UpdateInstanceState(name, reqState, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return op.Wait()
|
||||
}
|
116
tools/package_incus_test/main.go
Normal file
116
tools/package_incus_test/main.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var imagesRPM = []string{
|
||||
"fedora/41",
|
||||
"fedora/40",
|
||||
"centos/9-Stream",
|
||||
}
|
||||
|
||||
var imagesDEB = []string{
|
||||
"debian/bullseye",
|
||||
"debian/bookworm",
|
||||
"ubuntu/noble",
|
||||
"ubuntu/jammy",
|
||||
}
|
||||
|
||||
func main() {
|
||||
packageFile := ""
|
||||
image := ""
|
||||
|
||||
app := &cli.App{
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "package",
|
||||
Usage: ".deb or .rpm file for upgrade testing",
|
||||
Destination: &packageFile,
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "image",
|
||||
Usage: "optional, run with specific image",
|
||||
Destination: &image,
|
||||
},
|
||||
},
|
||||
Action: func(*cli.Context) error {
|
||||
if _, err := os.Stat(packageFile); err != nil {
|
||||
return fmt.Errorf("unknown package file: %w", err)
|
||||
}
|
||||
|
||||
if image != "" && packageFile != "" {
|
||||
fmt.Printf("test package %q on image %q\n", packageFile, image)
|
||||
return launchTests(packageFile, []string{image})
|
||||
} else if packageFile != "" {
|
||||
fmt.Printf("test package %q on all applicable images\n", packageFile)
|
||||
|
||||
extension := filepath.Ext(packageFile)
|
||||
switch extension {
|
||||
case ".rpm":
|
||||
return launchTests(packageFile, imagesRPM)
|
||||
case ".deb":
|
||||
return launchTests(packageFile, imagesDEB)
|
||||
default:
|
||||
return fmt.Errorf("%s has unknown package type: %s", packageFile, extension)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func launchTests(packageFile string, images []string) error {
|
||||
for _, image := range images {
|
||||
fmt.Printf("starting test with %s\n", image)
|
||||
|
||||
uuidWithHyphen := uuid.New()
|
||||
name := "telegraf-test-" + uuidWithHyphen.String()[0:8]
|
||||
|
||||
err := runTest(image, name, packageFile)
|
||||
if err != nil {
|
||||
fmt.Printf("*** FAIL: %s\n", image)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("*** PASS: %s\n\n", image)
|
||||
}
|
||||
|
||||
fmt.Println("*** ALL TESTS PASS ***")
|
||||
return nil
|
||||
}
|
||||
|
||||
func runTest(image, name, packageFile string) error {
|
||||
c := Container{Name: name}
|
||||
if err := c.Create(image); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Delete()
|
||||
|
||||
if err := c.Install("telegraf"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.CheckStatus("telegraf"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.UploadAndInstall(packageFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.CheckStatus("telegraf")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue