1
0
mirror of https://github.com/kairos-io/kairos-sdk.git synced 2025-05-06 15:17:12 +00:00
kairos-sdk/utils/utils.go
Dimitris Karakasilis 29fc4851f4
1999 introduce versioneer ()
* Introduce versioneer package

to replace the naming.sh script in kairos-io/kairos

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement BootableName for bootable artifacts

also introduce Version and SoftwareVersion fields

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Refactor to introduce commondName

to be used with ContainerName

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement ContainerName and add missing tests

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Introduce NewArtifactFromJSON

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Introduce NewFromOSRelease

to be used within a Kairos image

See also discussion: https://github.com/kairos-io/kairos/discussions/2035

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Introduce 3 methods to find releases (TODO: implement them)

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Use existing OSRelease method and remove the new one

the existing method had to be adapted to accept and optional path to
the os-release file to allow the tests to pass a tmp file.

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Introduce TagList and some basic filtering of tags

also introduce RegistryInspector which finds tags from a container
registry for a specific repository.

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement OtherVersions method of TagList

to return different Kairos versions of the exact same artifact.

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* [WIP] Add TODOs for 2 more methods

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement Sorted() method on TagList

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement RSorted

and change OtherVersions test to ensure it also returns older versions too.

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement NewerVersions method

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Make json file pretty

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement OtherSoftwareVersions and NewerSoftwareVersions

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement NewerAnyVersions and OtherAnyVersions

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Make TagList a struct so that it has an "Artifact" field

Now the Artifact has a TagList method that returns a TagList which is
associated to the specific Artifact.

All methods of TagList are now available to the Artifact.

E.g.
tagList, _ := artifact.TagList("quay.io/kairos")
tagList.NewerAnyVersion(...)

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Use the field Artifact in TagList methods instead of needing an argument

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Fix linting errors

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Restructure files and introduce main.go for versioneer

to provide a user interface that will replace naming.sh script of the
kairos repository

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Fix imports

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement bootable-artifact-name cli command

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement BaseContainerName method

Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com>

* Create a wrapper command base-container-artifact-name

Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>

* Extract the cli command to the packag to be re-used in kairos-agent

Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com>

* Go back to original package structure

and just nest the cli

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Move the versioneer "main" package outside the tree

to allow it to be imported in kairos-agent

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Allow setting the cli flags using environment variables

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Implement os-release-variables command

to replace the logic in Earthly and Dockerfiles

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Fix test

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Fix TODO in Readme

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Dry the creation of the Artifact

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

---------

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com>
Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>
Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
Co-authored-by: Dimitris Karakasilis <dimitris@spectrocloud.com>
2023-12-05 15:46:08 +02:00

296 lines
5.8 KiB
Go

package utils
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"image"
"net"
"os"
"os/exec"
"os/signal"
"runtime"
"strings"
"gopkg.in/yaml.v3"
"github.com/denisbrodbeck/machineid"
"github.com/joho/godotenv"
"github.com/pterm/pterm"
"github.com/qeesung/image2ascii/convert"
)
const (
systemd = "systemd"
openrc = "openrc"
unknown = "unknown"
)
func SH(c string) (string, error) {
cmd := exec.Command("/bin/sh", "-c", c)
cmd.Env = os.Environ()
o, err := cmd.CombinedOutput()
return string(o), err
}
func SHInDir(c, dir string, envs ...string) (string, error) {
cmd := exec.Command("/bin/sh", "-c", c)
cmd.Env = append(os.Environ(), envs...)
cmd.Dir = dir
o, err := cmd.CombinedOutput()
return string(o), err
}
func Exists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
// UUID TODO: move this into a machine submodule
func UUID() string {
if os.Getenv("UUID") != "" {
return os.Getenv("UUID")
}
id, _ := machineid.ID()
hostname, _ := os.Hostname()
return fmt.Sprintf("%s-%s", id, hostname)
}
// OSRelease finds the value of the specified key in the /etc/os-release file
// or, if a second argument is passed, on the file specified by the second argument.
// (optionally file argument is there for testing reasons).
func OSRelease(key string, file ...string) (string, error) {
var osReleaseFile string
if len(file) > 1 {
return "", errors.New("too many arguments passed")
}
if len(file) > 0 {
osReleaseFile = file[0]
} else {
osReleaseFile = "/etc/os-release"
}
release, err := godotenv.Read(osReleaseFile)
if err != nil {
return "", err
}
kairosKey := "KAIROS_" + key
v, exists := release[kairosKey]
if !exists {
// We try with the old naming without the prefix in case the key wasn't found
v, exists = release[key]
if !exists {
return "", fmt.Errorf("%s key not found", kairosKey)
}
}
return v, nil
}
func FindCommand(def string, options []string) string {
for _, p := range options {
path, err := exec.LookPath(p)
if err == nil {
return path
}
}
// Otherwise return default
return def
}
func K3sBin() string {
for _, p := range []string{"/usr/bin/k3s", "/usr/local/bin/k3s"} {
if _, err := os.Stat(p); err == nil {
return p
}
}
return ""
}
func WriteEnv(envFile string, config map[string]string) error {
content, _ := os.ReadFile(envFile)
env, _ := godotenv.Unmarshal(string(content))
for key, val := range config {
env[key] = val
}
return godotenv.Write(env, envFile)
}
func Flavor() string {
v, err := OSRelease("FLAVOR")
if err != nil {
return ""
}
return v
}
// GetInit Return the init system used by the OS
func GetInit() string {
for _, file := range []string{"/run/systemd/system", "/sbin/systemctl", "/usr/bin/systemctl", "/usr/sbin/systemctl", "/usr/bin/systemctl"} {
_, err := os.Stat(file)
// Found systemd
if err == nil {
return systemd
}
}
for _, file := range []string{"/sbin/openrc", "/usr/sbin/openrc", "/bin/openrc", "/usr/bin/openrc"} {
_, err := os.Stat(file)
// Found openrc
if err == nil {
return openrc
}
}
return unknown
}
func Name() string {
v, err := OSRelease("NAME")
if err != nil {
return ""
}
return strings.ReplaceAll(v, "kairos-", "")
}
func IsOpenRCBased() bool {
return GetInit() == openrc
}
func ShellSTDIN(s, c string) (string, error) {
cmd := exec.Command("/bin/sh", "-c", c)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = bytes.NewBuffer([]byte(s))
o, err := cmd.CombinedOutput()
return string(o), err
}
func SetEnv(env []string) {
for _, e := range env {
pair := strings.SplitN(e, "=", 2)
if len(pair) >= 2 {
os.Setenv(pair[0], pair[1])
}
}
}
func OnSignal(fn func(), sig ...os.Signal) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, sig...)
go func() {
<-sigs
fn()
}()
}
func Shell() *exec.Cmd {
cmd := exec.Command("/bin/sh")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
return cmd
}
func Prompt(t string) (string, error) {
if t != "" {
pterm.Info.Println(t)
}
answer, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(answer), nil
}
func PrintBanner(d []byte) {
img, _, _ := image.Decode(bytes.NewReader(d))
convertOptions := convert.DefaultOptions
convertOptions.FixedWidth = 100
convertOptions.FixedHeight = 40
// Create the image converter
converter := convert.NewImageConverter()
fmt.Print(converter.Image2ASCIIString(img, &convertOptions))
}
func Reboot() {
pterm.Info.Println("Rebooting node")
SH("reboot") //nolint:errcheck
}
func PowerOFF() {
pterm.Info.Println("Shutdown node")
if IsOpenRCBased() {
SH("poweroff") //nolint:errcheck
} else {
SH("shutdown") //nolint:errcheck
}
}
func Version() string {
v, err := OSRelease("VERSION")
if err != nil {
return ""
}
v = strings.ReplaceAll(v, "+k3s1-Kairos", "-")
v = strings.ReplaceAll(v, "+k3s-Kairos", "-")
return strings.ReplaceAll(v, "Kairos", "")
}
func ListToOutput(rels []string, output string) []string {
switch strings.ToLower(output) {
case "yaml":
d, _ := yaml.Marshal(rels)
return []string{string(d)}
case "json":
d, _ := json.Marshal(rels)
return []string{string(d)}
default:
return rels
}
}
func GetInterfaceIP(in string) string {
ifaces, err := net.Interfaces()
if err != nil {
fmt.Println("failed getting system interfaces")
return ""
}
for _, i := range ifaces {
if i.Name == in {
addrs, _ := i.Addrs()
// handle err
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip != nil {
return ip.String()
}
}
}
}
return ""
}
// GetCurrentPlatform returns the current platform in docker style `linux/amd64` for use with image utils
func GetCurrentPlatform() string {
return fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
}