Files
podman/pkg/machine/vmconfigs/machine.go
Kir Kolyshkin a776c1d82a pkg/machine/vmconfigs: simplify IsFirstBoot
This is faster and, to my best knowledge, is equivalent to the old code.

Remove the error return (as we don't guarantee stable API here), and
simplify callers.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-03-28 17:55:53 -07:00

393 lines
10 KiB
Go

package vmconfigs
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/common/pkg/strongunits"
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/pkg/machine/ports"
"github.com/containers/storage/pkg/fileutils"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile"
"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, machineLock *lockfile.LockFile) (*MachineConfig, error) {
mc := new(MachineConfig)
mc.Name = opts.Name
mc.dirs = dirs
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
// Given that we are locked now and check again that the config file does not exists,
// if it does it means the VM was already created and we should error.
if err := fileutils.Exists(cf.Path); err == nil {
return nil, fmt.Errorf("%s: %w", opts.Name, define.ErrVMAlreadyExists)
}
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: strongunits.GiB(opts.DiskSize),
Memory: strongunits.MiB(opts.Memory),
USBs: usbs,
}
mc.Resources = mrc
sshPort, err := ports.AllocateMachinePort()
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()
}
// 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 ioutils.AtomicWriteFile(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(machines map[string]bool, 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
}
gvProxySocket, err := mc.GVProxySocket()
if err != nil {
return nil, nil, err
}
apiSocket, err := mc.APISocket()
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(),
gvProxySocket.GetPath(),
apiSocket.GetPath(),
logPath.GetPath(),
}
if !saveImage {
mc.ImagePath.GetPath()
}
if !saveIgnition {
ignitionFile.GetPath()
}
mcRemove := func() error {
var errs []error
if err := connection.RemoveConnections(machines, 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 := gvProxySocket.Delete(); err != nil {
errs = append(errs, err)
}
if err := apiSocket.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)
}
if err := ports.ReleaseMachinePort(mc.SSH.Port); 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) APISocket() (*define.VMFile, error) {
machineRuntimeDir, err := mc.RuntimeDir()
if err != nil {
return nil, err
}
return apiSocket(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) IsFirstBoot() bool {
return mc.LastUp.IsZero()
}
func (mc *MachineConfig) ConnectionInfo(vmtype define.VMType) (*define.VMFile, *define.VMFile, error) {
socket, err := mc.APISocket()
return socket, getPipe(mc.Name), 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
}