mirror of
https://github.com/kairos-io/kairos-sdk.git
synced 2025-08-01 23:39:08 +00:00
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
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
|
||
}
|