1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View 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
```

View 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")
}

View 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()
}

View 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")
}