Skip to content

Commit

Permalink
Implement support for global default.yaml and override.yaml configs
Browse files Browse the repository at this point in the history
Stored inside the _config directory, they provide defaults for all
instances stored under the same LIMA_HOME. Settings from override.yaml
take precedence over the instance settings themselves.

Signed-off-by: Jan Dubois <[email protected]>
  • Loading branch information
jandubois committed Nov 26, 2021
1 parent 67e465e commit c30b563
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 53 deletions.
2 changes: 1 addition & 1 deletion pkg/cidata/cidata.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
}
}

if guestAgentBinary, err := GuestAgentBinary(y.Arch); err != nil {
if guestAgentBinary, err := GuestAgentBinary(*y.Arch); err != nil {
return err
} else {
defer guestAgentBinary.Close()
Expand Down
161 changes: 146 additions & 15 deletions pkg/limayaml/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,54 +51,136 @@ func MACAddress(uniqueID string) string {
return hw.String()
}

func FillDefault(y *LimaYAML, filePath string) {
y.Arch = resolveArch(y.Arch)
// FillDefault updates undefined fields in y with defaults from d (or built-in default), and overwrites with values from o.
// Both d and o may be empty.
func FillDefault(y *LimaYAML, d *LimaYAML, o *LimaYAML, filePath string) {
if y.Arch == nil {
y.Arch = d.Arch
}
if o.Arch != nil {
y.Arch = o.Arch
}
y.Arch = &[]Arch{resolveArch(y.Arch)}[0]

y.Images = append(append(o.Images, y.Images...), d.Images...)
for i := range y.Images {
img := &y.Images[i]
if img.Arch == "" {
img.Arch = y.Arch
img.Arch = *y.Arch
}
}
if y.CPUs == 0 {
y.CPUs = 4

if y.CPUs == nil {
y.CPUs = d.CPUs
}
if o.CPUs != nil {
y.CPUs = o.CPUs
}
if y.CPUs == nil || *y.CPUs == 0 {
y.CPUs = &[]int{4}[0]
}

if y.Memory == nil {
y.Memory = d.Memory
}
if y.Memory == "" {
y.Memory = "4GiB"
if o.Memory != nil {
y.Memory = o.Memory
}
if y.Memory == nil || *y.Memory == "" {
y.Memory = &[]string{"4GiB"}[0]
}

if y.Disk == nil {
y.Disk = d.Disk
}
if o.Disk != nil {
y.Disk = o.Disk
}
if y.Disk == nil || *y.Disk == "" {
y.Disk = &[]string{"100GiB"}[0]
}

if y.Video.Display == nil {
y.Video.Display = d.Video.Display
}
if y.Disk == "" {
y.Disk = "100GiB"
if o.Video.Display != nil {
y.Video.Display = o.Video.Display
}
if y.Video.Display == "" {
y.Video.Display = "none"
if y.Video.Display == nil || *y.Video.Display == "" {
y.Video.Display = &[]string{"none"}[0]
}

if y.Firmware.LegacyBIOS == nil {
y.Firmware.LegacyBIOS = d.Firmware.LegacyBIOS
}
if o.Firmware.LegacyBIOS != nil {
y.Firmware.LegacyBIOS = o.Firmware.LegacyBIOS
}
if y.Firmware.LegacyBIOS == nil {
y.Firmware.LegacyBIOS = &[]bool{false}[0]
}

// y.SSH.LocalPort is not filled here (filled by the hostagent)

if y.SSH.LoadDotSSHPubKeys == nil {
y.SSH.LoadDotSSHPubKeys = d.SSH.LoadDotSSHPubKeys
}
if o.SSH.LoadDotSSHPubKeys != nil {
y.SSH.LoadDotSSHPubKeys = o.SSH.LoadDotSSHPubKeys
}
if y.SSH.LoadDotSSHPubKeys == nil {
y.SSH.LoadDotSSHPubKeys = &[]bool{true}[0]
}

if y.SSH.ForwardAgent == nil {
y.SSH.ForwardAgent = d.SSH.ForwardAgent
}
if o.SSH.ForwardAgent != nil {
y.SSH.ForwardAgent = o.SSH.ForwardAgent
}
if y.SSH.ForwardAgent == nil {
y.SSH.ForwardAgent = &[]bool{false}[0]
}

y.Provision = append(append(o.Provision, y.Provision...), d.Provision...)
for i := range y.Provision {
provision := &y.Provision[i]
if provision.Mode == "" {
provision.Mode = ProvisionModeSystem
}
}

if y.Containerd.System == nil {
y.Containerd.System = d.Containerd.System
}
if o.Containerd.System != nil {
y.Containerd.System = o.Containerd.System
}
if y.Containerd.System == nil {
y.Containerd.System = &[]bool{false}[0]
}
if y.Containerd.User == nil {
y.Containerd.User = d.Containerd.User
}
if o.Containerd.User != nil {
y.Containerd.User = o.Containerd.User
}
if y.Containerd.User == nil {
y.Containerd.User = &[]bool{true}[0]
}

y.Containerd.Archives = append(append(o.Containerd.Archives, y.Containerd.Archives...), d.Containerd.Archives...)
if len(y.Containerd.Archives) == 0 {
y.Containerd.Archives = defaultContainerdArchives()
}
for i := range y.Containerd.Archives {
f := &y.Containerd.Archives[i]
if f.Arch == "" {
f.Arch = y.Arch
f.Arch = *y.Arch
}
}

y.Probes = append(append(o.Probes, y.Probes...), d.Probes...)
for i := range y.Probes {
probe := &y.Probes[i]
if probe.Mode == "" {
Expand All @@ -108,14 +190,30 @@ func FillDefault(y *LimaYAML, filePath string) {
probe.Description = fmt.Sprintf("user probe %d/%d", i+1, len(y.Probes))
}
}

y.PortForwards = append(append(o.PortForwards, y.PortForwards...), d.PortForwards...)
instDir := filepath.Dir(filePath)
for i := range y.PortForwards {
FillPortForwardDefaults(&y.PortForwards[i], instDir)
// After defaults processing the singular HostPort and GuestPort values should not be used again.
}

if y.UseHostResolver == nil {
y.UseHostResolver = d.UseHostResolver
}
if o.UseHostResolver != nil {
y.UseHostResolver = o.UseHostResolver
}
if y.UseHostResolver == nil {
y.UseHostResolver = &[]bool{true}[0]
}

if y.PropagateProxyEnv == nil {
y.PropagateProxyEnv = d.PropagateProxyEnv
}
if o.PropagateProxyEnv != nil {
y.PropagateProxyEnv = o.PropagateProxyEnv
}
if y.PropagateProxyEnv == nil {
y.PropagateProxyEnv = &[]bool{true}[0]
}
Expand All @@ -132,6 +230,8 @@ func FillDefault(y *LimaYAML, filePath string) {
}
y.Network.migrated = true
}

y.Networks = append(append(o.Networks, y.Networks...), d.Networks...)
for i := range y.Networks {
nw := &y.Networks[i]
if nw.MACAddress == "" {
Expand All @@ -142,6 +242,37 @@ func FillDefault(y *LimaYAML, filePath string) {
nw.Interface = "lima" + strconv.Itoa(i)
}
}

// Combine all mounts; highest priority entry determines writable status.
// Only works for exact matches; does not normalize case or resolve symlinks.
mounts := make([]Mount, 0, len(d.Mounts)+len(y.Mounts)+len(o.Mounts))
location := make(map[string]int)
for _, mount := range append(append(d.Mounts, y.Mounts...), o.Mounts...) {
if i, ok := location[mount.Location]; ok {
mounts[i].Writable = mount.Writable
} else {
location[mount.Location] = len(mounts)
mounts = append(mounts, mount)
}
}
y.Mounts = mounts

// Note: DNS lists are not combined; highest priority setting is picked
if len(y.DNS) == 0 {
y.DNS = d.DNS
}
if len(o.DNS) > 0 {
y.DNS = o.DNS
}

env := d.Env
for k, v := range y.Env {
env[k] = v
}
for k, v := range o.Env {
env[k] = v
}
y.Env = env
}

func FillPortForwardDefaults(rule *PortForward, instDir string) {
Expand Down Expand Up @@ -214,13 +345,13 @@ func FillPortForwardDefaults(rule *PortForward, instDir string) {
}
}

func resolveArch(s string) Arch {
if s == "" || s == "default" {
func resolveArch(s *string) Arch {
if s == nil || *s == "" || *s == "default" {
if runtime.GOARCH == "amd64" {
return X8664
} else {
return AARCH64
}
}
return s
return *s
}
12 changes: 6 additions & 6 deletions pkg/limayaml/limayaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
)

type LimaYAML struct {
Arch Arch `yaml:"arch,omitempty" json:"arch,omitempty"`
Arch *Arch `yaml:"arch,omitempty" json:"arch,omitempty"`
Images []File `yaml:"images" json:"images"` // REQUIRED
CPUs int `yaml:"cpus,omitempty" json:"cpus,omitempty"`
Memory string `yaml:"memory,omitempty" json:"memory,omitempty"` // go-units.RAMInBytes
Disk string `yaml:"disk,omitempty" json:"disk,omitempty"` // go-units.RAMInBytes
CPUs *int `yaml:"cpus,omitempty" json:"cpus,omitempty"`
Memory *string `yaml:"memory,omitempty" json:"memory,omitempty"` // go-units.RAMInBytes
Disk *string `yaml:"disk,omitempty" json:"disk,omitempty"` // go-units.RAMInBytes
Mounts []Mount `yaml:"mounts,omitempty" json:"mounts,omitempty"`
SSH SSH `yaml:"ssh,omitempty" json:"ssh,omitempty"` // REQUIRED (FIXME)
Firmware Firmware `yaml:"firmware,omitempty" json:"firmware,omitempty"`
Expand Down Expand Up @@ -57,12 +57,12 @@ type SSH struct {
type Firmware struct {
// LegacyBIOS disables UEFI if set.
// LegacyBIOS is ignored for aarch64.
LegacyBIOS bool `yaml:"legacyBIOS,omitempty" json:"legacyBIOS,omitempty"`
LegacyBIOS *bool `yaml:"legacyBIOS,omitempty" json:"legacyBIOS,omitempty"`
}

type Video struct {
// Display is a QEMU display string
Display string `yaml:"display,omitempty" json:"display,omitempty"`
Display *string `yaml:"display,omitempty" json:"display,omitempty"`
}

type ProvisionMode = string
Expand Down
29 changes: 28 additions & 1 deletion pkg/limayaml/load.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package limayaml

import (
"errors"
"os"
"path/filepath"

"github.com/lima-vm/lima/pkg/store/dirnames"
"github.com/lima-vm/lima/pkg/store/filenames"
"gopkg.in/yaml.v2"
)

Expand All @@ -12,6 +18,27 @@ func Load(b []byte, filePath string) (*LimaYAML, error) {
if err := yaml.Unmarshal(b, &y); err != nil {
return nil, err
}
FillDefault(&y, filePath)
configDir, err := dirnames.LimaConfigDir()
if err != nil {
return nil, err
}
var d, o LimaYAML
bytes, err := os.ReadFile(filepath.Join(configDir, filenames.Default))
if err == nil {
if err := yaml.Unmarshal(bytes, &d); err != nil {
return nil, err
}
} else if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
bytes, err = os.ReadFile(filepath.Join(configDir, filenames.Override))
if err == nil {
if err := yaml.Unmarshal(bytes, &o); err != nil {
return nil, err
}
} else if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
FillDefault(&y, &d, &o, filePath)
return &y, nil
}
12 changes: 6 additions & 6 deletions pkg/limayaml/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import (
)

func Validate(y LimaYAML, warn bool) error {
switch y.Arch {
switch *y.Arch {
case X8664, AARCH64:
default:
return fmt.Errorf("field `arch` must be %q or %q , got %q", X8664, AARCH64, y.Arch)
return fmt.Errorf("field `arch` must be %q or %q , got %q", X8664, AARCH64, *y.Arch)
}

if len(y.Images) == 0 {
Expand Down Expand Up @@ -50,15 +50,15 @@ func Validate(y LimaYAML, warn bool) error {
}
}

if y.CPUs == 0 {
if *y.CPUs == 0 {
return errors.New("field `cpus` must be set")
}

if _, err := units.RAMInBytes(y.Memory); err != nil {
if _, err := units.RAMInBytes(*y.Memory); err != nil {
return fmt.Errorf("field `memory` has an invalid value: %w", err)
}

if _, err := units.RAMInBytes(y.Disk); err != nil {
if _, err := units.RAMInBytes(*y.Disk); err != nil {
return fmt.Errorf("field `memory` has an invalid value: %w", err)
}

Expand Down Expand Up @@ -297,7 +297,7 @@ func validateNetwork(y LimaYAML, warn bool) error {
return fmt.Errorf("field `%s.interface` must not be set to %q because it is reserved for slirp", field, qemu.SlirpNICName)
}
if prev, ok := interfaceName[nw.Interface]; ok {
return fmt.Errorf("field `%s.interface` value %q has already been used by field `network.vde[%d].name`", field, nw.Interface, prev)
return fmt.Errorf("field `%s.interface` value %q has already been used by field `networks[%d].interface`", field, nw.Interface, prev)
}
interfaceName[nw.Interface] = i
}
Expand Down
Loading

0 comments on commit c30b563

Please sign in to comment.