Files
podman/pkg/machine/config.go
Brent Baude 10d748f584 Introduce Podman machine reset
Podman machine reset is a new command that will "reset" your podman
machine environment.  Reset is defined as:

* Stop and Remove all VMs
* Remove the following directories:
    - configuration dir i.e. ~/.config/containers/podman/machine/qemu
    - data dir i.e. ~/.local/.share/containers/podman/machine/qemu

When deleting, if errors are encountered, they will be batched and spit
out at the end.  Podman will try to proceed even in error in doing what
it was told.

Signed-off-by: Brent Baude <bbaude@redhat.com>
2024-02-18 08:43:12 -06:00

416 lines
9.7 KiB
Go

//go:build amd64 || arm64
package machine
import (
"context"
"errors"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/podman/v5/pkg/machine/compression"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
"github.com/containers/storage/pkg/homedir"
"github.com/sirupsen/logrus"
)
const (
DefaultMachineName string = "podman-machine-default"
apiUpTimeout = 20 * time.Second
)
var (
ForwarderBinaryName = "gvproxy"
)
type Download struct {
Arch string
Artifact define.Artifact
CacheDir string
CompressionType compression.ImageCompression
DataDir string
Format define.ImageFormat
ImageName string
LocalPath string
LocalUncompressedFile string
Sha256sum string
Size int64
URL *url.URL
VMKind define.VMType
VMName string
}
type ListOptions struct{}
type ListResponse struct {
Name string
CreatedAt time.Time
LastUp time.Time
Running bool
Starting bool
Stream string
VMType string
CPUs uint64
Memory uint64
DiskSize uint64
Port int
RemoteUsername string
IdentityPath string
UserModeNetworking bool
}
type SSHOptions struct {
Username string
Args []string
}
type StartOptions struct {
NoInfo bool
Quiet bool
}
type StopOptions struct{}
type RemoveOptions struct {
Force bool
SaveImage bool
SaveIgnition bool
}
type ResetOptions struct {
Force bool
}
type InspectOptions struct{}
// TODO This can be removed when WSL is refactored into podman 5
type VM interface {
Init(opts define.InitOptions) (bool, error)
Inspect() (*InspectInfo, error)
Remove(name string, opts RemoveOptions) (string, func() error, error)
Set(name string, opts define.SetOptions) ([]error, error)
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error
State(bypass bool) (define.Status, error)
Stop(name string, opts StopOptions) error
}
type DistributionDownload interface {
HasUsableCache() (bool, error)
Get() *Download
CleanCache() error
}
type InspectInfo struct {
ConfigPath define.VMFile
ConnectionInfo ConnectionConfig
Created time.Time
Image ImageConfig
LastUp time.Time
Name string
Resources vmconfigs.ResourceConfig
SSHConfig vmconfigs.SSHConfig
State define.Status
UserModeNetworking bool
Rootful bool
}
// GetCacheDir returns the dir where VM images are downloaded into when pulled
func GetCacheDir(vmType define.VMType) (string, error) {
dataDir, err := GetDataDir(vmType)
if err != nil {
return "", err
}
cacheDir := filepath.Join(dataDir, "cache")
if _, err := os.Stat(cacheDir); !errors.Is(err, os.ErrNotExist) {
return cacheDir, nil
}
return cacheDir, os.MkdirAll(cacheDir, 0755)
}
// GetDataDir returns the filepath where vm images should
// live for podman-machine.
func GetDataDir(vmType define.VMType) (string, error) {
dataDirPrefix, err := DataDirPrefix()
if err != nil {
return "", err
}
dataDir := filepath.Join(dataDirPrefix, vmType.String())
if _, err := os.Stat(dataDir); !errors.Is(err, os.ErrNotExist) {
return dataDir, nil
}
mkdirErr := os.MkdirAll(dataDir, 0755)
return dataDir, mkdirErr
}
// GetGlobalDataDir returns the root of all backends
// for shared machine data.
func GetGlobalDataDir() (string, error) {
dataDir, err := DataDirPrefix()
if err != nil {
return "", err
}
return dataDir, os.MkdirAll(dataDir, 0755)
}
func GetMachineDirs(vmType define.VMType) (*define.MachineDirs, error) {
rtDir, err := getRuntimeDir()
if err != nil {
return nil, err
}
rtDir = filepath.Join(rtDir, "podman")
configDir, err := GetConfDir(vmType)
if err != nil {
return nil, err
}
configDirFile, err := define.NewMachineFile(configDir, nil)
if err != nil {
return nil, err
}
dataDir, err := GetDataDir(vmType)
if err != nil {
return nil, err
}
dataDirFile, err := define.NewMachineFile(dataDir, nil)
if err != nil {
return nil, err
}
imageCacheDir, err := dataDirFile.AppendToNewVMFile("cache", nil)
if err != nil {
return nil, err
}
rtDirFile, err := define.NewMachineFile(rtDir, nil)
if err != nil {
return nil, err
}
dirs := define.MachineDirs{
ConfigDir: configDirFile,
DataDir: dataDirFile,
ImageCacheDir: imageCacheDir,
RuntimeDir: rtDirFile,
}
// make sure all machine dirs are present
if err := os.MkdirAll(rtDir, 0755); err != nil {
return nil, err
}
if err := os.MkdirAll(configDir, 0755); err != nil {
return nil, err
}
// Because this is a mkdirall, we make the image cache dir
// which is a subdir of datadir (so the datadir is made anyway)
err = os.MkdirAll(imageCacheDir.GetPath(), 0755)
return &dirs, err
}
// DataDirPrefix returns the path prefix for all machine data files
func DataDirPrefix() (string, error) {
data, err := homedir.GetDataHome()
if err != nil {
return "", err
}
dataDir := filepath.Join(data, "containers", "podman", "machine")
return dataDir, nil
}
// GetConfigDir returns the filepath to where configuration
// files for podman-machine should live
func GetConfDir(vmType define.VMType) (string, error) {
confDirPrefix, err := ConfDirPrefix()
if err != nil {
return "", err
}
confDir := filepath.Join(confDirPrefix, vmType.String())
if _, err := os.Stat(confDir); !errors.Is(err, os.ErrNotExist) {
return confDir, nil
}
mkdirErr := os.MkdirAll(confDir, 0755)
return confDir, mkdirErr
}
// ConfDirPrefix returns the path prefix for all machine config files
func ConfDirPrefix() (string, error) {
conf, err := homedir.GetConfigHome()
if err != nil {
return "", err
}
confDir := filepath.Join(conf, "containers", "podman", "machine")
return confDir, nil
}
// GetSSHIdentityPath returns the path to the expected SSH private key
func GetSSHIdentityPath(name string) (string, error) {
datadir, err := GetGlobalDataDir()
if err != nil {
return "", err
}
return filepath.Join(datadir, name), nil
}
// ImageConfig describes the bootable image for the VM
type ImageConfig struct {
// IgnitionFile is the path to the filesystem where the
// ignition file was written (if needs one)
IgnitionFile define.VMFile `json:"IgnitionFilePath"`
// ImageStream is the update stream for the image
ImageStream string
// ImageFile is the fq path to
ImagePath define.VMFile `json:"ImagePath"`
}
// ConnectionConfig contains connections like sockets, etc.
type ConnectionConfig struct {
// PodmanSocket is the exported podman service socket
PodmanSocket *define.VMFile `json:"PodmanSocket"`
// PodmanPipe is the exported podman service named pipe (Windows hosts only)
PodmanPipe *define.VMFile `json:"PodmanPipe"`
}
type APIForwardingState int
const (
NoForwarding APIForwardingState = iota
ClaimUnsupported
NotInstalled
MachineLocal
DockerGlobal
)
// TODO THis should be able to be removed once WSL is refactored for podman5
type Virtualization struct {
artifact define.Artifact
compression compression.ImageCompression
format define.ImageFormat
vmKind define.VMType
}
func (p *Virtualization) Artifact() define.Artifact {
return p.artifact
}
func (p *Virtualization) Compression() compression.ImageCompression {
return p.compression
}
func (p *Virtualization) Format() define.ImageFormat {
return p.format
}
func (p *Virtualization) VMType() define.VMType {
return p.vmKind
}
func (p *Virtualization) NewDownload(vmName string) (Download, error) {
cacheDir, err := GetCacheDir(p.VMType())
if err != nil {
return Download{}, err
}
dataDir, err := GetDataDir(p.VMType())
if err != nil {
return Download{}, err
}
return Download{
Artifact: p.Artifact(),
CacheDir: cacheDir,
CompressionType: p.Compression(),
DataDir: dataDir,
Format: p.Format(),
VMKind: p.VMType(),
VMName: vmName,
}, nil
}
func NewVirtualization(artifact define.Artifact, compression compression.ImageCompression, format define.ImageFormat, vmKind define.VMType) Virtualization {
return Virtualization{
artifact,
compression,
format,
vmKind,
}
}
func dialSocket(socket string, timeout time.Duration) (net.Conn, error) {
scheme := "unix"
if strings.Contains(socket, "://") {
url, err := url.Parse(socket)
if err != nil {
return nil, err
}
scheme = url.Scheme
socket = url.Path
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
var dial func() (net.Conn, error)
switch scheme {
default:
fallthrough
case "unix":
dial = func() (net.Conn, error) {
var dialer net.Dialer
return dialer.DialContext(ctx, "unix", socket)
}
case "npipe":
dial = func() (net.Conn, error) {
return DialNamedPipe(ctx, socket)
}
}
backoff := 500 * time.Millisecond
for {
conn, err := dial()
if !errors.Is(err, os.ErrNotExist) {
return conn, err
}
select {
case <-time.After(backoff):
backoff *= 2
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
func WaitAndPingAPI(sock string) {
client := http.Client{
Transport: &http.Transport{
DialContext: func(context.Context, string, string) (net.Conn, error) {
con, err := dialSocket(sock, apiUpTimeout)
if err != nil {
return nil, err
}
if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil {
return nil, err
}
return con, nil
},
},
}
resp, err := client.Get("http://host/_ping")
if err == nil {
defer resp.Body.Close()
}
if err != nil || resp.StatusCode != 200 {
logrus.Warn("API socket failed ping test")
}
}