361 lines
8 KiB
Go
361 lines
8 KiB
Go
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")
|
|
}
|