github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/machine/qemu/machine.go (about) 1 //go:build (amd64 && !windows) || (arm64 && !windows) 2 // +build amd64,!windows arm64,!windows 3 4 package qemu 5 6 import ( 7 "bufio" 8 "context" 9 "encoding/base64" 10 "encoding/json" 11 "fmt" 12 "io/fs" 13 "io/ioutil" 14 "net" 15 "net/http" 16 "net/url" 17 "os" 18 "os/exec" 19 "path/filepath" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/containers/common/pkg/config" 25 "github.com/hanks177/podman/v4/pkg/machine" 26 "github.com/hanks177/podman/v4/pkg/rootless" 27 "github.com/hanks177/podman/v4/utils" 28 "github.com/containers/storage/pkg/homedir" 29 "github.com/digitalocean/go-qemu/qmp" 30 "github.com/docker/go-units" 31 "github.com/pkg/errors" 32 "github.com/sirupsen/logrus" 33 ) 34 35 var ( 36 qemuProvider = &Provider{} 37 // vmtype refers to qemu (vs libvirt, krun, etc). 38 vmtype = "qemu" 39 ) 40 41 func GetQemuProvider() machine.Provider { 42 return qemuProvider 43 } 44 45 const ( 46 VolumeTypeVirtfs = "virtfs" 47 MountType9p = "9p" 48 dockerSock = "/var/run/docker.sock" 49 dockerConnectTimeout = 5 * time.Second 50 apiUpTimeout = 20 * time.Second 51 ) 52 53 type apiForwardingState int 54 55 const ( 56 noForwarding apiForwardingState = iota 57 claimUnsupported 58 notInstalled 59 machineLocal 60 dockerGlobal 61 ) 62 63 // NewMachine initializes an instance of a virtual machine based on the qemu 64 // virtualization. 65 func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { 66 vmConfigDir, err := machine.GetConfDir(vmtype) 67 if err != nil { 68 return nil, err 69 } 70 vm := new(MachineVM) 71 if len(opts.Name) > 0 { 72 vm.Name = opts.Name 73 } 74 ignitionFile, err := machine.NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil) 75 if err != nil { 76 return nil, err 77 } 78 vm.IgnitionFile = *ignitionFile 79 imagePath, err := machine.NewMachineFile(opts.ImagePath, nil) 80 if err != nil { 81 return nil, err 82 } 83 vm.ImagePath = *imagePath 84 vm.RemoteUsername = opts.Username 85 86 // Add a random port for ssh 87 port, err := utils.GetRandomPort() 88 if err != nil { 89 return nil, err 90 } 91 vm.Port = port 92 93 vm.CPUs = opts.CPUS 94 vm.Memory = opts.Memory 95 vm.DiskSize = opts.DiskSize 96 97 vm.Created = time.Now() 98 99 // Find the qemu executable 100 cfg, err := config.Default() 101 if err != nil { 102 return nil, err 103 } 104 execPath, err := cfg.FindHelperBinary(QemuCommand, true) 105 if err != nil { 106 return nil, err 107 } 108 cmd := []string{execPath} 109 // Add memory 110 cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...) 111 // Add cpus 112 cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...) 113 // Add ignition file 114 cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFile.GetPath()}...) 115 // Add qmp socket 116 monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout) 117 if err != nil { 118 return nil, err 119 } 120 vm.QMPMonitor = monitor 121 cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address.GetPath() + ",server=on,wait=off"}...) 122 123 // Add network 124 // Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is 125 // why we can only run one vm at a time right now 126 cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...) 127 if err := vm.setReadySocket(); err != nil { 128 return nil, err 129 } 130 131 // Add serial port for readiness 132 cmd = append(cmd, []string{ 133 "-device", "virtio-serial", 134 // qemu needs to establish the long name; other connections can use the symlink'd 135 "-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=" + vm.Name + "_ready", 136 "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...) 137 vm.CmdLine = cmd 138 if err := vm.setPIDSocket(); err != nil { 139 return nil, err 140 } 141 return vm, nil 142 } 143 144 // migrateVM takes the old configuration structure and migrates it 145 // to the new structure and writes it to the filesystem 146 func migrateVM(configPath string, config []byte, vm *MachineVM) error { 147 fmt.Printf("Migrating machine %q\n", vm.Name) 148 var old MachineVMV1 149 err := json.Unmarshal(config, &old) 150 if err != nil { 151 return err 152 } 153 // Looks like we loaded the older structure; now we need to migrate 154 // from the old structure to the new structure 155 _, pidFile, err := vm.getSocketandPid() 156 if err != nil { 157 return err 158 } 159 160 pidFilePath := machine.VMFile{Path: pidFile} 161 qmpMonitor := Monitor{ 162 Address: machine.VMFile{Path: old.QMPMonitor.Address}, 163 Network: old.QMPMonitor.Network, 164 Timeout: old.QMPMonitor.Timeout, 165 } 166 socketPath, err := getRuntimeDir() 167 if err != nil { 168 return err 169 } 170 virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock") 171 readySocket := machine.VMFile{Path: virtualSocketPath} 172 173 vm.HostUser = machine.HostUser{} 174 vm.ImageConfig = machine.ImageConfig{} 175 vm.ResourceConfig = machine.ResourceConfig{} 176 vm.SSHConfig = machine.SSHConfig{} 177 178 ignitionFilePath, err := machine.NewMachineFile(old.IgnitionFilePath, nil) 179 if err != nil { 180 return err 181 } 182 imagePath, err := machine.NewMachineFile(old.ImagePath, nil) 183 if err != nil { 184 return err 185 } 186 187 // setReadySocket will stick the entry into the new struct 188 if err := vm.setReadySocket(); err != nil { 189 return err 190 } 191 192 vm.CPUs = old.CPUs 193 vm.CmdLine = old.CmdLine 194 vm.DiskSize = old.DiskSize 195 vm.IdentityPath = old.IdentityPath 196 vm.IgnitionFile = *ignitionFilePath 197 vm.ImagePath = *imagePath 198 vm.ImageStream = old.ImageStream 199 vm.Memory = old.Memory 200 vm.Mounts = old.Mounts 201 vm.Name = old.Name 202 vm.PidFilePath = pidFilePath 203 vm.Port = old.Port 204 vm.QMPMonitor = qmpMonitor 205 vm.ReadySocket = readySocket 206 vm.RemoteUsername = old.RemoteUsername 207 vm.Rootful = old.Rootful 208 vm.UID = old.UID 209 210 // Backup the original config file 211 if err := os.Rename(configPath, configPath+".orig"); err != nil { 212 return err 213 } 214 // Write the config file 215 if err := vm.writeConfig(); err != nil { 216 // If the config file fails to be written, put the origina 217 // config file back before erroring 218 if renameError := os.Rename(configPath+".orig", configPath); renameError != nil { 219 logrus.Warn(renameError) 220 } 221 return err 222 } 223 // Remove the backup file 224 return os.Remove(configPath + ".orig") 225 } 226 227 // LoadVMByName reads a json file that describes a known qemu vm 228 // and returns a vm instance 229 func (p *Provider) LoadVMByName(name string) (machine.VM, error) { 230 vm := &MachineVM{Name: name} 231 vm.HostUser = machine.HostUser{UID: -1} // posix reserves -1, so use it to signify undefined 232 if err := vm.update(); err != nil { 233 return nil, err 234 } 235 236 // It is here for providing the ability to propagate 237 // proxy settings (e.g. HTTP_PROXY and others) on a start 238 // and avoid a need of re-creating/re-initiating a VM 239 if proxyOpts := machine.GetProxyVariables(); len(proxyOpts) > 0 { 240 proxyStr := "name=opt/com.coreos/environment,string=" 241 var proxies string 242 for k, v := range proxyOpts { 243 proxies = fmt.Sprintf("%s%s=\"%s\"|", proxies, k, v) 244 } 245 proxyStr = fmt.Sprintf("%s%s", proxyStr, base64.StdEncoding.EncodeToString([]byte(proxies))) 246 vm.CmdLine = append(vm.CmdLine, "-fw_cfg", proxyStr) 247 } 248 249 logrus.Debug(vm.CmdLine) 250 return vm, nil 251 } 252 253 // Init writes the json configuration file to the filesystem for 254 // other verbs (start, stop) 255 func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { 256 var ( 257 key string 258 ) 259 sshDir := filepath.Join(homedir.Get(), ".ssh") 260 v.IdentityPath = filepath.Join(sshDir, v.Name) 261 v.Rootful = opts.Rootful 262 263 switch opts.ImagePath { 264 case Testing, Next, Stable, "": 265 // Get image as usual 266 v.ImageStream = opts.ImagePath 267 dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath) 268 269 if err != nil { 270 return false, err 271 } 272 uncompressedFile, err := machine.NewMachineFile(dd.Get().LocalUncompressedFile, nil) 273 if err != nil { 274 return false, err 275 } 276 v.ImagePath = *uncompressedFile 277 if err := machine.DownloadImage(dd); err != nil { 278 return false, err 279 } 280 default: 281 // The user has provided an alternate image which can be a file path 282 // or URL. 283 v.ImageStream = "custom" 284 g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath) 285 if err != nil { 286 return false, err 287 } 288 imagePath, err := machine.NewMachineFile(g.Get().LocalUncompressedFile, nil) 289 if err != nil { 290 return false, err 291 } 292 v.ImagePath = *imagePath 293 if err := machine.DownloadImage(g); err != nil { 294 return false, err 295 } 296 } 297 // Add arch specific options including image location 298 v.CmdLine = append(v.CmdLine, v.addArchOptions()...) 299 var volumeType string 300 switch opts.VolumeDriver { 301 case "virtfs": 302 volumeType = VolumeTypeVirtfs 303 case "": // default driver 304 volumeType = VolumeTypeVirtfs 305 default: 306 err := fmt.Errorf("unknown volume driver: %s", opts.VolumeDriver) 307 return false, err 308 } 309 310 mounts := []machine.Mount{} 311 for i, volume := range opts.Volumes { 312 tag := fmt.Sprintf("vol%d", i) 313 paths := strings.SplitN(volume, ":", 3) 314 source := paths[0] 315 target := source 316 readonly := false 317 if len(paths) > 1 { 318 target = paths[1] 319 } 320 if len(paths) > 2 { 321 options := paths[2] 322 volopts := strings.Split(options, ",") 323 for _, o := range volopts { 324 switch o { 325 case "rw": 326 readonly = false 327 case "ro": 328 readonly = true 329 default: 330 fmt.Printf("Unknown option: %s\n", o) 331 } 332 } 333 } 334 if volumeType == VolumeTypeVirtfs { 335 virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=mapped-xattr", source, tag) 336 if readonly { 337 virtfsOptions += ",readonly" 338 } 339 v.CmdLine = append(v.CmdLine, []string{"-virtfs", virtfsOptions}...) 340 mounts = append(mounts, machine.Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly}) 341 } 342 } 343 v.Mounts = mounts 344 v.UID = os.Getuid() 345 346 // Add location of bootable image 347 v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.getImageFile()) 348 // This kind of stinks but no other way around this r/n 349 if len(opts.IgnitionPath) < 1 { 350 uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID), strconv.Itoa(v.Port), v.RemoteUsername) 351 uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root") 352 identity := filepath.Join(sshDir, v.Name) 353 354 uris := []url.URL{uri, uriRoot} 355 names := []string{v.Name, v.Name + "-root"} 356 357 // The first connection defined when connections is empty will become the default 358 // regardless of IsDefault, so order according to rootful 359 if opts.Rootful { 360 uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0] 361 } 362 363 for i := 0; i < 2; i++ { 364 if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil { 365 return false, err 366 } 367 } 368 } else { 369 fmt.Println("An ignition path was provided. No SSH connection was added to Podman") 370 } 371 // Write the JSON file 372 if err := v.writeConfig(); err != nil { 373 return false, fmt.Errorf("writing JSON file: %w", err) 374 } 375 // User has provided ignition file so keygen 376 // will be skipped. 377 if len(opts.IgnitionPath) < 1 { 378 var err error 379 key, err = machine.CreateSSHKeys(v.IdentityPath) 380 if err != nil { 381 return false, err 382 } 383 } 384 // Run arch specific things that need to be done 385 if err := v.prepare(); err != nil { 386 return false, err 387 } 388 originalDiskSize, err := getDiskSize(v.getImageFile()) 389 if err != nil { 390 return false, err 391 } 392 393 if err := v.resizeDisk(opts.DiskSize, originalDiskSize>>(10*3)); err != nil { 394 return false, err 395 } 396 // If the user provides an ignition file, we need to 397 // copy it into the conf dir 398 if len(opts.IgnitionPath) > 0 { 399 inputIgnition, err := ioutil.ReadFile(opts.IgnitionPath) 400 if err != nil { 401 return false, err 402 } 403 return false, ioutil.WriteFile(v.getIgnitionFile(), inputIgnition, 0644) 404 } 405 // Write the ignition file 406 ign := machine.DynamicIgnition{ 407 Name: opts.Username, 408 Key: key, 409 VMName: v.Name, 410 TimeZone: opts.TimeZone, 411 WritePath: v.getIgnitionFile(), 412 UID: v.UID, 413 } 414 err = machine.NewIgnitionFile(ign) 415 return err == nil, err 416 } 417 418 func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { 419 // If one setting fails to be applied, the others settings will not fail and still be applied. 420 // The setting(s) that failed to be applied will have its errors returned in setErrors 421 var setErrors []error 422 423 state, err := v.State(false) 424 if err != nil { 425 return setErrors, err 426 } 427 428 if state == machine.Running { 429 suffix := "" 430 if v.Name != machine.DefaultMachineName { 431 suffix = " " + v.Name 432 } 433 return setErrors, errors.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix) 434 } 435 436 if opts.Rootful != nil && v.Rootful != *opts.Rootful { 437 if err := v.setRootful(*opts.Rootful); err != nil { 438 setErrors = append(setErrors, errors.Wrapf(err, "failed to set rootful option")) 439 } else { 440 v.Rootful = *opts.Rootful 441 } 442 } 443 444 if opts.CPUs != nil && v.CPUs != *opts.CPUs { 445 v.CPUs = *opts.CPUs 446 v.editCmdLine("-smp", strconv.Itoa(int(v.CPUs))) 447 } 448 449 if opts.Memory != nil && v.Memory != *opts.Memory { 450 v.Memory = *opts.Memory 451 v.editCmdLine("-m", strconv.Itoa(int(v.Memory))) 452 } 453 454 if opts.DiskSize != nil && v.DiskSize != *opts.DiskSize { 455 if err := v.resizeDisk(*opts.DiskSize, v.DiskSize); err != nil { 456 setErrors = append(setErrors, errors.Wrapf(err, "failed to resize disk")) 457 } else { 458 v.DiskSize = *opts.DiskSize 459 } 460 } 461 462 err = v.writeConfig() 463 if err != nil { 464 setErrors = append(setErrors, err) 465 } 466 467 if len(setErrors) > 0 { 468 return setErrors, setErrors[0] 469 } 470 471 return setErrors, nil 472 } 473 474 // Start executes the qemu command line and forks it 475 func (v *MachineVM) Start(name string, _ machine.StartOptions) error { 476 var ( 477 conn net.Conn 478 err error 479 qemuSocketConn net.Conn 480 wait = time.Millisecond * 500 481 ) 482 483 v.Starting = true 484 if err := v.writeConfig(); err != nil { 485 return fmt.Errorf("writing JSON file: %w", err) 486 } 487 defer func() { 488 v.Starting = false 489 if err := v.writeConfig(); err != nil { 490 logrus.Errorf("Writing JSON file: %v", err) 491 } 492 }() 493 if v.isIncompatible() { 494 logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name) 495 } 496 497 forwardSock, forwardState, err := v.startHostNetworking() 498 if err != nil { 499 return errors.Errorf("unable to start host networking: %q", err) 500 } 501 502 rtPath, err := getRuntimeDir() 503 if err != nil { 504 return err 505 } 506 507 // If the temporary podman dir is not created, create it 508 podmanTempDir := filepath.Join(rtPath, "podman") 509 if _, err := os.Stat(podmanTempDir); os.IsNotExist(err) { 510 if mkdirErr := os.MkdirAll(podmanTempDir, 0755); mkdirErr != nil { 511 return err 512 } 513 } 514 515 // If the qemusocketpath exists and the vm is off/down, we should rm 516 // it before the dial as to avoid a segv 517 if err := v.QMPMonitor.Address.Delete(); err != nil { 518 return err 519 } 520 for i := 0; i < 6; i++ { 521 qemuSocketConn, err = net.Dial("unix", v.QMPMonitor.Address.GetPath()) 522 if err == nil { 523 break 524 } 525 time.Sleep(wait) 526 wait++ 527 } 528 if err != nil { 529 return err 530 } 531 defer qemuSocketConn.Close() 532 533 fd, err := qemuSocketConn.(*net.UnixConn).File() 534 if err != nil { 535 return err 536 } 537 defer fd.Close() 538 dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755) 539 if err != nil { 540 return err 541 } 542 defer dnr.Close() 543 dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755) 544 if err != nil { 545 return err 546 } 547 defer dnw.Close() 548 549 attr := new(os.ProcAttr) 550 files := []*os.File{dnr, dnw, dnw, fd} 551 attr.Files = files 552 logrus.Debug(v.CmdLine) 553 cmd := v.CmdLine 554 555 // Disable graphic window when not in debug mode 556 // Done in start, so we're not suck with the debug level we used on init 557 if !logrus.IsLevelEnabled(logrus.DebugLevel) { 558 cmd = append(cmd, "-display", "none") 559 } 560 561 _, err = os.StartProcess(v.CmdLine[0], cmd, attr) 562 if err != nil { 563 // check if qemu was not found 564 if !errors.Is(err, os.ErrNotExist) { 565 return err 566 } 567 // lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394 568 cfg, err := config.Default() 569 if err != nil { 570 return err 571 } 572 cmd[0], err = cfg.FindHelperBinary(QemuCommand, true) 573 if err != nil { 574 return err 575 } 576 _, err = os.StartProcess(cmd[0], cmd, attr) 577 if err != nil { 578 return errors.Wrapf(err, "unable to execute %q", cmd) 579 } 580 } 581 fmt.Println("Waiting for VM ...") 582 socketPath, err := getRuntimeDir() 583 if err != nil { 584 return err 585 } 586 587 // The socket is not made until the qemu process is running so here 588 // we do a backoff waiting for it. Once we have a conn, we break and 589 // then wait to read it. 590 for i := 0; i < 6; i++ { 591 conn, err = net.Dial("unix", filepath.Join(socketPath, "podman", v.Name+"_ready.sock")) 592 if err == nil { 593 break 594 } 595 time.Sleep(wait) 596 wait++ 597 } 598 if err != nil { 599 return err 600 } 601 defer conn.Close() 602 _, err = bufio.NewReader(conn).ReadString('\n') 603 if err != nil { 604 return err 605 } 606 if len(v.Mounts) > 0 { 607 state, err := v.State(true) 608 if err != nil { 609 return err 610 } 611 listening := v.isListening() 612 for state != machine.Running || !listening { 613 time.Sleep(100 * time.Millisecond) 614 state, err = v.State(true) 615 if err != nil { 616 return err 617 } 618 listening = v.isListening() 619 } 620 } 621 for _, mount := range v.Mounts { 622 fmt.Printf("Mounting volume... %s:%s\n", mount.Source, mount.Target) 623 // create mountpoint directory if it doesn't exist 624 // because / is immutable, we have to monkey around with permissions 625 // if we dont mount in /home or /mnt 626 args := []string{"-q", "--"} 627 if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") { 628 args = append(args, "sudo", "chattr", "-i", "/", ";") 629 } 630 args = append(args, "sudo", "mkdir", "-p", mount.Target) 631 if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") { 632 args = append(args, ";", "sudo", "chattr", "+i", "/", ";") 633 } 634 err = v.SSH(name, machine.SSHOptions{Args: args}) 635 if err != nil { 636 return err 637 } 638 switch mount.Type { 639 case MountType9p: 640 mountOptions := []string{"-t", "9p"} 641 mountOptions = append(mountOptions, []string{"-o", "trans=virtio", mount.Tag, mount.Target}...) 642 mountOptions = append(mountOptions, []string{"-o", "version=9p2000.L,msize=131072"}...) 643 if mount.ReadOnly { 644 mountOptions = append(mountOptions, []string{"-o", "ro"}...) 645 } 646 err = v.SSH(name, machine.SSHOptions{Args: append([]string{"-q", "--", "sudo", "mount"}, mountOptions...)}) 647 if err != nil { 648 return err 649 } 650 default: 651 return fmt.Errorf("unknown mount type: %s", mount.Type) 652 } 653 } 654 655 v.waitAPIAndPrintInfo(forwardState, forwardSock) 656 return nil 657 } 658 659 func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, error) { 660 // this is the format returned from the monitor 661 // {"return": {"status": "running", "singlestep": false, "running": true}} 662 663 type statusDetails struct { 664 Status string `json:"status"` 665 Step bool `json:"singlestep"` 666 Running bool `json:"running"` 667 Starting bool `json:"starting"` 668 } 669 type statusResponse struct { 670 Response statusDetails `json:"return"` 671 } 672 var response statusResponse 673 674 checkCommand := struct { 675 Execute string `json:"execute"` 676 }{ 677 Execute: "query-status", 678 } 679 input, err := json.Marshal(checkCommand) 680 if err != nil { 681 return "", err 682 } 683 b, err := monitor.Run(input) 684 if err != nil { 685 if errors.Cause(err) == os.ErrNotExist { 686 return machine.Stopped, nil 687 } 688 return "", err 689 } 690 if err := json.Unmarshal(b, &response); err != nil { 691 return "", err 692 } 693 if response.Response.Status == machine.Running { 694 return machine.Running, nil 695 } 696 return machine.Stopped, nil 697 } 698 699 // Stop uses the qmp monitor to call a system_powerdown 700 func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { 701 var disconnected bool 702 // check if the qmp socket is there. if not, qemu instance is gone 703 if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) { 704 // Right now it is NOT an error to stop a stopped machine 705 logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address) 706 return nil 707 } 708 qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) 709 if err != nil { 710 return err 711 } 712 // Simple JSON formation for the QAPI 713 stopCommand := struct { 714 Execute string `json:"execute"` 715 }{ 716 Execute: "system_powerdown", 717 } 718 input, err := json.Marshal(stopCommand) 719 if err != nil { 720 return err 721 } 722 if err := qmpMonitor.Connect(); err != nil { 723 return err 724 } 725 defer func() { 726 if !disconnected { 727 if err := qmpMonitor.Disconnect(); err != nil { 728 logrus.Error(err) 729 } 730 } 731 }() 732 733 if _, err = qmpMonitor.Run(input); err != nil { 734 return err 735 } 736 737 if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) { 738 return nil 739 } 740 pidString, err := v.PidFilePath.Read() 741 if err != nil { 742 return err 743 } 744 pidNum, err := strconv.Atoi(string(pidString)) 745 if err != nil { 746 return err 747 } 748 749 p, err := os.FindProcess(pidNum) 750 if p == nil && err != nil { 751 return err 752 } 753 754 v.LastUp = time.Now() 755 if err := v.writeConfig(); err != nil { // keep track of last up 756 return err 757 } 758 // Kill the process 759 if err := p.Kill(); err != nil { 760 return err 761 } 762 // Remove the pidfile 763 if err := v.PidFilePath.Delete(); err != nil { 764 return err 765 } 766 // Remove socket 767 if err := v.QMPMonitor.Address.Delete(); err != nil { 768 return err 769 } 770 771 if err := qmpMonitor.Disconnect(); err != nil { 772 // FIXME: this error should probably be returned 773 return nil // nolint: nilerr 774 } 775 776 disconnected = true 777 waitInternal := 250 * time.Millisecond 778 for i := 0; i < 5; i++ { 779 state, err := v.State(false) 780 if err != nil { 781 return err 782 } 783 if state != machine.Running { 784 break 785 } 786 time.Sleep(waitInternal) 787 waitInternal *= 2 788 } 789 790 return v.ReadySocket.Delete() 791 } 792 793 // NewQMPMonitor creates the monitor subsection of our vm 794 func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) { 795 rtDir, err := getRuntimeDir() 796 if err != nil { 797 return Monitor{}, err 798 } 799 if !rootless.IsRootless() { 800 rtDir = "/run" 801 } 802 rtDir = filepath.Join(rtDir, "podman") 803 if _, err := os.Stat(rtDir); os.IsNotExist(err) { 804 if err := os.MkdirAll(rtDir, 0755); err != nil { 805 return Monitor{}, err 806 } 807 } 808 if timeout == 0 { 809 timeout = defaultQMPTimeout 810 } 811 address, err := machine.NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil) 812 if err != nil { 813 return Monitor{}, err 814 } 815 monitor := Monitor{ 816 Network: network, 817 Address: *address, 818 Timeout: timeout, 819 } 820 return monitor, nil 821 } 822 823 // Remove deletes all the files associated with a machine including ssh keys, the image itself 824 func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() error, error) { 825 var ( 826 files []string 827 ) 828 829 // cannot remove a running vm unless --force is used 830 state, err := v.State(false) 831 if err != nil { 832 return "", nil, err 833 } 834 if state == machine.Running { 835 if !opts.Force { 836 return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) 837 } 838 err := v.Stop(v.Name, machine.StopOptions{}) 839 if err != nil { 840 return "", nil, err 841 } 842 } 843 844 // Collect all the files that need to be destroyed 845 if !opts.SaveKeys { 846 files = append(files, v.IdentityPath, v.IdentityPath+".pub") 847 } 848 if !opts.SaveIgnition { 849 files = append(files, v.getIgnitionFile()) 850 } 851 if !opts.SaveImage { 852 files = append(files, v.getImageFile()) 853 } 854 socketPath, err := v.forwardSocketPath() 855 if err != nil { 856 return "", nil, err 857 } 858 if socketPath.Symlink != nil { 859 files = append(files, *socketPath.Symlink) 860 } 861 files = append(files, socketPath.Path) 862 files = append(files, v.archRemovalFiles()...) 863 864 if err := machine.RemoveConnection(v.Name); err != nil { 865 logrus.Error(err) 866 } 867 if err := machine.RemoveConnection(v.Name + "-root"); err != nil { 868 logrus.Error(err) 869 } 870 871 vmConfigDir, err := machine.GetConfDir(vmtype) 872 if err != nil { 873 return "", nil, err 874 } 875 files = append(files, filepath.Join(vmConfigDir, v.Name+".json")) 876 confirmationMessage := "\nThe following files will be deleted:\n\n" 877 for _, msg := range files { 878 confirmationMessage += msg + "\n" 879 } 880 881 // remove socket and pid file if any: warn at low priority if things fail 882 // Remove the pidfile 883 if err := v.PidFilePath.Delete(); err != nil { 884 logrus.Debugf("Error while removing pidfile: %v", err) 885 } 886 // Remove socket 887 if err := v.QMPMonitor.Address.Delete(); err != nil { 888 logrus.Debugf("Error while removing podman-machine-socket: %v", err) 889 } 890 891 confirmationMessage += "\n" 892 return confirmationMessage, func() error { 893 for _, f := range files { 894 if err := os.Remove(f); err != nil && !errors.Is(err, os.ErrNotExist) { 895 logrus.Error(err) 896 } 897 } 898 return nil 899 }, nil 900 } 901 902 func (v *MachineVM) State(bypass bool) (machine.Status, error) { 903 // Check if qmp socket path exists 904 if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) { 905 return "", nil 906 } 907 err := v.update() 908 if err != nil { 909 return "", err 910 } 911 // Check if we can dial it 912 if v.Starting && !bypass { 913 return "", nil 914 } 915 monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) 916 if err != nil { 917 // FIXME: this error should probably be returned 918 return "", err 919 } 920 if err := monitor.Connect(); err != nil { 921 return "", err 922 } 923 defer func() { 924 if err := monitor.Disconnect(); err != nil { 925 logrus.Error(err) 926 } 927 }() 928 // If there is a monitor, lets see if we can query state 929 return v.checkStatus(monitor) 930 } 931 932 func (v *MachineVM) isListening() bool { 933 // Check if we can dial it 934 conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", v.Port), 10*time.Millisecond) 935 if err != nil { 936 return false 937 } 938 conn.Close() 939 return true 940 } 941 942 // SSH opens an interactive SSH session to the vm specified. 943 // Added ssh function to VM interface: pkg/machine/config/go : line 58 944 func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { 945 state, err := v.State(true) 946 if err != nil { 947 return err 948 } 949 if state != machine.Running { 950 return errors.Errorf("vm %q is not running", v.Name) 951 } 952 953 username := opts.Username 954 if username == "" { 955 username = v.RemoteUsername 956 } 957 958 sshDestination := username + "@localhost" 959 port := strconv.Itoa(v.Port) 960 961 args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", 962 "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR"} 963 if len(opts.Args) > 0 { 964 args = append(args, opts.Args...) 965 } else { 966 fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name) 967 } 968 969 cmd := exec.Command("ssh", args...) 970 logrus.Debugf("Executing: ssh %v\n", args) 971 972 cmd.Stdout = os.Stdout 973 cmd.Stderr = os.Stderr 974 cmd.Stdin = os.Stdin 975 976 return cmd.Run() 977 } 978 979 // executes qemu-image info to get the virtual disk size 980 // of the diskimage 981 func getDiskSize(path string) (uint64, error) { 982 // Find the qemu executable 983 cfg, err := config.Default() 984 if err != nil { 985 return 0, err 986 } 987 qemuPathDir, err := cfg.FindHelperBinary("qemu-img", true) 988 if err != nil { 989 return 0, err 990 } 991 diskInfo := exec.Command(qemuPathDir, "info", "--output", "json", path) 992 stdout, err := diskInfo.StdoutPipe() 993 if err != nil { 994 return 0, err 995 } 996 if err := diskInfo.Start(); err != nil { 997 return 0, err 998 } 999 tmpInfo := struct { 1000 VirtualSize uint64 `json:"virtual-size"` 1001 Filename string `json:"filename"` 1002 ClusterSize int64 `json:"cluster-size"` 1003 Format string `json:"format"` 1004 FormatSpecific struct { 1005 Type string `json:"type"` 1006 Data map[string]string `json:"data"` 1007 } 1008 DirtyFlag bool `json:"dirty-flag"` 1009 }{} 1010 if err := json.NewDecoder(stdout).Decode(&tmpInfo); err != nil { 1011 return 0, err 1012 } 1013 if err := diskInfo.Wait(); err != nil { 1014 return 0, err 1015 } 1016 return tmpInfo.VirtualSize, nil 1017 } 1018 1019 // List lists all vm's that use qemu virtualization 1020 func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) { 1021 return getVMInfos() 1022 } 1023 1024 func getVMInfos() ([]*machine.ListResponse, error) { 1025 vmConfigDir, err := machine.GetConfDir(vmtype) 1026 if err != nil { 1027 return nil, err 1028 } 1029 1030 var listed []*machine.ListResponse 1031 1032 if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error { 1033 vm := new(MachineVM) 1034 if strings.HasSuffix(d.Name(), ".json") { 1035 fullPath := filepath.Join(vmConfigDir, d.Name()) 1036 b, err := ioutil.ReadFile(fullPath) 1037 if err != nil { 1038 return err 1039 } 1040 err = json.Unmarshal(b, vm) 1041 if err != nil { 1042 // Checking if the file did not unmarshal because it is using 1043 // the deprecated config file format. 1044 migrateErr := migrateVM(fullPath, b, vm) 1045 if migrateErr != nil { 1046 return migrateErr 1047 } 1048 } 1049 listEntry := new(machine.ListResponse) 1050 1051 listEntry.Name = vm.Name 1052 listEntry.Stream = vm.ImageStream 1053 listEntry.VMType = "qemu" 1054 listEntry.CPUs = vm.CPUs 1055 listEntry.Memory = vm.Memory * units.MiB 1056 listEntry.DiskSize = vm.DiskSize * units.GiB 1057 listEntry.Port = vm.Port 1058 listEntry.RemoteUsername = vm.RemoteUsername 1059 listEntry.IdentityPath = vm.IdentityPath 1060 listEntry.CreatedAt = vm.Created 1061 1062 if listEntry.CreatedAt.IsZero() { 1063 listEntry.CreatedAt = time.Now() 1064 vm.Created = time.Now() 1065 if err := vm.writeConfig(); err != nil { 1066 return err 1067 } 1068 } 1069 1070 state, err := vm.State(false) 1071 if err != nil { 1072 return err 1073 } 1074 1075 if !vm.LastUp.IsZero() { // this means we have already written a time to the config 1076 listEntry.LastUp = vm.LastUp 1077 } else { // else we just created the machine AKA last up = created time 1078 listEntry.LastUp = vm.Created 1079 vm.LastUp = listEntry.LastUp 1080 if err := vm.writeConfig(); err != nil { 1081 return err 1082 } 1083 } 1084 if state == machine.Running { 1085 listEntry.Running = true 1086 } 1087 1088 listed = append(listed, listEntry) 1089 } 1090 return nil 1091 }); err != nil { 1092 return nil, err 1093 } 1094 return listed, err 1095 } 1096 1097 func (p *Provider) IsValidVMName(name string) (bool, error) { 1098 infos, err := getVMInfos() 1099 if err != nil { 1100 return false, err 1101 } 1102 for _, vm := range infos { 1103 if vm.Name == name { 1104 return true, nil 1105 } 1106 } 1107 return false, nil 1108 } 1109 1110 // CheckExclusiveActiveVM checks if there is a VM already running 1111 // that does not allow other VMs to be running 1112 func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { 1113 vms, err := getVMInfos() 1114 if err != nil { 1115 return false, "", errors.Wrap(err, "error checking VM active") 1116 } 1117 for _, vm := range vms { 1118 if vm.Running { 1119 return true, vm.Name, nil 1120 } 1121 } 1122 return false, "", nil 1123 } 1124 1125 // startHostNetworking runs a binary on the host system that allows users 1126 // to setup port forwarding to the podman virtual machine 1127 func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { 1128 cfg, err := config.Default() 1129 if err != nil { 1130 return "", noForwarding, err 1131 } 1132 binary, err := cfg.FindHelperBinary(machine.ForwarderBinaryName, false) 1133 if err != nil { 1134 return "", noForwarding, err 1135 } 1136 1137 attr := new(os.ProcAttr) 1138 dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755) 1139 if err != nil { 1140 return "", noForwarding, err 1141 } 1142 dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755) 1143 if err != nil { 1144 return "", noForwarding, err 1145 } 1146 1147 defer dnr.Close() 1148 defer dnw.Close() 1149 1150 attr.Files = []*os.File{dnr, dnw, dnw} 1151 cmd := []string{binary} 1152 cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", v.QMPMonitor.Address.GetPath()), "-pid-file", v.PidFilePath.GetPath()}...) 1153 // Add the ssh port 1154 cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...) 1155 1156 var forwardSock string 1157 var state apiForwardingState 1158 if !v.isIncompatible() { 1159 cmd, forwardSock, state = v.setupAPIForwarding(cmd) 1160 } 1161 1162 if logrus.GetLevel() == logrus.DebugLevel { 1163 cmd = append(cmd, "--debug") 1164 fmt.Println(cmd) 1165 } 1166 _, err = os.StartProcess(cmd[0], cmd, attr) 1167 return forwardSock, state, errors.Wrapf(err, "unable to execute: %q", cmd) 1168 } 1169 1170 func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) { 1171 socket, err := v.forwardSocketPath() 1172 1173 if err != nil { 1174 return cmd, "", noForwarding 1175 } 1176 1177 destSock := fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID) 1178 forwardUser := "core" 1179 1180 if v.Rootful { 1181 destSock = "/run/podman/podman.sock" 1182 forwardUser = "root" 1183 } 1184 1185 cmd = append(cmd, []string{"-forward-sock", socket.GetPath()}...) 1186 cmd = append(cmd, []string{"-forward-dest", destSock}...) 1187 cmd = append(cmd, []string{"-forward-user", forwardUser}...) 1188 cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...) 1189 1190 // The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket) 1191 // This allows the helper to only have to maintain one constant target to the user, which can be 1192 // repositioned without updating docker.sock. 1193 1194 link, err := v.userGlobalSocketLink() 1195 if err != nil { 1196 return cmd, socket.GetPath(), machineLocal 1197 } 1198 1199 if !dockerClaimSupported() { 1200 return cmd, socket.GetPath(), claimUnsupported 1201 } 1202 1203 if !dockerClaimHelperInstalled() { 1204 return cmd, socket.GetPath(), notInstalled 1205 } 1206 1207 if !alreadyLinked(socket.GetPath(), link) { 1208 if checkSockInUse(link) { 1209 return cmd, socket.GetPath(), machineLocal 1210 } 1211 1212 _ = os.Remove(link) 1213 if err = os.Symlink(socket.GetPath(), link); err != nil { 1214 logrus.Warnf("could not create user global API forwarding link: %s", err.Error()) 1215 return cmd, socket.GetPath(), machineLocal 1216 } 1217 } 1218 1219 if !alreadyLinked(link, dockerSock) { 1220 if checkSockInUse(dockerSock) { 1221 return cmd, socket.GetPath(), machineLocal 1222 } 1223 1224 if !claimDockerSock() { 1225 logrus.Warn("podman helper is installed, but was not able to claim the global docker sock") 1226 return cmd, socket.GetPath(), machineLocal 1227 } 1228 } 1229 1230 return cmd, dockerSock, dockerGlobal 1231 } 1232 1233 func (v *MachineVM) isIncompatible() bool { 1234 return v.UID == -1 1235 } 1236 1237 func (v *MachineVM) userGlobalSocketLink() (string, error) { 1238 path, err := machine.GetDataDir(v.Name) 1239 if err != nil { 1240 logrus.Errorf("Resolving data dir: %s", err.Error()) 1241 return "", err 1242 } 1243 // User global socket is located in parent directory of machine dirs (one per user) 1244 return filepath.Join(filepath.Dir(path), "podman.sock"), err 1245 } 1246 1247 func (v *MachineVM) forwardSocketPath() (*machine.VMFile, error) { 1248 sockName := "podman.sock" 1249 path, err := machine.GetDataDir(v.Name) 1250 if err != nil { 1251 logrus.Errorf("Resolving data dir: %s", err.Error()) 1252 return nil, err 1253 } 1254 return machine.NewMachineFile(filepath.Join(path, sockName), &sockName) 1255 } 1256 1257 func (v *MachineVM) setConfigPath() error { 1258 vmConfigDir, err := machine.GetConfDir(vmtype) 1259 if err != nil { 1260 return err 1261 } 1262 1263 configPath, err := machine.NewMachineFile(filepath.Join(vmConfigDir, v.Name)+".json", nil) 1264 if err != nil { 1265 return err 1266 } 1267 v.ConfigPath = *configPath 1268 return nil 1269 } 1270 1271 func (v *MachineVM) setReadySocket() error { 1272 readySocketName := v.Name + "_ready.sock" 1273 rtPath, err := getRuntimeDir() 1274 if err != nil { 1275 return err 1276 } 1277 virtualSocketPath, err := machine.NewMachineFile(filepath.Join(rtPath, "podman", readySocketName), &readySocketName) 1278 if err != nil { 1279 return err 1280 } 1281 v.ReadySocket = *virtualSocketPath 1282 return nil 1283 } 1284 1285 func (v *MachineVM) setPIDSocket() error { 1286 rtPath, err := getRuntimeDir() 1287 if err != nil { 1288 return err 1289 } 1290 if !rootless.IsRootless() { 1291 rtPath = "/run" 1292 } 1293 pidFileName := fmt.Sprintf("%s.pid", v.Name) 1294 socketDir := filepath.Join(rtPath, "podman") 1295 pidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName) 1296 if err != nil { 1297 return err 1298 } 1299 v.PidFilePath = *pidFilePath 1300 return nil 1301 } 1302 1303 // Deprecated: getSocketandPid is being replace by setPIDSocket and 1304 // machinefiles. 1305 func (v *MachineVM) getSocketandPid() (string, string, error) { 1306 rtPath, err := getRuntimeDir() 1307 if err != nil { 1308 return "", "", err 1309 } 1310 if !rootless.IsRootless() { 1311 rtPath = "/run" 1312 } 1313 socketDir := filepath.Join(rtPath, "podman") 1314 pidFile := filepath.Join(socketDir, fmt.Sprintf("%s.pid", v.Name)) 1315 qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name)) 1316 return qemuSocket, pidFile, nil 1317 } 1318 1319 func checkSockInUse(sock string) bool { 1320 if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket { 1321 _, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout) 1322 return err == nil 1323 } 1324 1325 return false 1326 } 1327 1328 func alreadyLinked(target string, link string) bool { 1329 read, err := os.Readlink(link) 1330 return err == nil && read == target 1331 } 1332 1333 func waitAndPingAPI(sock string) { 1334 client := http.Client{ 1335 Transport: &http.Transport{ 1336 DialContext: func(context.Context, string, string) (net.Conn, error) { 1337 con, err := net.DialTimeout("unix", sock, apiUpTimeout) 1338 if err != nil { 1339 return nil, err 1340 } 1341 if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil { 1342 return nil, err 1343 } 1344 return con, nil 1345 }, 1346 }, 1347 } 1348 1349 resp, err := client.Get("http://host/_ping") 1350 if err == nil { 1351 defer resp.Body.Close() 1352 } 1353 if err != nil || resp.StatusCode != 200 { 1354 logrus.Warn("API socket failed ping test") 1355 } 1356 } 1357 1358 func (v *MachineVM) waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string) { 1359 suffix := "" 1360 if v.Name != machine.DefaultMachineName { 1361 suffix = " " + v.Name 1362 } 1363 1364 if v.isIncompatible() { 1365 fmt.Fprintf(os.Stderr, "\n!!! ACTION REQUIRED: INCOMPATIBLE MACHINE !!!\n") 1366 1367 fmt.Fprintf(os.Stderr, "\nThis machine was created by an older podman release that is incompatible\n") 1368 fmt.Fprintf(os.Stderr, "with this release of podman. It has been started in a limited operational\n") 1369 fmt.Fprintf(os.Stderr, "mode to allow you to copy any necessary files before recreating it. This\n") 1370 fmt.Fprintf(os.Stderr, "can be accomplished with the following commands:\n\n") 1371 fmt.Fprintf(os.Stderr, "\t# Login and copy desired files (Optional)\n") 1372 fmt.Fprintf(os.Stderr, "\t# podman machine ssh%s tar cvPf - /path/to/files > backup.tar\n\n", suffix) 1373 fmt.Fprintf(os.Stderr, "\t# Recreate machine (DESTRUCTIVE!) \n") 1374 fmt.Fprintf(os.Stderr, "\tpodman machine stop%s\n", suffix) 1375 fmt.Fprintf(os.Stderr, "\tpodman machine rm -f%s\n", suffix) 1376 fmt.Fprintf(os.Stderr, "\tpodman machine init --now%s\n\n", suffix) 1377 fmt.Fprintf(os.Stderr, "\t# Copy back files (Optional)\n") 1378 fmt.Fprintf(os.Stderr, "\t# cat backup.tar | podman machine ssh%s tar xvPf - \n\n", suffix) 1379 } 1380 1381 if forwardState == noForwarding { 1382 return 1383 } 1384 1385 waitAndPingAPI(forwardSock) 1386 if !v.Rootful { 1387 fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") 1388 fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") 1389 fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") 1390 fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) 1391 } 1392 1393 fmt.Printf("API forwarding listening on: %s\n", forwardSock) 1394 if forwardState == dockerGlobal { 1395 fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") 1396 } else { 1397 stillString := "still " 1398 switch forwardState { 1399 case notInstalled: 1400 fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n") 1401 fmt.Printf("address can't be used by podman. ") 1402 if helper := findClaimHelper(); len(helper) > 0 { 1403 fmt.Printf("If you would like to install it run the\nfollowing commands:\n") 1404 fmt.Printf("\n\tsudo %s install\n", helper) 1405 fmt.Printf("\tpodman machine stop%s; podman machine start%s\n\n", suffix, suffix) 1406 } 1407 case machineLocal: 1408 fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n") 1409 case claimUnsupported: 1410 fallthrough 1411 default: 1412 stillString = "" 1413 } 1414 1415 fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString) 1416 fmt.Printf("following command in your terminal session:\n") 1417 fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) 1418 } 1419 } 1420 1421 // update returns the content of the VM's 1422 // configuration file in json 1423 func (v *MachineVM) update() error { 1424 if err := v.setConfigPath(); err != nil { 1425 return err 1426 } 1427 b, err := v.ConfigPath.Read() 1428 if err != nil { 1429 if errors.Is(err, os.ErrNotExist) { 1430 return errors.Wrap(machine.ErrNoSuchVM, v.Name) 1431 } 1432 return err 1433 } 1434 if err != nil { 1435 return err 1436 } 1437 err = json.Unmarshal(b, v) 1438 if err != nil { 1439 err = migrateVM(v.ConfigPath.GetPath(), b, v) 1440 if err != nil { 1441 return err 1442 } 1443 } 1444 return err 1445 } 1446 1447 func (v *MachineVM) writeConfig() error { 1448 // Set the path of the configfile before writing to make 1449 // life easier down the line 1450 if err := v.setConfigPath(); err != nil { 1451 return err 1452 } 1453 // Write the JSON file 1454 b, err := json.MarshalIndent(v, "", " ") 1455 if err != nil { 1456 return err 1457 } 1458 if err := ioutil.WriteFile(v.ConfigPath.GetPath(), b, 0644); err != nil { 1459 return err 1460 } 1461 return nil 1462 } 1463 1464 // getImageFile wrapper returns the path to the image used 1465 // to boot the VM 1466 func (v *MachineVM) getImageFile() string { 1467 return v.ImagePath.GetPath() 1468 } 1469 1470 // getIgnitionFile wrapper returns the path to the ignition file 1471 func (v *MachineVM) getIgnitionFile() string { 1472 return v.IgnitionFile.GetPath() 1473 } 1474 1475 // Inspect returns verbose detail about the machine 1476 func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { 1477 state, err := v.State(false) 1478 if err != nil { 1479 return nil, err 1480 } 1481 connInfo := new(machine.ConnectionConfig) 1482 podmanSocket, err := v.forwardSocketPath() 1483 if err != nil { 1484 return nil, err 1485 } 1486 connInfo.PodmanSocket = podmanSocket 1487 return &machine.InspectInfo{ 1488 ConfigPath: v.ConfigPath, 1489 ConnectionInfo: *connInfo, 1490 Created: v.Created, 1491 Image: v.ImageConfig, 1492 LastUp: v.LastUp, 1493 Name: v.Name, 1494 Resources: v.ResourceConfig, 1495 SSHConfig: v.SSHConfig, 1496 State: state, 1497 }, nil 1498 } 1499 1500 // resizeDisk increases the size of the machine's disk in GB. 1501 func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error { 1502 // Resize the disk image to input disk size 1503 // only if the virtualdisk size is less than 1504 // the given disk size 1505 if diskSize < oldSize { 1506 return errors.Errorf("new disk size must be larger than current disk size: %vGB", oldSize) 1507 } 1508 1509 // Find the qemu executable 1510 cfg, err := config.Default() 1511 if err != nil { 1512 return err 1513 } 1514 resizePath, err := cfg.FindHelperBinary("qemu-img", true) 1515 if err != nil { 1516 return err 1517 } 1518 resize := exec.Command(resizePath, []string{"resize", v.getImageFile(), strconv.Itoa(int(diskSize)) + "G"}...) 1519 resize.Stdout = os.Stdout 1520 resize.Stderr = os.Stderr 1521 if err := resize.Run(); err != nil { 1522 return errors.Errorf("resizing image: %q", err) 1523 } 1524 1525 return nil 1526 } 1527 1528 func (v *MachineVM) setRootful(rootful bool) error { 1529 changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root") 1530 if err != nil { 1531 return err 1532 } 1533 1534 if changeCon { 1535 newDefault := v.Name 1536 if rootful { 1537 newDefault += "-root" 1538 } 1539 err := machine.ChangeDefault(newDefault) 1540 if err != nil { 1541 return err 1542 } 1543 } 1544 return nil 1545 } 1546 1547 func (v *MachineVM) editCmdLine(flag string, value string) { 1548 found := false 1549 for i, val := range v.CmdLine { 1550 if val == flag { 1551 found = true 1552 v.CmdLine[i+1] = value 1553 break 1554 } 1555 } 1556 if !found { 1557 v.CmdLine = append(v.CmdLine, []string{flag, value}...) 1558 } 1559 } 1560 1561 // RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine 1562 func (p *Provider) RemoveAndCleanMachines() error { 1563 var ( 1564 vm machine.VM 1565 listResponse []*machine.ListResponse 1566 opts machine.ListOptions 1567 destroyOptions machine.RemoveOptions 1568 ) 1569 destroyOptions.Force = true 1570 var prevErr error 1571 1572 listResponse, err := p.List(opts) 1573 if err != nil { 1574 return err 1575 } 1576 1577 for _, mach := range listResponse { 1578 vm, err = p.LoadVMByName(mach.Name) 1579 if err != nil { 1580 if prevErr != nil { 1581 logrus.Error(prevErr) 1582 } 1583 prevErr = err 1584 } 1585 _, remove, err := vm.Remove(mach.Name, destroyOptions) 1586 if err != nil { 1587 if prevErr != nil { 1588 logrus.Error(prevErr) 1589 } 1590 prevErr = err 1591 } else { 1592 if err := remove(); err != nil { 1593 if prevErr != nil { 1594 logrus.Error(prevErr) 1595 } 1596 prevErr = err 1597 } 1598 } 1599 } 1600 1601 // Clean leftover files in data dir 1602 dataDir, err := machine.DataDirPrefix() 1603 if err != nil { 1604 if prevErr != nil { 1605 logrus.Error(prevErr) 1606 } 1607 prevErr = err 1608 } else { 1609 err := os.RemoveAll(dataDir) 1610 if err != nil { 1611 if prevErr != nil { 1612 logrus.Error(prevErr) 1613 } 1614 prevErr = err 1615 } 1616 } 1617 1618 // Clean leftover files in conf dir 1619 confDir, err := machine.ConfDirPrefix() 1620 if err != nil { 1621 if prevErr != nil { 1622 logrus.Error(prevErr) 1623 } 1624 prevErr = err 1625 } else { 1626 err := os.RemoveAll(confDir) 1627 if err != nil { 1628 if prevErr != nil { 1629 logrus.Error(prevErr) 1630 } 1631 prevErr = err 1632 } 1633 } 1634 return prevErr 1635 }