mirror of
https://github.com/kairos-io/kairos-sdk.git
synced 2025-09-01 07:08:39 +00:00
sparkles: state api sdk (#262)
* ✨ Add state api This is related to https://github.com/kairos-io/kairos/issues/34. Starts to unify the API to retrieve the state in the sdk to have a common place to query system status information. * 🤖 Add test * Update go.mod * ⚙️ Fine-tune detection of partitions * 🤖 Add more fine-grained tests * 🎨 Add /dev/ to partition name * 🤖 Fixup tests * ⚙️ Remount accessors * ✨ Add state partition to cloud-init paths * 📝 Upper case Kairos in motd * 🎨 Add mounts sdk * 🎨 Set grub options via SDK * 🎨 Make it more idiomatic
This commit is contained in:
committed by
Itxaka
parent
5aeed0ddd2
commit
696e87bbee
34
sdk/mounts/system.go
Normal file
34
sdk/mounts/system.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package mounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kairos-io/kairos/pkg/machine"
|
||||||
|
"github.com/kairos-io/kairos/sdk/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrepareWrite(partition state.PartitionState, mountpath string) error {
|
||||||
|
if partition.Mounted && partition.IsReadOnly {
|
||||||
|
if mountpath == partition.MountPoint {
|
||||||
|
return machine.Remount("rw", partition.MountPoint)
|
||||||
|
}
|
||||||
|
err := machine.Remount("rw", partition.MountPoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return machine.Mount(partition.Label, mountpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return machine.Mount(partition.Label, mountpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mount(partition state.PartitionState, mountpath string) error {
|
||||||
|
return machine.Mount(partition.Label, mountpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Umount(partition state.PartitionState) error {
|
||||||
|
if !partition.Mounted {
|
||||||
|
return fmt.Errorf("partition not mounted")
|
||||||
|
}
|
||||||
|
return machine.Umount(partition.MountPoint)
|
||||||
|
}
|
11
sdk/state/machine.go
Normal file
11
sdk/state/machine.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package state
|
||||||
|
|
||||||
|
type Machine struct {
|
||||||
|
UUID string
|
||||||
|
BootArgs []string
|
||||||
|
CloudConfig string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Spec struct {
|
||||||
|
MachineSpec Machine
|
||||||
|
}
|
142
sdk/state/state.go
Normal file
142
sdk/state/state.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/itchyny/gojq"
|
||||||
|
"github.com/jaypipes/ghw"
|
||||||
|
"github.com/jaypipes/ghw/pkg/block"
|
||||||
|
"github.com/kairos-io/kairos/pkg/machine"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Active Boot = "active_boot"
|
||||||
|
Passive Boot = "passive_boot"
|
||||||
|
Recovery Boot = "recovery_boot"
|
||||||
|
LiveCD Boot = "livecd_boot"
|
||||||
|
Unknown Boot = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Boot string
|
||||||
|
|
||||||
|
type PartitionState struct {
|
||||||
|
Mounted bool `yaml:"mounted" json:"mounted"`
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
Label string `yaml:"label" json:"label"`
|
||||||
|
MountPoint string `yaml:"mount_point" json:"mount_point"`
|
||||||
|
SizeBytes uint64 `yaml:"size_bytes" json:"size_bytes"`
|
||||||
|
Type string `yaml:"type" json:"type"`
|
||||||
|
IsReadOnly bool `yaml:"read_only" json:"read_only"`
|
||||||
|
UUID string `yaml:"uuid" json:"uuid"` // This would be volume UUID on macOS, PartUUID on linux, empty on Windows
|
||||||
|
}
|
||||||
|
|
||||||
|
type Runtime struct {
|
||||||
|
UUID string `yaml:"uuid" json:"uuid"`
|
||||||
|
Persistent PartitionState `yaml:"persistent" json:"persistent"`
|
||||||
|
OEM PartitionState `yaml:"oem" json:"oem"`
|
||||||
|
State PartitionState `yaml:"state" json:"state"`
|
||||||
|
BootState Boot `yaml:"boot" json:"boot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectPartition(b *block.Partition) PartitionState {
|
||||||
|
return PartitionState{
|
||||||
|
Type: b.Type,
|
||||||
|
IsReadOnly: b.IsReadOnly,
|
||||||
|
UUID: b.UUID,
|
||||||
|
Name: fmt.Sprintf("/dev/%s", b.Name),
|
||||||
|
SizeBytes: b.SizeBytes,
|
||||||
|
Label: b.Label,
|
||||||
|
MountPoint: b.MountPoint,
|
||||||
|
Mounted: b.MountPoint != "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectBoot() Boot {
|
||||||
|
cmdline, err := ioutil.ReadFile("/proc/cmdline")
|
||||||
|
if err != nil {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
cmdlineS := string(cmdline)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(cmdlineS, "COS_ACTIVE"):
|
||||||
|
return Active
|
||||||
|
case strings.Contains(cmdlineS, "COS_PASSIVE"):
|
||||||
|
return Passive
|
||||||
|
case strings.Contains(cmdlineS, "COS_RECOVERY"), strings.Contains(cmdlineS, "COS_SYSTEM"):
|
||||||
|
return Recovery
|
||||||
|
case strings.Contains(cmdlineS, "live:LABEL"), strings.Contains(cmdlineS, "live:CDLABEL"):
|
||||||
|
return LiveCD
|
||||||
|
default:
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectRuntimeState(r *Runtime) error {
|
||||||
|
blockDevices, err := block.New(ghw.WithDisableTools(), ghw.WithDisableWarnings())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, d := range blockDevices.Disks {
|
||||||
|
for _, part := range d.Partitions {
|
||||||
|
switch part.Label {
|
||||||
|
case "COS_PERSISTENT":
|
||||||
|
r.Persistent = detectPartition(part)
|
||||||
|
case "COS_OEM":
|
||||||
|
r.OEM = detectPartition(part)
|
||||||
|
case "COS_STATE":
|
||||||
|
r.State = detectPartition(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRuntime() (Runtime, error) {
|
||||||
|
runtime := &Runtime{
|
||||||
|
BootState: detectBoot(),
|
||||||
|
UUID: machine.UUID(),
|
||||||
|
}
|
||||||
|
err := detectRuntimeState(runtime)
|
||||||
|
return *runtime, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Runtime) String() string {
|
||||||
|
dat, err := yaml.Marshal(r)
|
||||||
|
if err == nil {
|
||||||
|
return string(dat)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Runtime) Query(s string) (res string, err error) {
|
||||||
|
jsondata := map[string]interface{}{}
|
||||||
|
var dat []byte
|
||||||
|
dat, err = json.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(dat, &jsondata)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query, err := gojq.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
iter := query.Run(jsondata) // or query.RunWithContext
|
||||||
|
for {
|
||||||
|
v, ok := iter.Next()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err, ok := v.(error); ok {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res += fmt.Sprint(v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
47
sdk/system/grub.go
Normal file
47
sdk/system/grub.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kairos-io/kairos/pkg/machine"
|
||||||
|
"github.com/kairos-io/kairos/pkg/utils"
|
||||||
|
"github.com/kairos-io/kairos/sdk/mounts"
|
||||||
|
"github.com/kairos-io/kairos/sdk/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetGRUBOptions(opts map[string]string) Option {
|
||||||
|
return func(c *Changeset) error {
|
||||||
|
if len(opts) > 0 {
|
||||||
|
c.Add(func() error { return setGRUBOptions(opts) })
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setGRUBOptions(opts map[string]string) error {
|
||||||
|
runtime, err := state.NewRuntime()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oem := runtime.OEM
|
||||||
|
if runtime.OEM.Name == "" {
|
||||||
|
oem = runtime.Persistent
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mounts.PrepareWrite(oem, "/tmp/oem"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
machine.Umount("/tmp/oem") //nolint:errcheck
|
||||||
|
}()
|
||||||
|
|
||||||
|
for k, v := range opts {
|
||||||
|
out, err := utils.SH(fmt.Sprintf(`grub2-editenv /tmp/oem/grubenv set "%s=%s"`, k, v))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("could not set boot option: %s\n", out+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
30
sdk/system/options.go
Normal file
30
sdk/system/options.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Changeset []func() error
|
||||||
|
|
||||||
|
func (c *Changeset) Add(f func() error) {
|
||||||
|
*c = append(*c, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(c *Changeset) error
|
||||||
|
|
||||||
|
func Apply(opts ...Option) error {
|
||||||
|
|
||||||
|
c := &Changeset{}
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, f := range *c {
|
||||||
|
err = multierror.Append(f())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
Reference in New Issue
Block a user