github.com/containers/podman/v4@v4.9.4/pkg/machine/config.go (about) 1 //go:build amd64 || arm64 2 // +build amd64 arm64 3 4 package machine 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "net" 11 "net/http" 12 "net/url" 13 "os" 14 "path/filepath" 15 "strings" 16 "time" 17 18 "github.com/containers/podman/v4/pkg/machine/compression" 19 "github.com/containers/podman/v4/pkg/machine/define" 20 "github.com/containers/storage/pkg/homedir" 21 "github.com/containers/storage/pkg/lockfile" 22 "github.com/sirupsen/logrus" 23 ) 24 25 type InitOptions struct { 26 CPUS uint64 27 DiskSize uint64 28 IgnitionPath string 29 ImagePath string 30 Volumes []string 31 VolumeDriver string 32 IsDefault bool 33 Memory uint64 34 Name string 35 TimeZone string 36 URI url.URL 37 Username string 38 ReExec bool 39 Rootful bool 40 UID string // uid of the user that called machine 41 UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable 42 USBs []string 43 } 44 45 type Status = string 46 47 const ( 48 // Running indicates the qemu vm is running. 49 Running Status = "running" 50 // Stopped indicates the vm has stopped. 51 Stopped Status = "stopped" 52 // Starting indicated the vm is in the process of starting 53 Starting Status = "starting" 54 // Unknown means the state is not known 55 Unknown Status = "unknown" 56 DefaultMachineName string = "podman-machine-default" 57 apiUpTimeout = 20 * time.Second 58 ) 59 60 type RemoteConnectionType string 61 62 var ( 63 SSHRemoteConnection RemoteConnectionType = "ssh" 64 DefaultIgnitionUserName = "core" 65 ForwarderBinaryName = "gvproxy" 66 ) 67 68 type Download struct { 69 Arch string 70 Artifact define.Artifact 71 CacheDir string 72 CompressionType compression.ImageCompression 73 DataDir string 74 Format define.ImageFormat 75 ImageName string 76 LocalPath string 77 LocalUncompressedFile string 78 Sha256sum string 79 Size int64 80 URL *url.URL 81 VMKind VMType 82 VMName string 83 } 84 85 type ListOptions struct{} 86 87 type ListResponse struct { 88 Name string 89 CreatedAt time.Time 90 LastUp time.Time 91 Running bool 92 Starting bool 93 Stream string 94 VMType string 95 CPUs uint64 96 Memory uint64 97 DiskSize uint64 98 Port int 99 RemoteUsername string 100 IdentityPath string 101 UserModeNetworking bool 102 } 103 104 type SetOptions struct { 105 CPUs *uint64 106 DiskSize *uint64 107 Memory *uint64 108 Rootful *bool 109 UserModeNetworking *bool 110 USBs *[]string 111 } 112 113 type SSHOptions struct { 114 Username string 115 Args []string 116 } 117 118 type StartOptions struct { 119 NoInfo bool 120 Quiet bool 121 } 122 123 type StopOptions struct{} 124 125 type RemoveOptions struct { 126 Force bool 127 SaveKeys bool 128 SaveImage bool 129 SaveIgnition bool 130 } 131 132 type InspectOptions struct{} 133 134 type VM interface { 135 Init(opts InitOptions) (bool, error) 136 Inspect() (*InspectInfo, error) 137 Remove(name string, opts RemoveOptions) (string, func() error, error) 138 Set(name string, opts SetOptions) ([]error, error) 139 SSH(name string, opts SSHOptions) error 140 Start(name string, opts StartOptions) error 141 State(bypass bool) (Status, error) 142 Stop(name string, opts StopOptions) error 143 } 144 145 func GetLock(name string, vmtype VMType) (*lockfile.LockFile, error) { 146 // FIXME: there's a painful amount of `GetConfDir` calls scattered 147 // across the code base. This should be done once and stored 148 // somewhere instead. 149 vmConfigDir, err := GetConfDir(vmtype) 150 if err != nil { 151 return nil, err 152 } 153 154 lockPath := filepath.Join(vmConfigDir, name+".lock") 155 lock, err := lockfile.GetLockFile(lockPath) 156 if err != nil { 157 return nil, fmt.Errorf("creating lockfile for VM: %w", err) 158 } 159 160 return lock, nil 161 } 162 163 type DistributionDownload interface { 164 HasUsableCache() (bool, error) 165 Get() *Download 166 CleanCache() error 167 } 168 type InspectInfo struct { 169 ConfigPath define.VMFile 170 ConnectionInfo ConnectionConfig 171 Created time.Time 172 Image ImageConfig 173 LastUp time.Time 174 Name string 175 Resources ResourceConfig 176 SSHConfig SSHConfig 177 State Status 178 UserModeNetworking bool 179 Rootful bool 180 } 181 182 func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL { 183 // TODO Should this function have input verification? 184 userInfo := url.User(userName) 185 uri := url.URL{ 186 Scheme: "ssh", 187 Opaque: "", 188 User: userInfo, 189 Host: host, 190 Path: path, 191 RawPath: "", 192 ForceQuery: false, 193 RawQuery: "", 194 Fragment: "", 195 } 196 if len(port) > 0 { 197 uri.Host = net.JoinHostPort(uri.Hostname(), port) 198 } 199 return uri 200 } 201 202 // GetCacheDir returns the dir where VM images are downloaded into when pulled 203 func GetCacheDir(vmType VMType) (string, error) { 204 dataDir, err := GetDataDir(vmType) 205 if err != nil { 206 return "", err 207 } 208 cacheDir := filepath.Join(dataDir, "cache") 209 if _, err := os.Stat(cacheDir); !errors.Is(err, os.ErrNotExist) { 210 return cacheDir, nil 211 } 212 return cacheDir, os.MkdirAll(cacheDir, 0755) 213 } 214 215 // GetDataDir returns the filepath where vm images should 216 // live for podman-machine. 217 func GetDataDir(vmType VMType) (string, error) { 218 dataDirPrefix, err := DataDirPrefix() 219 if err != nil { 220 return "", err 221 } 222 dataDir := filepath.Join(dataDirPrefix, vmType.String()) 223 if _, err := os.Stat(dataDir); !errors.Is(err, os.ErrNotExist) { 224 return dataDir, nil 225 } 226 mkdirErr := os.MkdirAll(dataDir, 0755) 227 return dataDir, mkdirErr 228 } 229 230 // GetGLobalDataDir returns the root of all backends 231 // for shared machine data. 232 func GetGlobalDataDir() (string, error) { 233 dataDir, err := DataDirPrefix() 234 if err != nil { 235 return "", err 236 } 237 238 return dataDir, os.MkdirAll(dataDir, 0755) 239 } 240 241 // DataDirPrefix returns the path prefix for all machine data files 242 func DataDirPrefix() (string, error) { 243 data, err := homedir.GetDataHome() 244 if err != nil { 245 return "", err 246 } 247 dataDir := filepath.Join(data, "containers", "podman", "machine") 248 return dataDir, nil 249 } 250 251 // GetConfigDir returns the filepath to where configuration 252 // files for podman-machine should live 253 func GetConfDir(vmType VMType) (string, error) { 254 confDirPrefix, err := ConfDirPrefix() 255 if err != nil { 256 return "", err 257 } 258 confDir := filepath.Join(confDirPrefix, vmType.String()) 259 if _, err := os.Stat(confDir); !errors.Is(err, os.ErrNotExist) { 260 return confDir, nil 261 } 262 mkdirErr := os.MkdirAll(confDir, 0755) 263 return confDir, mkdirErr 264 } 265 266 // ConfDirPrefix returns the path prefix for all machine config files 267 func ConfDirPrefix() (string, error) { 268 conf, err := homedir.GetConfigHome() 269 if err != nil { 270 return "", err 271 } 272 confDir := filepath.Join(conf, "containers", "podman", "machine") 273 return confDir, nil 274 } 275 276 type USBConfig struct { 277 Bus string 278 DevNumber string 279 Vendor int 280 Product int 281 } 282 283 // ResourceConfig describes physical attributes of the machine 284 type ResourceConfig struct { 285 // CPUs to be assigned to the VM 286 CPUs uint64 287 // Disk size in gigabytes assigned to the vm 288 DiskSize uint64 289 // Memory in megabytes assigned to the vm 290 Memory uint64 291 // Usbs 292 USBs []USBConfig 293 } 294 295 type Mount struct { 296 ReadOnly bool 297 Source string 298 Tag string 299 Target string 300 Type string 301 } 302 303 // ImageConfig describes the bootable image for the VM 304 type ImageConfig struct { 305 // IgnitionFile is the path to the filesystem where the 306 // ignition file was written (if needs one) 307 IgnitionFile define.VMFile `json:"IgnitionFilePath"` 308 // ImageStream is the update stream for the image 309 ImageStream string 310 // ImageFile is the fq path to 311 ImagePath define.VMFile `json:"ImagePath"` 312 } 313 314 // HostUser describes the host user 315 type HostUser struct { 316 // Whether this machine should run in a rootful or rootless manner 317 Rootful bool 318 // UID is the numerical id of the user that called machine 319 UID int 320 // Whether one of these fields has changed and actions should be taken 321 Modified bool `json:"HostUserModified"` 322 } 323 324 // SSHConfig contains remote access information for SSH 325 type SSHConfig struct { 326 // IdentityPath is the fq path to the ssh priv key 327 IdentityPath string 328 // SSH port for user networking 329 Port int 330 // RemoteUsername of the vm user 331 RemoteUsername string 332 } 333 334 // ConnectionConfig contains connections like sockets, etc. 335 type ConnectionConfig struct { 336 // PodmanSocket is the exported podman service socket 337 PodmanSocket *define.VMFile `json:"PodmanSocket"` 338 // PodmanPipe is the exported podman service named pipe (Windows hosts only) 339 PodmanPipe *define.VMFile `json:"PodmanPipe"` 340 } 341 342 type VMType int64 343 344 const ( 345 QemuVirt VMType = iota 346 WSLVirt 347 AppleHvVirt 348 HyperVVirt 349 UnknownVirt 350 ) 351 352 func (v VMType) String() string { 353 switch v { 354 case WSLVirt: 355 return "wsl" 356 case AppleHvVirt: 357 return "applehv" 358 case HyperVVirt: 359 return "hyperv" 360 } 361 return "qemu" 362 } 363 364 type APIForwardingState int 365 366 const ( 367 NoForwarding APIForwardingState = iota 368 ClaimUnsupported 369 NotInstalled 370 MachineLocal 371 DockerGlobal 372 ) 373 374 func ParseVMType(input string, emptyFallback VMType) (VMType, error) { 375 switch strings.TrimSpace(strings.ToLower(input)) { 376 case "qemu": 377 return QemuVirt, nil 378 case "wsl": 379 return WSLVirt, nil 380 case "applehv": 381 return AppleHvVirt, nil 382 case "hyperv": 383 return HyperVVirt, nil 384 case "": 385 return emptyFallback, nil 386 default: 387 return UnknownVirt, fmt.Errorf("unknown VMType `%s`", input) 388 } 389 } 390 391 type VirtProvider interface { //nolint:interfacebloat 392 Artifact() define.Artifact 393 CheckExclusiveActiveVM() (bool, string, error) 394 Compression() compression.ImageCompression 395 Format() define.ImageFormat 396 IsValidVMName(name string) (bool, error) 397 List(opts ListOptions) ([]*ListResponse, error) 398 LoadVMByName(name string) (VM, error) 399 NewMachine(opts InitOptions) (VM, error) 400 NewDownload(vmName string) (Download, error) 401 RemoveAndCleanMachines() error 402 VMType() VMType 403 } 404 405 type Virtualization struct { 406 artifact define.Artifact 407 compression compression.ImageCompression 408 format define.ImageFormat 409 vmKind VMType 410 } 411 412 func (p *Virtualization) Artifact() define.Artifact { 413 return p.artifact 414 } 415 416 func (p *Virtualization) Compression() compression.ImageCompression { 417 return p.compression 418 } 419 420 func (p *Virtualization) Format() define.ImageFormat { 421 return p.format 422 } 423 424 func (p *Virtualization) VMType() VMType { 425 return p.vmKind 426 } 427 428 func (p *Virtualization) NewDownload(vmName string) (Download, error) { 429 cacheDir, err := GetCacheDir(p.VMType()) 430 if err != nil { 431 return Download{}, err 432 } 433 434 dataDir, err := GetDataDir(p.VMType()) 435 if err != nil { 436 return Download{}, err 437 } 438 439 return Download{ 440 Artifact: p.Artifact(), 441 CacheDir: cacheDir, 442 CompressionType: p.Compression(), 443 DataDir: dataDir, 444 Format: p.Format(), 445 VMKind: p.VMType(), 446 VMName: vmName, 447 }, nil 448 } 449 450 func NewVirtualization(artifact define.Artifact, compression compression.ImageCompression, format define.ImageFormat, vmKind VMType) Virtualization { 451 return Virtualization{ 452 artifact, 453 compression, 454 format, 455 vmKind, 456 } 457 } 458 459 func WaitAndPingAPI(sock string) { 460 client := http.Client{ 461 Transport: &http.Transport{ 462 DialContext: func(context.Context, string, string) (net.Conn, error) { 463 con, err := net.DialTimeout("unix", sock, apiUpTimeout) 464 if err != nil { 465 return nil, err 466 } 467 if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil { 468 return nil, err 469 } 470 return con, nil 471 }, 472 }, 473 } 474 475 resp, err := client.Get("http://host/_ping") 476 if err == nil { 477 defer resp.Body.Close() 478 } 479 if err != nil || resp.StatusCode != 200 { 480 logrus.Warn("API socket failed ping test") 481 } 482 } 483 484 func (dl Download) NewFcosDownloader(imageStream FCOSStream) (DistributionDownload, error) { 485 info, err := dl.GetFCOSDownload(imageStream) 486 if err != nil { 487 return nil, err 488 } 489 urlSplit := strings.Split(info.Location, "/") 490 dl.ImageName = urlSplit[len(urlSplit)-1] 491 downloadURL, err := url.Parse(info.Location) 492 if err != nil { 493 return nil, err 494 } 495 496 // Complete the download struct 497 dl.Arch = GetFcosArch() 498 // This could be eliminated as a struct and be a generated() 499 dl.LocalPath = filepath.Join(dl.CacheDir, dl.ImageName) 500 dl.Sha256sum = info.Sha256Sum 501 dl.URL = downloadURL 502 fcd := FcosDownload{ 503 Download: dl, 504 } 505 dataDir, err := GetDataDir(dl.VMKind) 506 if err != nil { 507 return nil, err 508 } 509 fcd.Download.LocalUncompressedFile = fcd.GetLocalUncompressedFile(dataDir) 510 return fcd, nil 511 } 512 513 // AcquireVMImage determines if the image is already in a FCOS stream. If so, 514 // retrieves the image path of the uncompressed file. Otherwise, the user has 515 // provided an alternative image, so we set the image path and download the image. 516 func (dl Download) AcquireVMImage(imagePath string) (*define.VMFile, FCOSStream, error) { 517 var ( 518 err error 519 imageLocation *define.VMFile 520 fcosStream FCOSStream 521 ) 522 523 switch imagePath { 524 // TODO these need to be re-typed as FCOSStreams 525 case Testing.String(), Next.String(), Stable.String(), "": 526 // Get image as usual 527 fcosStream, err = FCOSStreamFromString(imagePath) 528 if err != nil { 529 return nil, 0, err 530 } 531 532 dd, err := dl.NewFcosDownloader(fcosStream) 533 if err != nil { 534 return nil, 0, err 535 } 536 537 imageLocation, err = define.NewMachineFile(dd.Get().LocalUncompressedFile, nil) 538 if err != nil { 539 return nil, 0, err 540 } 541 542 if err := DownloadImage(dd); err != nil { 543 return nil, 0, err 544 } 545 default: 546 // The user has provided an alternate image which can be a file path 547 // or URL. 548 fcosStream = CustomStream 549 imgPath, err := dl.AcquireAlternateImage(imagePath) 550 if err != nil { 551 return nil, 0, err 552 } 553 imageLocation = imgPath 554 } 555 return imageLocation, fcosStream, nil 556 }