mirror of
https://github.com/kairos-io/kairos-sdk.git
synced 2025-08-31 23:01:43 +00:00
Provides raw disk utils to transform a raw disk into a GCE or Azure disk Provieds a new constants pkg to store constants that cn be used across all projects Expands the KairosFs interface to be in line with what its used across the projects Signed-off-by: Itxaka <itxaka@kairos.io>
189 lines
6.3 KiB
Go
189 lines
6.3 KiB
Go
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
|
||
}
|