2023-08-29 20:56:21 +01:00
|
|
|
package storage
|
|
|
|
|
|
|
|
|
|
import (
|
2023-08-30 13:13:08 +01:00
|
|
|
"compress/bzip2"
|
2023-08-29 20:56:21 +01:00
|
|
|
"fmt"
|
2023-08-30 13:13:08 +01:00
|
|
|
"io"
|
2023-08-29 20:56:21 +01:00
|
|
|
"log/slog"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2023-08-30 13:13:08 +01:00
|
|
|
"runtime"
|
2023-08-29 20:56:21 +01:00
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
"github.com/AlexSSD7/linsk/constants"
|
|
|
|
|
"github.com/AlexSSD7/linsk/imgbuilder"
|
2023-08-29 20:56:21 +01:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Storage struct {
|
|
|
|
|
logger *slog.Logger
|
|
|
|
|
|
|
|
|
|
path string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewStorage(logger *slog.Logger, dataDir string) (*Storage, error) {
|
|
|
|
|
dataDir = filepath.Clean(dataDir)
|
|
|
|
|
|
|
|
|
|
err := os.MkdirAll(dataDir, 0700)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("mkdir all data dir")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &Storage{
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
|
|
|
|
path: dataDir,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
func (s *Storage) CheckDownloadBaseImage() (string, error) {
|
|
|
|
|
baseImagePath := filepath.Join(s.path, constants.GetAlpineBaseImageFileName())
|
|
|
|
|
_, err := os.Stat(baseImagePath)
|
2023-08-30 09:43:41 +01:00
|
|
|
if err != nil {
|
2023-08-30 12:39:38 +01:00
|
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
|
|
|
return "", errors.Wrap(err, "stat base image path")
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
// Image doesn't exist. Download one.
|
2023-08-30 13:13:08 +01:00
|
|
|
err := s.download(constants.GetAlpineBaseImageURL(), constants.GetAlpineBaseImageHash(), baseImagePath, nil)
|
2023-08-29 20:56:21 +01:00
|
|
|
if err != nil {
|
2023-08-30 12:39:38 +01:00
|
|
|
return "", errors.Wrap(err, "download base alpine image")
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
return baseImagePath, nil
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
// Image exists. Ensure that the hash is correct.
|
|
|
|
|
err = validateFileHash(baseImagePath, constants.GetAlpineBaseImageHash())
|
2023-08-29 20:56:21 +01:00
|
|
|
if err != nil {
|
2023-08-30 12:39:38 +01:00
|
|
|
return "", errors.Wrap(err, "validate hash of existing image")
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
return baseImagePath, nil
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
func (s *Storage) GetVMImagePath() string {
|
|
|
|
|
return filepath.Join(s.path, constants.GetVMImageTags()+".qcow2")
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 13:13:08 +01:00
|
|
|
func (s *Storage) GetAarch64EFIImagePath() string {
|
|
|
|
|
return filepath.Join(s.path, constants.GetAarch64EFIImageName())
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
func (s *Storage) BuildVMImageWithInterruptHandler(showBuilderVMDisplay bool, overwrite bool) error {
|
|
|
|
|
vmImagePath := s.GetVMImagePath()
|
2023-08-30 13:13:08 +01:00
|
|
|
removed, err := checkExistsOrRemove(vmImagePath, overwrite)
|
2023-08-30 09:43:41 +01:00
|
|
|
if err != nil {
|
2023-08-30 13:13:08 +01:00
|
|
|
return errors.Wrap(err, "check exists or remove")
|
2023-08-30 09:19:02 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
baseImagePath, err := s.CheckDownloadBaseImage()
|
2023-08-30 09:43:41 +01:00
|
|
|
if err != nil {
|
2023-08-30 13:13:08 +01:00
|
|
|
return errors.Wrap(err, "check/download base image")
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-30 13:32:00 +01:00
|
|
|
biosPath, err := s.CheckDownloadVMBIOS()
|
2023-08-30 13:13:08 +01:00
|
|
|
if err != nil {
|
2023-08-30 13:32:00 +01:00
|
|
|
return errors.Wrap(err, "check/download vm bios")
|
2023-08-30 09:19:02 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 13:13:08 +01:00
|
|
|
s.logger.Info("Building VM image", "tags", constants.GetAlpineBaseImageTags(), "overwriting", removed, "dst", vmImagePath)
|
2023-08-30 09:19:02 +01:00
|
|
|
|
2023-08-30 13:13:08 +01:00
|
|
|
buildCtx, err := imgbuilder.NewBuildContext(s.logger.With("subcaller", "imgbuilder"), baseImagePath, vmImagePath, showBuilderVMDisplay, biosPath)
|
2023-08-30 12:39:38 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "create new img build context")
|
|
|
|
|
}
|
2023-08-29 20:56:21 +01:00
|
|
|
|
2023-08-30 15:24:25 +01:00
|
|
|
err = buildCtx.BuildWithInterruptHandler()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "do build")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = os.Remove(baseImagePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("Failed to remove base image", "error", err.Error(), "path", baseImagePath)
|
|
|
|
|
} else {
|
|
|
|
|
s.logger.Info("Removed base image", "path", baseImagePath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2023-08-30 12:39:38 +01:00
|
|
|
}
|
2023-08-29 20:56:21 +01:00
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
func (s *Storage) CheckVMImageExists() (string, error) {
|
|
|
|
|
p := s.GetVMImagePath()
|
|
|
|
|
_, err := os.Stat(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
|
|
|
return "", errors.Wrap(err, "stat vm image path")
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
// Image doesn't exist.
|
|
|
|
|
return "", nil
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 12:39:38 +01:00
|
|
|
// Image exists. Returning the full path.
|
|
|
|
|
return p, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Storage) DataDirPath() string {
|
|
|
|
|
return s.path
|
2023-08-29 20:56:21 +01:00
|
|
|
}
|
2023-08-30 13:13:08 +01:00
|
|
|
|
2023-08-30 13:32:00 +01:00
|
|
|
func (s *Storage) CheckDownloadVMBIOS() (string, error) {
|
2023-08-30 13:13:08 +01:00
|
|
|
if runtime.GOARCH == "arm64" {
|
|
|
|
|
p, err := s.CheckDownloadAarch64EFIImage()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "check/download aarch64 efi image")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return p, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-30 13:32:00 +01:00
|
|
|
// On x86_64, there is no requirement to supply QEMU with any BIOS images.
|
|
|
|
|
|
2023-08-30 13:13:08 +01:00
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Storage) CheckDownloadAarch64EFIImage() (string, error) {
|
|
|
|
|
efiImagePath := s.GetAarch64EFIImagePath()
|
|
|
|
|
_, err := os.Stat(efiImagePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
|
|
|
return "", errors.Wrap(err, "stat base image path")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EFI image doesn't exist. Download one.
|
|
|
|
|
err := s.download(constants.GetAarch64EFIImageBZ2URL(), constants.GetAarch64EFIImageHash(), efiImagePath, func(r io.Reader) io.Reader {
|
|
|
|
|
return bzip2.NewReader(r)
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "download base alpine image")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return efiImagePath, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EFI image exists. Ensure that the hash is correct.
|
|
|
|
|
err = validateFileHash(efiImagePath, constants.GetAarch64EFIImageHash())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "validate hash of existing image")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return efiImagePath, nil
|
|
|
|
|
}
|