diff --git a/cmd/start.go b/cmd/start.go index 408fb1a9b..a64330fba 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -175,6 +175,9 @@ func init() { startCmd.Flags().StringVar(&startCmdArgs.MountType, "mount-type", defaultMountTypeQEMU, "volume driver for the mount ("+mounts+")") startCmd.Flags().BoolVar(&startCmdArgs.MountINotify, "mount-inotify", true, "propagate inotify file events to the VM") + // lima disks + startCmd.Flags().StringSliceVarP(&startCmdArgs.Flags.Mounts, "lima-disk", "D", nil, "map of disk name and size e.g. --lima-disk=my-disk,10G") + // ssh agent startCmd.Flags().BoolVarP(&startCmdArgs.ForwardAgent, "ssh-agent", "s", false, "forward SSH agent to the VM") @@ -372,6 +375,9 @@ func prepareConfig(cmd *cobra.Command) { if !cmd.Flag("mount-inotify").Changed { startCmdArgs.MountINotify = current.MountINotify } + if !cmd.Flag("lima-disk").Changed { + startCmdArgs.LimaDisks = current.LimaDisks + } if !cmd.Flag("ssh-agent").Changed { startCmdArgs.ForwardAgent = current.ForwardAgent } diff --git a/config/config.go b/config/config.go index a0558be98..d113d9c7b 100644 --- a/config/config.go +++ b/config/config.go @@ -84,7 +84,7 @@ type Config struct { VZRosetta bool `yaml:"rosetta,omitempty"` // disks - Disks []Disk `yaml:"disks,omitempty"` + LimaDisks []Disk `yaml:"limaDisks,omitempty"` // volume mounts Mounts []Mount `yaml:"mounts,omitempty"` @@ -109,9 +109,8 @@ type Config struct { } type Disk struct { - Name string `yaml:"name"` - Size string `yaml:"size"` - Format string `yaml:"format,omitempty"` + Name string `yaml:"name"` + Size string `yaml:"size"` } // Kubernetes is kubernetes configuration diff --git a/config/configmanager/config.go b/config/configmanager/config.go index 02199f5ce..4be8be7aa 100644 --- a/config/configmanager/config.go +++ b/config/configmanager/config.go @@ -71,7 +71,6 @@ func ValidateConfig(c config.Config) error { if _, ok := validVMTypes[c.VMType]; !ok { return fmt.Errorf("invalid vmType: '%s'", c.VMType) } - return nil } @@ -85,6 +84,7 @@ func Load() (config.Config, error) { // config file does not exist, check older version for backward compatibility if _, err := os.Stat(oldCFile); err != nil { + logrus.Warn(fmt.Errorf("error loading config: %w, proceeding with defaults", err)) return config.Config{}, nil } diff --git a/embedded/defaults/colima.yaml b/embedded/defaults/colima.yaml index 5276c609f..605d3ebab 100644 --- a/embedded/defaults/colima.yaml +++ b/embedded/defaults/colima.yaml @@ -175,12 +175,13 @@ mounts: [] # Configure additional disks # # EXAMPLE -# additionalDisks: -# - size: 10 -# path: /tmp/colima/disk1.img -# - size: 20 -# path: /tmp/colima/disk2.img -additionalDisks: [] +# disks: +# - name: mydisk +# size: 20G +# format: raw (optional). qcow2 is not supported in current limactl. raw is default. +# - name: otherdisk +# size: 1M +limaDisks: [] # Environment variables for the virtual machine. # diff --git a/environment/vm/lima/disk.go b/environment/vm/lima/disk.go new file mode 100644 index 000000000..ca8b28b6c --- /dev/null +++ b/environment/vm/lima/disk.go @@ -0,0 +1,97 @@ +package lima + +import ( + "encoding/json" + "fmt" + "github.com/abiosoft/colima/config" + "github.com/docker/go-units" + "github.com/sirupsen/logrus" + "strings" +) + +func (l *limaVM) updateLimaDisks(conf config.Config) ([]Disk, error) { + logrus.Trace(fmt.Errorf("updating lima disks: %s", conf.LimaDisks)) + out, err := l.host.RunOutput(limactl, "disk", "list", "--json") + if err != nil { + logrus.Trace(fmt.Errorf("error listing disks: %s, %s", out, err)) + return []Disk{}, err + } + logrus.Trace(fmt.Errorf("listing disks: %s", out)) + + var listedDisks []Disk + if out != "" { + for _, line := range strings.Split(strings.TrimSuffix(out, "\n"), "\n") { + var listedDisk Disk + err = json.Unmarshal([]byte(line), &listedDisk) + if err != nil { + logrus.Trace(fmt.Errorf("error unmarshaling listed disks: %s", err)) + return []Disk{}, err + } + listedDisks = append(listedDisks, listedDisk) + } + } + + var disksToCreate []config.Disk + for _, disk := range conf.LimaDisks { + diskName := config.CurrentProfile().ID + "-" + disk.Name + var found = false + for _, listedDisk := range listedDisks { + if listedDisk.Name == diskName { + found = true + break + } + } + if !found { + disksToCreate = append(disksToCreate, disk) + } + } + + for _, disk := range disksToCreate { + diskName := config.CurrentProfile().ID + "-" + disk.Name + logrus.Trace(fmt.Errorf("creating disk %s", diskName)) + out, err = l.host.RunOutput(limactl, "disk", "create", diskName, "--size", disk.Size, "--format", "raw") + if err != nil { + logrus.Trace(fmt.Errorf("error creating disk: %s, %s", out, err)) + return []Disk{}, err + } + logrus.Trace(fmt.Errorf("disk create output: %s", out)) + } + + var disksToDelete []Disk + for _, listedDisk := range listedDisks { + var found = false + for _, disk := range conf.LimaDisks { + diskName := config.CurrentProfile().ID + "-" + disk.Name + if listedDisk.Name == diskName { + found = true + diskSize, err := units.RAMInBytes(disk.Size) + if err != nil { + logrus.Trace(fmt.Errorf("error parsing disk size: %s", err)) + return []Disk{}, err + } + logrus.Trace(fmt.Errorf("disk size: %d", diskSize)) + + if diskSize == listedDisk.Size { + logrus.Trace(fmt.Errorf("disk %s is up to date", diskName)) + continue + } + return []Disk{}, fmt.Errorf("%s cannot be updated: limactl does not support updating disks", diskName) + } + } + if !found { + disksToDelete = append(disksToDelete, listedDisk) + } + } + + for _, disk := range disksToDelete { + logrus.Trace(fmt.Errorf("deleting disk %s", disk.Name)) + out, err := l.host.RunOutput(limactl, "disk", "delete", disk.Name) + if err != nil { + logrus.Trace(fmt.Errorf("error deleting disk: %s, %s", out, err)) + return []Disk{}, err + } + logrus.Trace(fmt.Errorf("disk delete output: %s", out)) + } + + return disksToDelete, nil +} diff --git a/environment/vm/lima/lima.go b/environment/vm/lima/lima.go index 9813ddb28..32f285da9 100644 --- a/environment/vm/lima/lima.go +++ b/environment/vm/lima/lima.go @@ -78,6 +78,14 @@ func (l limaVM) Dependencies() []string { } } +type Disk struct { + Name string `json:"name"` + Size int64 `json:"size"` + Dir string `json:"dir"` + Instance string `json:"instance"` + MountPoint string `json:"mountPoint"` +} + func (l *limaVM) Start(ctx context.Context, conf config.Config) error { a := l.Init(ctx) @@ -90,9 +98,14 @@ func (l *limaVM) Start(ctx context.Context, conf config.Config) error { return err }) - a.Stage("creating and starting") configFile := filepath.Join(os.TempDir(), config.CurrentProfile().ID+".yaml") + var disksToDelete []Disk + disksToDelete, err := l.updateLimaDisks(conf) + if err != nil { + return err + } + a.Add(func() (err error) { l.limaConf, err = newConf(ctx, conf) if err != nil { @@ -102,14 +115,6 @@ func (l *limaVM) Start(ctx context.Context, conf config.Config) error { }) a.Add(l.writeNetworkFile) - if len(conf.Disks) > 0 { - for _, d := range conf.Disks { - a.Add(func() error { - return l.host.Run(limactl, "disk", "create", d.Name, "--size", d.Size, "--format", d.Format) - }) - } - } - a.Add(func() error { return l.host.Run(limactl, "start", "--tty=false", configFile) }) @@ -117,13 +122,13 @@ func (l *limaVM) Start(ctx context.Context, conf config.Config) error { return os.Remove(configFile) }) - // adding it to command chain to execute only after successful startup. + logrus.Trace("adding it to command chain to execute only after successful startup.") a.Add(func() error { l.conf = conf return nil }) - // restart needed for docker user + logrus.Trace("restart needed for docker user") if conf.Runtime == docker.Name { a.Add(func() error { ctx := context.WithValue(ctx, cli.CtxKeyQuiet, true) @@ -131,7 +136,7 @@ func (l *limaVM) Start(ctx context.Context, conf config.Config) error { }) } - l.addPostStartActions(a, conf) + l.addPostStartActions(a, conf, disksToDelete) return a.Exec() } @@ -145,6 +150,12 @@ func (l *limaVM) resume(ctx context.Context, conf config.Config) error { return nil } + var disksToDelete []Disk + disksToDelete, err := l.updateLimaDisks(conf) + if err != nil { + return err + } + a.Add(func() (err error) { ctx, err = l.startDaemon(ctx, conf) return err @@ -163,21 +174,12 @@ func (l *limaVM) resume(ctx context.Context, conf config.Config) error { a.Add(l.writeNetworkFile) - if len(conf.Disks) > 0 { - for _, d := range conf.Disks { - log.Println("creating disk", d.Name) - a.Add(func() error { - return l.host.Run(limactl, "disk", "create", d.Name, "--size", d.Size, "--format", d.Format) - }) - } - } - a.Stage("starting") a.Add(func() error { return l.host.Run(limactl, "start", config.CurrentProfile().ID) }) - l.addPostStartActions(a, conf) + l.addPostStartActions(a, conf, disksToDelete) return a.Exec() } @@ -224,27 +226,31 @@ func (l limaVM) Stop(ctx context.Context, force bool) error { func (l limaVM) Teardown(ctx context.Context) error { a := l.Init(ctx) + conf, _ := limautil.InstanceConfig() if util.MacOS() { - conf, _ := limautil.InstanceConfig() a.Retry("", time.Second*1, 10, func(retryCount int) error { return l.daemon.Stop(ctx, conf) }) } + a.Stage("stopping") a.Add(func() error { - return l.host.Run(limactl, "delete", "--force", config.CurrentProfile().ID) + return l.Stop(ctx, false) }) - conf, _ := limautil.InstanceConfig() - - if len(conf.Disks) > 0 { - for _, d := range conf.Disks { - a.Add(func() error { - return l.host.Run(limactl, "disk", "delete", d.Name) - }) + a.Add(func() error { + for _, d := range conf.LimaDisks { + diskName := config.CurrentProfile().ID + "-" + d.Name + logrus.Trace(fmt.Errorf("deleting disk %s", diskName)) + return l.host.Run(limactl, "disk", "delete", diskName) } - } + return nil + }) + + a.Add(func() error { + return l.host.Run(limactl, "delete", "--force", config.CurrentProfile().ID) + }) return a.Exec() } @@ -341,7 +347,19 @@ func (l *limaVM) syncDiskSize(ctx context.Context, conf config.Config) config.Co return conf } -func (l *limaVM) addPostStartActions(a *cli.ActiveCommandChain, conf config.Config) { +func (l *limaVM) addPostStartActions(a *cli.ActiveCommandChain, conf config.Config, disksToDelete []Disk) { + // delete unused disk mount directories + a.Add(func() error { + for _, disk := range disksToDelete { + err := l.Run("sh", "-c", "sudo rm -rf /mnt/lima-"+disk.Name) + if err != nil { + logrus.Trace(fmt.Errorf("error deleting disk mount directory: %s", err)) + return err + } + } + return nil + }) + // package dependencies a.Add(func() error { return l.installDependencies(a.Logger(), conf) diff --git a/environment/vm/lima/limautil/limautil.go b/environment/vm/lima/limautil/limautil.go index f9419b14d..af441b43a 100644 --- a/environment/vm/lima/limautil/limautil.go +++ b/environment/vm/lima/limautil/limautil.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/sirupsen/logrus" "os" "os/exec" "strings" @@ -52,17 +53,19 @@ func IPAddress(profileID string) string { const fallback = "127.0.0.1" instance, err := getInstance(profileID) if err != nil { + logrus.Trace(fmt.Errorf("error getting instance: %s", err)) return fallback } - + logrus.Trace(fmt.Errorf("instance: %s", instance)) if len(instance.Network) > 0 { for _, n := range instance.Network { if n.Interface == vmnet.NetInterface { - return getIPAddress(profileID, n.Interface) + result := getIPAddress(profileID, n.Interface) + logrus.Trace(fmt.Errorf("ip address: %s", result)) + return result } } } - return fallback } @@ -171,14 +174,16 @@ func Instances(ids ...string) ([]InstanceInfo, error) { } func getIPAddress(profileID, interfaceName string) string { var buf bytes.Buffer + logrus.Trace(fmt.Errorf("getting ip address for %s", interfaceName)) // TODO: this should be less hacky - cmd := Limactl("shell", profileID, "sh", "-c", - `ip -4 addr show `+interfaceName+` | grep inet | awk -F' ' '{print $2 }' | cut -d/ -f1`) + cmd := Limactl("shell", profileID, "sh", "-c", `ip -4 -o addr show `+interfaceName+` | grep inet | cut -d ' ' -f7 | cut -d/ -f1`) cmd.Stderr = nil cmd.Stdout = &buf _ = cmd.Run() - return strings.TrimSpace(buf.String()) + result := strings.TrimSpace(buf.String()) + logrus.Trace(fmt.Errorf("ip address: %s", result)) + return result } func getRuntime(conf config.Config) string { diff --git a/environment/vm/lima/yaml.go b/environment/vm/lima/yaml.go index b568f208c..a8b612354 100644 --- a/environment/vm/lima/yaml.go +++ b/environment/vm/lima/yaml.go @@ -100,8 +100,9 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { // add user to docker group // "sudo", "usermod", "-aG", "docker", user l.Provision = append(l.Provision, Provision{ - Mode: ProvisionModeDependency, - Script: "groupadd -f docker && usermod -aG docker $LIMA_CIDATA_USER", + Mode: ProvisionModeDependency, + Script: "groupadd -f docker && usermod -aG docker $LIMA_CIDATA_USER", + SkipResolution: true, }) // set hostname @@ -253,6 +254,15 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { Script: `readlink /usr/sbin/fstrim || fstrim -a`, }) + // workaround for slow virtiofs https://github.com/drud/ddev/issues/4466#issuecomment-1361261185 + // TODO: remove when fixed upstream + if l.MountType == VIRTIOFS { + l.Provision = append(l.Provision, Provision{ + Mode: ProvisionModeSystem, + Script: `stat /sys/class/block/vda/queue/write_cache && echo 'write through' > /sys/class/block/vda/queue/write_cache`, + }) + } + if len(conf.Mounts) == 0 { l.Mounts = append(l.Mounts, Mount{Location: "~", Writable: true}, @@ -291,10 +301,14 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { } } - if len(conf.Disks) > 0 { - for _, d := range conf.Disks { - l.AdditionalDisks = append(l.AdditionalDisks, d.Name) - } + for _, d := range conf.LimaDisks { + diskName := config.CurrentProfile().ID + "-" + d.Name + logrus.Traceln(fmt.Errorf("using additional disk %s", diskName)) + l.AdditionalDisks = append(l.AdditionalDisks, AdditionalDisk{ + Name: diskName, + Format: true, + FsType: "ext4", + }) } // provision scripts @@ -310,7 +324,11 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { type Arch = environment.Arch -type Disk = string +type AdditionalDisk struct { + Name string `yaml:"name"` + Format bool `yaml:"format,omitempty"` + FsType string `yaml:"fsType,omitempty"` +} // Config is lima config. Code copied from lima and modified. type Config struct { @@ -320,7 +338,7 @@ type Config struct { CPUs *int `yaml:"cpus,omitempty"` Memory string `yaml:"memory,omitempty"` Disk string `yaml:"disk,omitempty"` - AdditionalDisks []Disk `yaml:"additionalDisks,omitempty" json:"additionalDisks,omitempty"` + AdditionalDisks []AdditionalDisk `yaml:"additionalDisks,omitempty" json:"additionalDisks,omitempty"` Mounts []Mount `yaml:"mounts,omitempty"` MountType MountType `yaml:"mountType,omitempty" json:"mountType,omitempty"` SSH SSH `yaml:"ssh"` @@ -423,9 +441,9 @@ type Network struct { type ProvisionMode = string const ( - ProvisionModeSystem ProvisionMode = "system" - ProvisionModeUser ProvisionMode = "user" - ProvisionModeBoot ProvisionMode = "boot" + ProvisionModeSystem ProvisionMode = "system" + // ProvisionModeUser ProvisionMode = "user" + // ProvisionModeBoot ProvisionMode = "boot" ProvisionModeDependency ProvisionMode = "dependency" )