mirror of
https://github.com/containers/podman.git
synced 2025-08-06 11:32:07 +08:00

Changes the order in which the machine-specific files are removed in `Remove()`. Removes the system connections first, then removes the `configPath` last. `configPath` is removed last, because in the case of an error with any of the previous files, the removal can be attempted again since the machine still "exists". Made the errors in `Remove` hard errors instead of soft errors. Added the implementation for the QEMU-specific file removal. [NO NEW TESTS NEEDED] Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
412 lines
10 KiB
Go
412 lines
10 KiB
Go
package vmconfigs
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
define2 "github.com/containers/podman/v5/libpod/define"
|
|
"github.com/containers/podman/v5/pkg/errorhandling"
|
|
"github.com/containers/podman/v5/pkg/machine/connection"
|
|
"github.com/containers/podman/v5/pkg/machine/define"
|
|
"github.com/containers/podman/v5/pkg/machine/lock"
|
|
"github.com/containers/podman/v5/utils"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
/*
|
|
info Display machine host info common
|
|
init Initialize a virtual machine specific
|
|
inspect Inspect an existing machine specific
|
|
list List machines specific
|
|
os Manage a Podman virtual machine's OS common
|
|
rm Remove an existing machine specific
|
|
set Set a virtual machine setting specific
|
|
ssh SSH into an existing machine common
|
|
start Start an existing machine specific
|
|
stop Stop an existing machine specific
|
|
*/
|
|
|
|
var (
|
|
SSHRemoteConnection RemoteConnectionType = "ssh"
|
|
DefaultIgnitionUserName = "core"
|
|
ForwarderBinaryName = "gvproxy"
|
|
)
|
|
|
|
type RemoteConnectionType string
|
|
|
|
// NewMachineConfig creates the initial machine configuration file from cli options
|
|
func NewMachineConfig(opts define.InitOptions, dirs *define.MachineDirs, sshIdentityPath string, vmtype define.VMType) (*MachineConfig, error) {
|
|
mc := new(MachineConfig)
|
|
mc.Name = opts.Name
|
|
mc.dirs = dirs
|
|
|
|
machineLock, err := lock.GetMachineLock(opts.Name, dirs.ConfigDir.GetPath())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mc.lock = machineLock
|
|
|
|
// Assign Dirs
|
|
cf, err := define.NewMachineFile(filepath.Join(dirs.ConfigDir.GetPath(), fmt.Sprintf("%s.json", opts.Name)), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mc.configPath = cf
|
|
|
|
if vmtype != define.QemuVirt && len(opts.USBs) > 0 {
|
|
return nil, fmt.Errorf("USB host passthrough not supported for %s machines", vmtype)
|
|
}
|
|
|
|
usbs, err := define.ParseUSBs(opts.USBs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// System Resources
|
|
mrc := ResourceConfig{
|
|
CPUs: opts.CPUS,
|
|
DiskSize: opts.DiskSize,
|
|
Memory: opts.Memory,
|
|
USBs: usbs,
|
|
}
|
|
mc.Resources = mrc
|
|
|
|
// TODO WSL had a locking port mechanism, we should consider this.
|
|
sshPort, err := utils.GetRandomPort()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sshConfig := SSHConfig{
|
|
IdentityPath: sshIdentityPath,
|
|
Port: sshPort,
|
|
RemoteUsername: opts.Username,
|
|
}
|
|
|
|
mc.SSH = sshConfig
|
|
mc.Created = time.Now()
|
|
|
|
mc.HostUser = HostUser{UID: getHostUID(), Rootful: opts.Rootful}
|
|
|
|
return mc, nil
|
|
}
|
|
|
|
// Lock creates a lock on the machine for single access
|
|
func (mc *MachineConfig) Lock() {
|
|
mc.lock.Lock()
|
|
}
|
|
|
|
// Unlock removes an existing lock
|
|
func (mc *MachineConfig) Unlock() {
|
|
mc.lock.Unlock()
|
|
}
|
|
|
|
// Write is a locking way to the machine configuration file
|
|
func (mc *MachineConfig) Write() error {
|
|
mc.Lock()
|
|
defer mc.Unlock()
|
|
return mc.write()
|
|
}
|
|
|
|
// Refresh reloads the config file from disk
|
|
func (mc *MachineConfig) Refresh() error {
|
|
content, err := os.ReadFile(mc.configPath.GetPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(content, mc)
|
|
}
|
|
|
|
// write is a non-locking way to write the machine configuration file to disk
|
|
func (mc *MachineConfig) write() error {
|
|
if mc.configPath == nil {
|
|
return fmt.Errorf("no configuration file associated with vm %q", mc.Name)
|
|
}
|
|
b, err := json.Marshal(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
logrus.Debugf("writing configuration file %q", mc.configPath.Path)
|
|
return os.WriteFile(mc.configPath.GetPath(), b, define.DefaultFilePerm)
|
|
}
|
|
|
|
func (mc *MachineConfig) SetRootful(rootful bool) error {
|
|
if err := connection.UpdateConnectionIfDefault(rootful, mc.Name, mc.Name+"-root"); err != nil {
|
|
return err
|
|
}
|
|
mc.HostUser.Rootful = rootful
|
|
mc.HostUser.Modified = true
|
|
return nil
|
|
}
|
|
|
|
func (mc *MachineConfig) removeSystemConnection() error { //nolint:unused
|
|
return define2.ErrNotImplemented
|
|
}
|
|
|
|
// updateLastBoot writes the current time to the machine configuration file. it is
|
|
// an non-locking method and assumes it is being called locked
|
|
func (mc *MachineConfig) updateLastBoot() error { //nolint:unused
|
|
mc.LastUp = time.Now()
|
|
return mc.Write()
|
|
}
|
|
|
|
func (mc *MachineConfig) Remove(saveIgnition, saveImage bool) ([]string, func() error, error) {
|
|
ignitionFile, err := mc.IgnitionFile()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
readySocket, err := mc.ReadySocket()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
logPath, err := mc.LogFile()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
rmFiles := []string{
|
|
mc.configPath.GetPath(),
|
|
readySocket.GetPath(),
|
|
logPath.GetPath(),
|
|
}
|
|
if !saveImage {
|
|
mc.ImagePath.GetPath()
|
|
}
|
|
if !saveIgnition {
|
|
ignitionFile.GetPath()
|
|
}
|
|
|
|
mcRemove := func() error {
|
|
var errs []error
|
|
if err := connection.RemoveConnections(mc.Name, mc.Name+"-root"); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
if !saveIgnition {
|
|
if err := ignitionFile.Delete(); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
if !saveImage {
|
|
if err := mc.ImagePath.Delete(); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
if err := readySocket.Delete(); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
if err := logPath.Delete(); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
if err := mc.configPath.Delete(); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
return errorhandling.JoinErrors(errs)
|
|
}
|
|
|
|
return rmFiles, mcRemove, nil
|
|
}
|
|
|
|
// ConfigDir is a simple helper to obtain the machine config dir
|
|
func (mc *MachineConfig) ConfigDir() (*define.VMFile, error) {
|
|
if mc.dirs == nil || mc.dirs.ConfigDir == nil {
|
|
return nil, errors.New("no configuration directory set")
|
|
}
|
|
return mc.dirs.ConfigDir, nil
|
|
}
|
|
|
|
// DataDir is a simple helper function to obtain the machine data dir
|
|
func (mc *MachineConfig) DataDir() (*define.VMFile, error) {
|
|
if mc.dirs == nil || mc.dirs.DataDir == nil {
|
|
return nil, errors.New("no data directory set")
|
|
}
|
|
return mc.dirs.DataDir, nil
|
|
}
|
|
|
|
// RuntimeDir is simple helper function to obtain the runtime dir
|
|
func (mc *MachineConfig) RuntimeDir() (*define.VMFile, error) {
|
|
if mc.dirs == nil || mc.dirs.RuntimeDir == nil {
|
|
return nil, errors.New("no runtime directory set")
|
|
}
|
|
return mc.dirs.RuntimeDir, nil
|
|
}
|
|
|
|
func (mc *MachineConfig) SetDirs(dirs *define.MachineDirs) {
|
|
mc.dirs = dirs
|
|
}
|
|
|
|
func (mc *MachineConfig) IgnitionFile() (*define.VMFile, error) {
|
|
configDir, err := mc.ConfigDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return configDir.AppendToNewVMFile(mc.Name+".ign", nil)
|
|
}
|
|
|
|
func (mc *MachineConfig) ReadySocket() (*define.VMFile, error) {
|
|
rtDir, err := mc.RuntimeDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return readySocket(mc.Name, rtDir)
|
|
}
|
|
|
|
func (mc *MachineConfig) GVProxySocket() (*define.VMFile, error) {
|
|
machineRuntimeDir, err := mc.RuntimeDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return gvProxySocket(mc.Name, machineRuntimeDir)
|
|
}
|
|
|
|
func (mc *MachineConfig) LogFile() (*define.VMFile, error) {
|
|
rtDir, err := mc.RuntimeDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rtDir.AppendToNewVMFile(mc.Name+".log", nil)
|
|
}
|
|
|
|
func (mc *MachineConfig) Kind() (define.VMType, error) {
|
|
// Not super in love with this approach
|
|
if mc.QEMUHypervisor != nil {
|
|
return define.QemuVirt, nil
|
|
}
|
|
if mc.AppleHypervisor != nil {
|
|
return define.AppleHvVirt, nil
|
|
}
|
|
if mc.HyperVHypervisor != nil {
|
|
return define.HyperVVirt, nil
|
|
}
|
|
if mc.WSLHypervisor != nil {
|
|
return define.WSLVirt, nil
|
|
}
|
|
|
|
return define.UnknownVirt, nil
|
|
}
|
|
|
|
func (mc *MachineConfig) IsFirstBoot() (bool, error) {
|
|
never, err := time.Parse(time.RFC3339, "0001-01-01T00:00:00Z")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return mc.LastUp == never, nil
|
|
}
|
|
|
|
func (mc *MachineConfig) ConnectionInfo(vmtype define.VMType) (*define.VMFile, *define.VMFile, error) {
|
|
var (
|
|
socket *define.VMFile
|
|
pipe *define.VMFile
|
|
)
|
|
|
|
if vmtype == define.HyperVVirt || vmtype == define.WSLVirt {
|
|
pipeName := mc.Name
|
|
if !strings.HasPrefix(pipeName, "podman") {
|
|
pipeName = "podman-" + pipeName
|
|
}
|
|
pipe = &define.VMFile{Path: `\\.\pipe\` + pipeName}
|
|
}
|
|
|
|
if vmtype == define.WSLVirt {
|
|
return nil, pipe, nil
|
|
}
|
|
|
|
sockName := "podman.sock"
|
|
dataDir, err := mc.DataDir()
|
|
if err != nil {
|
|
logrus.Errorf("Resolving data dir: %s", err.Error())
|
|
return nil, nil, err
|
|
}
|
|
|
|
socket, err = define.NewMachineFile(filepath.Join(dataDir.Path, sockName), &sockName)
|
|
return socket, pipe, err
|
|
}
|
|
|
|
// LoadMachineByName returns a machine config based on the vm name and provider
|
|
func LoadMachineByName(name string, dirs *define.MachineDirs) (*MachineConfig, error) {
|
|
fullPath, err := dirs.ConfigDir.AppendToNewVMFile(name+".json", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mc, err := loadMachineFromFQPath(fullPath)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return nil, &define.ErrVMDoesNotExist{Name: name}
|
|
}
|
|
return nil, err
|
|
}
|
|
mc.dirs = dirs
|
|
mc.configPath = fullPath
|
|
|
|
// If we find an incompatible configuration, we return a hard
|
|
// error because the user wants to deal directly with this
|
|
// machine
|
|
if mc.Version == 0 {
|
|
return mc, &define.ErrIncompatibleMachineConfig{
|
|
Name: name,
|
|
Path: fullPath.GetPath(),
|
|
}
|
|
}
|
|
return mc, nil
|
|
}
|
|
|
|
// loadMachineFromFQPath stub function for loading a JSON configuration file and returning
|
|
// a machineconfig. this should only be called if you know what you are doing.
|
|
func loadMachineFromFQPath(path *define.VMFile) (*MachineConfig, error) {
|
|
mc := new(MachineConfig)
|
|
b, err := path.Read()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = json.Unmarshal(b, mc); err != nil {
|
|
return nil, fmt.Errorf("unable to load machine config file: %q", err)
|
|
}
|
|
lock, err := lock.GetMachineLock(mc.Name, filepath.Dir(path.GetPath()))
|
|
mc.lock = lock
|
|
return mc, err
|
|
}
|
|
|
|
// LoadMachinesInDir returns all the machineconfigs located in given dir
|
|
func LoadMachinesInDir(dirs *define.MachineDirs) (map[string]*MachineConfig, error) {
|
|
mcs := make(map[string]*MachineConfig)
|
|
if err := filepath.WalkDir(dirs.ConfigDir.GetPath(), func(path string, d fs.DirEntry, err error) error {
|
|
if strings.HasSuffix(d.Name(), ".json") {
|
|
fullPath, err := dirs.ConfigDir.AppendToNewVMFile(d.Name(), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mc, err := loadMachineFromFQPath(fullPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// if we find an incompatible machine configuration file, we emit and error
|
|
//
|
|
if mc.Version == 0 {
|
|
tmpErr := &define.ErrIncompatibleMachineConfig{
|
|
Name: mc.Name,
|
|
Path: fullPath.GetPath(),
|
|
}
|
|
logrus.Error(tmpErr)
|
|
return nil
|
|
}
|
|
mc.configPath = fullPath
|
|
mc.dirs = dirs
|
|
mcs[mc.Name] = mc
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return mcs, nil
|
|
}
|