resturctured code

This commit is contained in:
Ryan Ward 2025-03-28 12:03:19 -04:00
parent a0f6f3a6a0
commit 7384a56b07
30 changed files with 351 additions and 284 deletions

45
main.go
View File

@ -15,9 +15,17 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/melbahja/goph" "github.com/melbahja/goph"
"github.com/rayaman/go-qemu/pkg/image" "github.com/rayaman/go-qemu/pkg/v1/image"
"github.com/rayaman/go-qemu/pkg/image/formats" "github.com/rayaman/go-qemu/pkg/v1/image/formats"
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/system"
"github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/accel"
"github.com/rayaman/go-qemu/pkg/v1/types/arch"
"github.com/rayaman/go-qemu/pkg/v1/types/boot"
"github.com/rayaman/go-qemu/pkg/v1/types/disk"
"github.com/rayaman/go-qemu/pkg/v1/types/memory"
"github.com/rayaman/go-qemu/pkg/v1/types/nic"
"github.com/rayaman/go-qemu/pkg/v1/types/smp"
"github.com/shirou/gopsutil/v3/process" "github.com/shirou/gopsutil/v3/process"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -33,12 +41,17 @@ type Credentials struct {
type Controller struct { type Controller struct {
cancel context.CancelFunc cancel context.CancelFunc
cmd *exec.Cmd cmd *exec.Cmd
err []error
} }
func (c *Controller) Stop() { func (c *Controller) Stop() {
c.cancel() c.cancel()
} }
func (c *Controller) Errors() []error {
return c.err
}
func KillProcess(name string) error { func KillProcess(name string) error {
processes, err := process.Processes() processes, err := process.Processes()
if err != nil { if err != nil {
@ -56,7 +69,7 @@ func KillProcess(name string) error {
return fmt.Errorf("process not found") return fmt.Errorf("process not found")
} }
func StartMachine(machine types.Machine) *Controller { func StartMachine(machine system.Machine) *Controller {
ctrl := &Controller{} ctrl := &Controller{}
go func() { go func() {
@ -111,24 +124,24 @@ func main() {
// if err != nil { // if err != nil {
// panic(err) // panic(err)
// } // }
machine := types.Machine{ machine := system.Machine{
Arch: types.X86_64, Arch: arch.X86_64,
Cores: types.SMP{ Cores: smp.SMP{
Cpus: 4, Cpus: 4,
}, },
Boot: types.Boot{ Boot: boot.Boot{
Order: []types.Drives{types.HARDDISK}, Order: []boot.Drives{boot.HARDDISK},
Menu: types.Off, Menu: types.Off,
}, },
Memory: types.Memory{ Memory: memory.Memory{
Size: 4096, Size: 4096,
}, },
Accel: types.Accel{ Accel: accel.Accel{
Accelerator: types.WHPX, Accelerator: accel.WHPX,
}, },
Nic: types.NIC{ Nic: nic.NIC{
Type: types.TAP, Type: nic.TAP,
Options: &types.TapOptions{ Options: &nic.TapOptions{
IFName: "qemu-tap", IFName: "qemu-tap",
}, },
}, },
@ -181,7 +194,7 @@ func main() {
// SetUpMachine(types.GetSize(types.GB, 64)) // SetUpMachine(types.GetSize(types.GB, 64))
} }
func SetUpMachine(size types.Size) (*Credentials, error) { func SetUpMachine(size disk.Size) (*Credentials, error) {
machine_id := uuid.New().String() machine_id := uuid.New().String()
private_path := filepath.Join("keys", machine_id) private_path := filepath.Join("keys", machine_id)
public_path := filepath.Join("keys", machine_id+".pub") public_path := filepath.Join("keys", machine_id+".pub")

View File

@ -1,71 +0,0 @@
package image
import (
"encoding/json"
"os"
"reflect"
"strings"
"github.com/rayaman/go-qemu/pkg/types"
)
// Reperesents a qemu image
type Image interface {
//GetType() types.Format
}
type holder struct {
Format string
Image any
}
// Loades a saved Image into memory
func LoadImage(path string) (Image, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var h = &holder{}
err = json.Unmarshal(data, h)
if err != nil {
return nil, err
}
data, err = json.Marshal(h.Image)
if err != nil {
return nil, err
}
t := reflect.New(reflect.TypeOf(types.GetTypes()[h.Format])).Interface()
err = json.Unmarshal(data, t)
if err != nil {
return nil, err
}
return t, nil
}
// Saves an image to disk
func SaveImage(path string, img Image) error {
data, err := json.MarshalIndent(holder{
Format: strings.ReplaceAll(strings.ToLower(reflect.TypeOf(img).String()), "*formats.", ""),
Image: img,
}, "", "\t")
if err != nil {
return err
}
file, err := os.Create(path)
if err != nil {
return err
}
_, err = file.Write(data)
return err
}

View File

@ -8,58 +8,13 @@ import (
"strings" "strings"
"github.com/abdfnx/gosh" "github.com/abdfnx/gosh"
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/types/disk"
) )
func getOptions(m map[string]string) string {
str := []string{}
for k, v := range m {
if len(v) > 0 {
str = append(str, fmt.Sprintf("%v=%v", k, v))
}
}
n_str := strings.Join(str, ",")
if len(n_str) > 0 {
return "-o \"" + strings.Join(str, ",") + "\" "
}
return ""
}
func getData(m map[string]string, key string) (string, bool) {
if d, ok := m[key]; ok {
delete(m, key)
if len(d) == 0 {
return "", false
}
return d, true
}
return "", false
}
func getMap(q any) map[string]string {
m := map[string]string{}
v := reflect.ValueOf(q).Elem()
for j := 0; j < v.NumField(); j++ { // Go through all fields of struct
if !v.Field(j).IsZero() {
index := strings.ReplaceAll(v.Type().Field(j).Tag.Get("json"), ",omitempty", "")
if v.Field(j).Type() == reflect.TypeOf(true) {
m[index] = types.SW[v.Field(j).Bool()]
} else {
m[index] = v.Field(j).String()
}
}
}
return m
}
type Options struct {
IsBaseImage bool `json:"is_base_image"`
}
// create [--object OBJECTDEF] [-q] [-f FMT] [-b BACKING_FILE [-F BACKING_FMT]] [-u] [-o OPTIONS] FILENAME [SIZE] // create [--object OBJECTDEF] [-q] [-f FMT] [-b BACKING_FILE [-F BACKING_FMT]] [-u] [-o OPTIONS] FILENAME [SIZE]
// Creates an image based of the supplied image // Creates an image based of the supplied image structure
func Create(i Image, size types.Size, opts ...Options) error { func Create(i Image, size disk.Size, opts ...Options) error {
data := getMap(i) data := getMap(i)

View File

@ -0,0 +1,37 @@
package image
import (
"encoding/json"
"os"
"reflect"
"github.com/rayaman/go-qemu/pkg/v1/types"
)
// Loads a saved image structure into memory
func LoadImage(path string) (Image, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var h = &holder{}
err = json.Unmarshal(data, h)
if err != nil {
return nil, err
}
data, err = json.Marshal(h.Image)
if err != nil {
return nil, err
}
t := reflect.New(reflect.TypeOf(types.GetTypes()[h.Format])).Interface()
err = json.Unmarshal(data, t)
if err != nil {
return nil, err
}
return t, nil
}

View File

@ -0,0 +1,31 @@
package image
import (
"encoding/json"
"os"
"reflect"
"strings"
)
// Saves an image structure to disk
func SaveImage(path string, img Image) error {
data, err := json.MarshalIndent(holder{
Format: strings.ReplaceAll(strings.ToLower(reflect.TypeOf(img).String()), "*formats.", ""),
Image: img,
}, "", "\t")
if err != nil {
return err
}
file, err := os.Create(path)
if err != nil {
return err
}
_, err = file.Write(data)
return err
}

View File

@ -1,7 +1,8 @@
package formats package formats
import ( import (
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/image"
) )
func init() { func init() {
@ -12,13 +13,13 @@ type QCOW2 struct {
// Name of the image // Name of the image
ImageName string `json:"image_name"` ImageName string `json:"image_name"`
// Determines the qcow2 version to use. compat=0.10 uses the traditional image format that can be read by any QEMU since 0.10. compat=1.1 enables image format extensions that only QEMU 1.1 and newer understand (this is the default). Amongst others, this includes zero clusters, which allow efficient copy-on-read for sparse images // Determines the qcow2 version to use. compat=0.10 uses the traditional image format that can be read by any QEMU since 0.10. compat=1.1 enables image format extensions that only QEMU 1.1 and newer understand (this is the default). Amongst others, this includes zero clusters, which allow efficient copy-on-read for sparse images
Compat types.Compat `json:"compat,omitempty"` Compat image.Compat `json:"compat,omitempty"`
// File name of a base image // File name of a base image
BackingFile string `json:"backing_file,omitempty"` BackingFile string `json:"backing_file,omitempty"`
// Image format of the base image // Image format of the base image
BackingFmt string `json:"backing_fmt,omitempty"` BackingFmt string `json:"backing_fmt,omitempty"`
// This option configures which compression algorithm will be used for compressed clusters on the image. Note that setting this option doesnt yet cause the image to actually receive compressed writes. It is most commonly used with the -c option of qemu-img convert, but can also be used with the compress filter driver or backup block jobs with compression enabled. // This option configures which compression algorithm will be used for compressed clusters on the image. Note that setting this option doesnt yet cause the image to actually receive compressed writes. It is most commonly used with the -c option of qemu-img convert, but can also be used with the compress filter driver or backup block jobs with compression enabled.
CompressionType types.CompressionType `json:"compression_type,omitempty"` CompressionType image.CompressionType `json:"compression_type,omitempty"`
/* /*
If this option is set to true, the image is encrypted with 128-bit AES-CBC. If this option is set to true, the image is encrypted with 128-bit AES-CBC.
@ -38,7 +39,7 @@ type QCOW2 struct {
// Changes the qcow2 cluster size (must be between 512 and 2M). Smaller cluster sizes can improve the image file size whereas larger cluster sizes generally provide better performance. // Changes the qcow2 cluster size (must be between 512 and 2M). Smaller cluster sizes can improve the image file size whereas larger cluster sizes generally provide better performance.
ClusterSize string `json:"cluster_size,omitempty"` ClusterSize string `json:"cluster_size,omitempty"`
// Preallocation mode (allowed values: off, metadata, falloc, full). An image with preallocated metadata is initially larger but can improve performance when the image needs to grow. falloc and full preallocations are like the same options of raw format, but sets up metadata also. // Preallocation mode (allowed values: off, metadata, falloc, full). An image with preallocated metadata is initially larger but can improve performance when the image needs to grow. falloc and full preallocations are like the same options of raw format, but sets up metadata also.
Preallocation types.Preallocation `json:"preallocation,omitempty"` Preallocation image.Preallocation `json:"preallocation,omitempty"`
/* /*
If this option is set to true, reference count updates are postponed with the goal of avoiding metadata I/O and improving performance. This is particularly interesting with cache=writethrough which doesnt batch metadata updates. The tradeoff is that after a host crash, the reference count tables must be rebuilt, i.e. on the next open an (automatic) qemu-img check -r all is required, which may take some time. If this option is set to true, reference count updates are postponed with the goal of avoiding metadata I/O and improving performance. This is particularly interesting with cache=writethrough which doesnt batch metadata updates. The tradeoff is that after a host crash, the reference count tables must be rebuilt, i.e. on the next open an (automatic) qemu-img check -r all is required, which may take some time.

View File

@ -1,7 +1,8 @@
package formats package formats
import ( import (
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/image"
) )
func init() { func init() {
@ -12,5 +13,5 @@ type Raw struct {
// Name of the image // Name of the image
ImageName string `json:"image_name"` ImageName string `json:"image_name"`
// Preallocation mode (allowed values: off, falloc, full). falloc mode preallocates space for image by calling posix_fallocate(). full mode preallocates space for image by writing data to underlying storage. This data may or may not be zero, depending on the storage location. // Preallocation mode (allowed values: off, falloc, full). falloc mode preallocates space for image by calling posix_fallocate(). full mode preallocates space for image by writing data to underlying storage. This data may or may not be zero, depending on the storage location.
Preallocation types.Preallocation `json:"preallocation"` Preallocation image.Preallocation `json:"preallocation"`
} }

17
pkg/v1/image/types.go Normal file
View File

@ -0,0 +1,17 @@
package image
type (
Options struct {
IsBaseImage bool `json:"is_base_image"`
}
// Reperesents a qemu image
Image interface {
//GetType() types.Format
}
holder struct {
Format string
Image any
}
)

50
pkg/v1/image/utils.go Normal file
View File

@ -0,0 +1,50 @@
package image
import (
"fmt"
"reflect"
"strings"
"github.com/rayaman/go-qemu/pkg/v1/types/utils"
)
func getOptions(m map[string]string) string {
str := []string{}
for k, v := range m {
if len(v) > 0 {
str = append(str, fmt.Sprintf("%v=%v", k, v))
}
}
n_str := strings.Join(str, ",")
if len(n_str) > 0 {
return "-o \"" + strings.Join(str, ",") + "\" "
}
return ""
}
func getData(m map[string]string, key string) (string, bool) {
if d, ok := m[key]; ok {
delete(m, key)
if len(d) == 0 {
return "", false
}
return d, true
}
return "", false
}
func getMap(q any) map[string]string {
m := map[string]string{}
v := reflect.ValueOf(q).Elem()
for j := 0; j < v.NumField(); j++ { // Go through all fields of struct
if !v.Field(j).IsZero() {
index := strings.ReplaceAll(v.Type().Field(j).Tag.Get("json"), ",omitempty", "")
if v.Field(j).Type() == reflect.TypeOf(true) {
m[index] = utils.SW[v.Field(j).Bool()]
} else {
m[index] = v.Field(j).String()
}
}
}
return m
}

90
pkg/v1/system/machine.go Normal file
View File

@ -0,0 +1,90 @@
package system
import (
"fmt"
"reflect"
"strings"
"github.com/fatih/structs"
"github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/accel"
"github.com/rayaman/go-qemu/pkg/v1/types/arch"
"github.com/rayaman/go-qemu/pkg/v1/types/boot"
"github.com/rayaman/go-qemu/pkg/v1/types/chip"
"github.com/rayaman/go-qemu/pkg/v1/types/memory"
"github.com/rayaman/go-qemu/pkg/v1/types/nic"
"github.com/rayaman/go-qemu/pkg/v1/types/numa"
"github.com/rayaman/go-qemu/pkg/v1/types/smp"
"github.com/rayaman/go-qemu/pkg/v1/types/utils"
)
/*
TODO:
-add-fd fd=fd,set=set[,opaque=opaque]
Add 'fd' to fd 'set'
-set group.id.arg=value
set <arg> parameter for item <id> of type <group>
i.e. -set drive.$id.file=/path/to/image
-global driver.property=value
-global driver=driver,property=property,value=value
set a global default for a driver property
*/
type Machine struct {
Arch arch.System // The binary we use
// Amount of memory in MB
Memory memory.Memory `json:"m,omitempty"`
// Number of CPU cores
Cores smp.SMP `json:"smp,omitempty"`
Cpu chip.CHIP `json:"cpu,omitempty"`
Accel accel.Accel `json:"accel,omitempty"`
Boot boot.Boot `json:"boot,omitempty"`
Numa numa.Numa `json:"numa,omitempty" omit:"true"`
MemoryPath string `json:"memory-path,omitempty"`
MemoryPrealloc types.Flag `json:"memory-prealloc,omitempty"`
Nic nic.NIC `json:"nic,omitempty"`
// Graphics
NoGraphic types.Flag `json:"nographic,omitempty"`
// Block devices
HardDiskA string `json:"hda,omitempty"`
HardDiskB string `json:"hdb,omitempty"`
HardDiskC string `json:"hdc,omitempty"`
HardDiskD string `json:"hdd,omitempty"`
FloppyA string `json:"fda,omitempty"`
FloppyB string `json:"fdb,omitempty"`
CDROM string `json:"cdrom,omitempty"`
}
func (m *Machine) Expand() []string {
fields := structs.Fields(m)
exp := []string{}
for _, field := range fields {
tag := strings.ReplaceAll(field.Tag("json"), ",omitempty", "")
omit := field.Tag("omit")
if tag != "" {
if field.Kind() == reflect.Struct || field.Kind() == reflect.Interface && !field.IsZero() {
if omit != "" {
exp = append(exp, utils.Expand(field.Value())...)
} else {
exp = append(exp, utils.Expand(field.Value(), tag)...)
}
} else {
if !field.IsZero() {
if fmt.Sprintf("%v", field.Value()) == "flag-on" {
exp = append(exp, "-"+tag)
} else {
exp = append(exp, "-"+tag, fmt.Sprintf("%v", field.Value()))
}
}
}
}
}
exp = utils.Remove(exp, "")
return exp
}

View File

@ -1,52 +1,8 @@
package types package arch
import "fmt" type System string
type (
Preallocation string
Compat string
CompressionType string
Format string
System string
Flag string
)
// converts bool to on/off format
var SW = map[bool]string{
true: "on",
false: "off",
}
var (
On *bool = func(b bool) *bool {
return &b
}(true)
Off *bool = func(b bool) *bool {
return &b
}(false)
)
type Param interface {
Expand() string
}
var register = map[string]any{}
var ( var (
// No pre-allocation
OFF Preallocation = "off"
// Allocates qcow2 metadata, and it's still a sparse image.
METADATA Preallocation = "metadata"
// Uses posix_fallocate() to "allocate blocks and marking them as uninitialized", and is relatively faster than writing out zeroes to a file:
FALLOC Preallocation = "falloc"
// Allocates zeroes and makes a non-sparse image.
FULL Preallocation = "full"
Compat_0_10 Compat = "0.10"
Compat_1_1 Compat = "1.1"
Zlib CompressionType = "zlib"
Zstd CompressionType = "zstd"
AARCH64 System = "aarch64" AARCH64 System = "aarch64"
AARCH64W System = "aarch64w" AARCH64W System = "aarch64w"
ALPHA System = "alpha" ALPHA System = "alpha"
@ -105,18 +61,4 @@ var (
XTENSAEB System = "xtensaeb" XTENSAEB System = "xtensaeb"
XTENSAEBW System = "xtensaebw" XTENSAEBW System = "xtensaebw"
XTENSAW System = "xtensaw" XTENSAW System = "xtensaw"
Set Flag = "flag-on"
) )
func GetTypes() map[string]any {
return register
}
func RegisterType(t string, i any) error {
if _, ok := register[t]; !ok {
register[t] = i
return nil
}
return fmt.Errorf("type already registered")
}

View File

@ -1,4 +1,4 @@
package types package chip
type CHIP string type CHIP string

View File

@ -1,4 +1,4 @@
package types package disk
import ( import (
"math/big" "math/big"

View File

@ -0,0 +1,25 @@
package image
type (
Preallocation string
Compat string
CompressionType string
Format string
)
var (
// No pre-allocation
OFF Preallocation = "off"
// Allocates qcow2 metadata, and it's still a sparse image.
METADATA Preallocation = "metadata"
// Uses posix_fallocate() to "allocate blocks and marking them as uninitialized", and is relatively faster than writing out zeroes to a file:
FALLOC Preallocation = "falloc"
// Allocates zeroes and makes a non-sparse image.
FULL Preallocation = "full"
Compat_0_10 Compat = "0.10"
Compat_1_1 Compat = "1.1"
Zlib CompressionType = "zlib"
Zstd CompressionType = "zstd"
)

View File

@ -1,4 +1,4 @@
package types package memory
/* Configure guest RAM /* Configure guest RAM
Note: Some architectures might enforce a specific granularity Note: Some architectures might enforce a specific granularity

View File

@ -1,6 +1,8 @@
package types package nic
import "strings" import (
"strings"
)
type ( type (
NICType string NICType string
@ -21,7 +23,14 @@ type TapOptions struct {
} }
func (t *TapOptions) ExpandOptions() []string { func (t *TapOptions) ExpandOptions() []string {
return []string{"tap," + strings.Join(Expand(t), ",")} opts := []string{}
if t.ID != "" {
opts = append(opts, "id="+t.ID)
}
if t.IFName != "" {
opts = append(opts, "ifname="+t.IFName)
}
return []string{"tap," + strings.Join(opts, ",")}
} }
type BridgeOptions struct { type BridgeOptions struct {

View File

@ -1,4 +1,4 @@
package types package smp
/* /*
Note: Different machines may have different subsets of the CPU topology Note: Different machines may have different subsets of the CPU topology

33
pkg/v1/types/types.go Normal file
View File

@ -0,0 +1,33 @@
package types
import "fmt"
type (
Flag string
)
var (
register = map[string]any{}
On *bool = func(b bool) *bool {
return &b
}(true)
Off *bool = func(b bool) *bool {
return &b
}(false)
Set Flag = "flag-on"
)
func GetTypes() map[string]any {
return register
}
func RegisterType(t string, i any) error {
if _, ok := register[t]; !ok {
register[t] = i
return nil
}
return fmt.Errorf("type already registered")
}

View File

@ -1,4 +1,4 @@
package types package utils
import ( import (
"fmt" "fmt"
@ -6,55 +6,18 @@ import (
"strings" "strings"
"github.com/fatih/structs" "github.com/fatih/structs"
"github.com/rayaman/go-qemu/pkg/types/accel" "github.com/rayaman/go-qemu/pkg/v1/types/boot"
"github.com/rayaman/go-qemu/pkg/types/boot" "github.com/rayaman/go-qemu/pkg/v1/types/nic"
"github.com/rayaman/go-qemu/pkg/types/numa" "github.com/rayaman/go-qemu/pkg/v1/types/numa"
) )
/* // converts bool to on/off format
TODO: var SW = map[bool]string{
-add-fd fd=fd,set=set[,opaque=opaque] true: "on",
false: "off",
Add 'fd' to fd 'set'
-set group.id.arg=value
set <arg> parameter for item <id> of type <group>
i.e. -set drive.$id.file=/path/to/image
-global driver.property=value
-global driver=driver,property=property,value=value
set a global default for a driver property
*/
type Machine struct {
Arch System // The binary we use
// Amount of memory in MB
Memory Memory `json:"m,omitempty"`
// Number of CPU cores
Cores SMP `json:"smp,omitempty"`
Cpu CHIP `json:"cpu,omitempty"`
Accel accel.Accel `json:"accel,omitempty"`
Boot boot.Boot `json:"boot,omitempty"`
Numa numa.Numa `json:"numa,omitempty" omit:"true"`
MemoryPath string `json:"memory-path,omitempty"`
MemoryPrealloc Flag `json:"memory-prealloc,omitempty"`
Nic NIC `json:"nic,omitempty"`
// Graphics
NoGraphic Flag `json:"nographic,omitempty"`
// Block devices
HardDiskA string `json:"hda,omitempty"`
HardDiskB string `json:"hdb,omitempty"`
HardDiskC string `json:"hdc,omitempty"`
HardDiskD string `json:"hdd,omitempty"`
FloppyA string `json:"fda,omitempty"`
FloppyB string `json:"fdb,omitempty"`
CDROM string `json:"cdrom,omitempty"`
} }
func remove(s []string, r string) []string { func Remove(s []string, r string) []string {
for i, v := range s { for i, v := range s {
if v == r { if v == r {
return append(s[:i], s[i+1:]...) return append(s[:i], s[i+1:]...)
@ -63,35 +26,6 @@ func remove(s []string, r string) []string {
return s return s
} }
func (m *Machine) Expand() []string {
fields := structs.Fields(m)
exp := []string{}
for _, field := range fields {
tag := strings.ReplaceAll(field.Tag("json"), ",omitempty", "")
omit := field.Tag("omit")
if tag != "" {
if field.Kind() == reflect.Struct || field.Kind() == reflect.Interface && !field.IsZero() {
if omit != "" {
exp = append(exp, Expand(field.Value())...)
} else {
exp = append(exp, Expand(field.Value(), tag)...)
}
} else {
if !field.IsZero() {
if fmt.Sprintf("%v", field.Value()) == "flag-on" {
exp = append(exp, "-"+tag)
} else {
exp = append(exp, "-"+tag, fmt.Sprintf("%v", field.Value()))
}
}
}
}
}
exp = remove(exp, "")
return exp
}
func Expand(obj any, tag ...string) []string { func Expand(obj any, tag ...string) []string {
opts := []string{} opts := []string{}
fields := structs.Fields(obj) fields := structs.Fields(obj)
@ -139,7 +73,7 @@ func Expand(obj any, tag ...string) []string {
opts = append(opts, "-numa", opt) opts = append(opts, "-numa", opt)
opts = append(opts, Expand(hmatcache)...) opts = append(opts, Expand(hmatcache)...)
} }
case Options: case nic.Options:
opts = append(opts, value.ExpandOptions()...) opts = append(opts, value.ExpandOptions()...)
case []boot.Drives: case []boot.Drives:
if omit == "tag" { if omit == "tag" {