kairos-sdk/utils/rawDisk.go
Itxaka 26187e369e
Adds raw disk utils
Adds raw disk utils to convert a raw disk into a GCE or Azure image
Adds a new constants package to store constants that can be reused
across all of our projects
Expands KairosFs interface to be in line with whats used on other
projects so we can use it
2024-12-18 12:17:10 +01:00

189 lines
6.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"archive/tar"
"compress/gzip"
"encoding/binary"
"fmt"
"io"
"io/fs"
"os"
"github.com/kairos-io/kairos-sdk/constants"
"github.com/kairos-io/kairos-sdk/types"
)
// Raw2Azure converts a raw disk to a VHD disk compatible with Azure
// All VHDs on Azure must have a virtual size aligned to 1 MB (1024 × 1024 bytes)
// The Hyper-V virtual hard disk (VHDX) format isn't supported in Azure, only fixed VHD
func Raw2Azure(source string, logger types.KairosLogger) error {
logger.Logger.Info().Str("source", source).Msg("Converting raw disk to Azure VHD")
// Copy raw to new image with VHD appended
// rename file to .vhd
err := os.Rename(source, fmt.Sprintf("%s.vhd", source))
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error renaming raw image to vhd")
return err
}
// Open it
vhdFile, _ := os.OpenFile(fmt.Sprintf("%s.vhd", source), os.O_APPEND|os.O_WRONLY, constants.FilePerm)
// Calculate rounded size
info, err := vhdFile.Stat()
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error getting file info")
return err
}
actualSize := info.Size()
finalSizeBytes := ((actualSize + 1024*1024 - 1) / 1024 * 1024) * 1024 * 1024
// Don't forget to remove 512 bytes for the header that we are going to add afterwards!
finalSizeBytes = finalSizeBytes - 512
// For smaller than 1 MB images, this calculation doesn't work, so we round up to 1 MB
if finalSizeBytes == 0 {
finalSizeBytes = 1*1024*1024 - 512
}
if actualSize != finalSizeBytes {
logger.Logger.Info().Int64("actualSize", actualSize).Int64("finalSize", finalSizeBytes).Msg("Resizing image")
// If you do not seek, you will override the data
_, err = vhdFile.Seek(0, io.SeekEnd)
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error seeking to end")
return err
}
err = vhdFile.Truncate(finalSizeBytes)
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error truncating file")
return err
}
}
// Transform it to VHD
info, err = vhdFile.Stat() // Stat again to get the new size
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error getting file info")
return err
}
size := uint64(info.Size())
header := newVHDFixed(size)
err = binary.Write(vhdFile, binary.BigEndian, header)
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error writing header")
return err
}
err = vhdFile.Close()
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error closing file")
return err
}
return nil
}
// Raw2Gce transforms an image from RAW format into GCE format
// The RAW image file must have a size in an increment of 1 GB. For example, the file must be either 10 GB or 11 GB but not 10.5 GB.
// The disk image filename must be disk.raw.
// The compressed file must be a .tar.gz file that uses gzip compression and the --format=oldgnu option for the tar utility.
func Raw2Gce(source string, kairosFs types.KairosFS, logger types.KairosLogger) error {
logger.Logger.Info().Msg("Transforming raw image into gce format")
actImg, err := kairosFs.OpenFile(source, os.O_CREATE|os.O_APPEND|os.O_WRONLY, constants.FilePerm)
if err != nil {
logger.Logger.Error().Err(err).Str("file", source).Msg("Error opening file")
return err
}
info, err := actImg.Stat()
if err != nil {
logger.Logger.Error().Err(err).Str("file", source).Msg("Error getting file info")
return err
}
actualSize := info.Size()
finalSizeGB := actualSize/constants.GB + 1
finalSizeBytes := finalSizeGB * constants.GB
logger.Logger.Info().Int64("current", actualSize).Int64("final", finalSizeGB).Str("file", source).Msg("Resizing image")
// REMEMBER TO SEEK!
_, err = actImg.Seek(0, io.SeekEnd)
if err != nil {
logger.Logger.Error().Err(err).Str("file", source).Msg("Error seeking to end")
return err
}
err = actImg.Truncate(finalSizeBytes)
if err != nil {
logger.Logger.Error().Err(err).Str("file", source).Msg("Error truncating file")
return err
}
err = actImg.Close()
if err != nil {
logger.Logger.Error().Err(err).Str("file", source).Msg("Error closing file")
return err
}
// Tar gz the image
// Create destination file
file, err := kairosFs.Create(fmt.Sprintf("%s.tar.gz", source))
if err != nil {
logger.Logger.Error().Err(err).Str("destination", fmt.Sprintf("%s.tar.gz", source)).Msg("Error creating destination file")
return err
}
logger.Logger.Info().Str("destination", file.Name()).Msg("Compressing raw image into a tar.gz")
defer func(file *os.File) {
err = file.Close()
if err != nil {
logger.Logger.Error().Err(err).Str("destination", file.Name()).Msg("Error closing destination file")
}
}(file)
// Create gzip writer
gzipWriter, err := gzip.NewWriterLevel(file, gzip.BestSpeed)
if err != nil {
return err
}
defer func(gzipWriter *gzip.Writer) {
err := gzipWriter.Close()
if err != nil {
logger.Logger.Error().Err(err).Str("destination", file.Name()).Msg("Error closing gzip writer")
}
}(gzipWriter)
// Create tarwriter pointing to our gzip writer
tarWriter := tar.NewWriter(gzipWriter)
defer func(tarWriter *tar.Writer) {
err = tarWriter.Close()
if err != nil {
logger.Logger.Error().Err(err).Str("destination", file.Name()).Msg("Error closing tar writer")
}
}(tarWriter)
// Open disk.raw
sourceFile, _ := kairosFs.Open(source)
sourceStat, _ := sourceFile.Stat()
defer func(sourceFile fs.File) {
err = sourceFile.Close()
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error closing source file")
}
}(sourceFile)
// Add disk.raw file
header := &tar.Header{
Name: sourceStat.Name(),
Size: sourceStat.Size(),
Mode: int64(sourceStat.Mode()),
Format: tar.FormatGNU,
}
// Write header with all the info
err = tarWriter.WriteHeader(header)
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error writing header")
return err
}
// copy the actual data
_, err = io.Copy(tarWriter, sourceFile)
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error copying data")
return err
}
// Remove full raw image, we already got the compressed one
err = kairosFs.RemoveAll(source)
if err != nil {
logger.Logger.Error().Err(err).Str("source", source).Msg("Error removing full raw image")
return err
}
return nil
}