Some checks failed
Tests / 🛃 Unit tests (push) Failing after 13s
Tests / 🗄 DB tests (push) Failing after 19s
Tests / 🐍 Lint python scripts (push) Failing after 1s
Tests / ⌨ Golang Lint (push) Failing after 1s
Tests / 📦 Packit config lint (push) Failing after 1s
Tests / 🔍 Check source preparation (push) Failing after 1s
Tests / 🔍 Check for valid snapshot urls (push) Failing after 1s
Tests / 🔍 Check for missing or unused runner repos (push) Failing after 1s
Tests / 🐚 Shellcheck (push) Failing after 1s
Tests / 📦 RPMlint (push) Failing after 1s
Tests / Gitlab CI trigger helper (push) Failing after 1s
Tests / 🎀 kube-linter (push) Failing after 1s
Tests / 🧹 cloud-cleaner-is-enabled (push) Successful in 3s
Tests / 🔍 Check spec file osbuild/images dependencies (push) Failing after 1s
677 lines
18 KiB
Go
677 lines
18 KiB
Go
package imageformats
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type MultiFormatGenerator struct {
|
|
logger *logrus.Logger
|
|
config *ImageConfig
|
|
formats map[string]ImageFormat
|
|
validators map[string]ImageValidator
|
|
}
|
|
|
|
type ImageConfig struct {
|
|
OutputDir string `json:"output_dir"`
|
|
BaseImage string `json:"base_image"`
|
|
Compression string `json:"compression"`
|
|
Size string `json:"size"`
|
|
Format string `json:"format"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}
|
|
|
|
type ImageFormat struct {
|
|
Name string `json:"name"`
|
|
Extension string `json:"extension"`
|
|
Description string `json:"description"`
|
|
Tools []string `json:"tools"`
|
|
Options map[string]interface{} `json:"options"`
|
|
}
|
|
|
|
type ImageValidator struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Commands []string `json:"commands"`
|
|
}
|
|
|
|
type GeneratedImage struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Format string `json:"format"`
|
|
Path string `json:"path"`
|
|
Size int64 `json:"size"`
|
|
Checksum string `json:"checksum"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
Validated bool `json:"validated"`
|
|
ValidationResults []ValidationResult `json:"validation_results"`
|
|
}
|
|
|
|
type ValidationResult struct {
|
|
Validator string `json:"validator"`
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Details map[string]interface{} `json:"details"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
func NewMultiFormatGenerator(config *ImageConfig, logger *logrus.Logger) *MultiFormatGenerator {
|
|
generator := &MultiFormatGenerator{
|
|
logger: logger,
|
|
config: config,
|
|
formats: make(map[string]ImageFormat),
|
|
validators: make(map[string]ImageValidator),
|
|
}
|
|
|
|
// Initialize supported formats
|
|
generator.initializeFormats()
|
|
|
|
// Initialize validators
|
|
generator.initializeValidators()
|
|
|
|
return generator
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) initializeFormats() {
|
|
// ISO format
|
|
mfg.formats["iso"] = ImageFormat{
|
|
Name: "iso",
|
|
Extension: ".iso",
|
|
Description: "ISO images for physical media",
|
|
Tools: []string{"genisoimage", "xorriso", "mkisofs"},
|
|
Options: map[string]interface{}{
|
|
"bootable": true,
|
|
"volume_id": "DEBIAN_ATOMIC",
|
|
"joliet": true,
|
|
"rock_ridge": true,
|
|
},
|
|
}
|
|
|
|
// QCOW2 format
|
|
mfg.formats["qcow2"] = ImageFormat{
|
|
Name: "qcow2",
|
|
Extension: ".qcow2",
|
|
Description: "QCOW2 for virtualization",
|
|
Tools: []string{"qemu-img", "virt-make-fs"},
|
|
Options: map[string]interface{}{
|
|
"compression": "zlib",
|
|
"cluster_size": 65536,
|
|
"preallocation": "metadata",
|
|
},
|
|
}
|
|
|
|
// RAW format
|
|
mfg.formats["raw"] = ImageFormat{
|
|
Name: "raw",
|
|
Extension: ".raw",
|
|
Description: "RAW images for direct disk writing",
|
|
Tools: []string{"dd", "qemu-img", "virt-make-fs"},
|
|
Options: map[string]interface{}{
|
|
"sparse": true,
|
|
"alignment": 4096,
|
|
},
|
|
}
|
|
|
|
// VMDK format
|
|
mfg.formats["vmdk"] = ImageFormat{
|
|
Name: "vmdk",
|
|
Extension: ".vmdk",
|
|
Description: "VMDK for VMware compatibility",
|
|
Tools: []string{"qemu-img", "vmdk-tool"},
|
|
Options: map[string]interface{}{
|
|
"subformat": "monolithicSparse",
|
|
"adapter_type": "lsilogic",
|
|
},
|
|
}
|
|
|
|
// TAR format
|
|
mfg.formats["tar"] = ImageFormat{
|
|
Name: "tar",
|
|
Extension: ".tar.gz",
|
|
Description: "TAR archives for deployment",
|
|
Tools: []string{"tar", "gzip"},
|
|
Options: map[string]interface{}{
|
|
"compression": "gzip",
|
|
"preserve_permissions": true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) initializeValidators() {
|
|
// ISO validator
|
|
mfg.validators["iso"] = ImageValidator{
|
|
Name: "iso_validator",
|
|
Description: "Validate ISO image structure and bootability",
|
|
Commands: []string{"file", "isoinfo", "xorriso"},
|
|
}
|
|
|
|
// QCOW2 validator
|
|
mfg.validators["qcow2"] = ImageValidator{
|
|
Name: "qcow2_validator",
|
|
Description: "Validate QCOW2 image integrity",
|
|
Commands: []string{"qemu-img", "virt-filesystems"},
|
|
}
|
|
|
|
// RAW validator
|
|
mfg.validators["raw"] = ImageValidator{
|
|
Name: "raw_validator",
|
|
Description: "Validate RAW image structure",
|
|
Commands: []string{"file", "fdisk", "parted"},
|
|
}
|
|
|
|
// VMDK validator
|
|
mfg.validators["vmdk"] = ImageValidator{
|
|
Name: "vmdk_validator",
|
|
Description: "Validate VMDK image format",
|
|
Commands: []string{"qemu-img", "vmdk-tool"},
|
|
}
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) GenerateImage(blueprint *Blueprint, format string, variant string) (*GeneratedImage, error) {
|
|
mfg.logger.Infof("Generating %s image for blueprint: %s, variant: %s", format, blueprint.Name, variant)
|
|
|
|
// Check if format is supported
|
|
imageFormat, exists := mfg.formats[format]
|
|
if !exists {
|
|
return nil, fmt.Errorf("unsupported format: %s", format)
|
|
}
|
|
|
|
// Check if required tools are available
|
|
if err := mfg.checkTools(imageFormat.Tools); err != nil {
|
|
return nil, fmt.Errorf("required tools not available: %w", err)
|
|
}
|
|
|
|
// Create image structure
|
|
image := &GeneratedImage{
|
|
ID: generateImageID(),
|
|
Name: fmt.Sprintf("%s-%s-%s", blueprint.Name, variant, format),
|
|
Format: format,
|
|
Metadata: make(map[string]interface{}),
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
// Set output path
|
|
image.Path = filepath.Join(mfg.config.OutputDir, image.Name+imageFormat.Extension)
|
|
|
|
// Generate image based on format
|
|
if err := mfg.generateFormatSpecificImage(image, blueprint, variant, imageFormat); err != nil {
|
|
return nil, fmt.Errorf("failed to generate %s image: %w", format, err)
|
|
}
|
|
|
|
// Calculate image size
|
|
if err := mfg.calculateImageSize(image); err != nil {
|
|
mfg.logger.Warnf("Failed to calculate image size: %v", err)
|
|
}
|
|
|
|
// Generate checksum
|
|
if err := mfg.generateChecksum(image); err != nil {
|
|
mfg.logger.Warnf("Failed to generate checksum: %v", err)
|
|
}
|
|
|
|
// Validate image
|
|
if err := mfg.validateImage(image); err != nil {
|
|
mfg.logger.Warnf("Image validation failed: %v", err)
|
|
} else {
|
|
image.Validated = true
|
|
}
|
|
|
|
// Set metadata
|
|
image.Metadata["blueprint"] = blueprint.Name
|
|
image.Metadata["variant"] = variant
|
|
image.Metadata["format"] = format
|
|
image.Metadata["tools_used"] = imageFormat.Tools
|
|
image.Metadata["options"] = imageFormat.Options
|
|
|
|
mfg.logger.Infof("Successfully generated image: %s", image.Path)
|
|
return image, nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) generateFormatSpecificImage(image *GeneratedImage, blueprint *Blueprint, variant string, format ImageFormat) error {
|
|
switch format.Name {
|
|
case "iso":
|
|
return mfg.generateISOImage(image, blueprint, variant, format)
|
|
case "qcow2":
|
|
return mfg.generateQCOW2Image(image, blueprint, variant, format)
|
|
case "raw":
|
|
return mfg.generateRAWImage(image, blueprint, variant, format)
|
|
case "vmdk":
|
|
return mfg.generateVMDKImage(image, blueprint, variant, format)
|
|
case "tar":
|
|
return mfg.generateTARImage(image, blueprint, variant, format)
|
|
default:
|
|
return fmt.Errorf("unsupported format: %s", format.Name)
|
|
}
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) generateISOImage(image *GeneratedImage, blueprint *Blueprint, variant string, format ImageFormat) error {
|
|
// Create temporary directory for ISO contents
|
|
tempDir, err := os.MkdirTemp("", "debian-iso-*")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temp directory: %w", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Create ISO structure
|
|
if err := mfg.createISOStructure(tempDir, blueprint, variant); err != nil {
|
|
return fmt.Errorf("failed to create ISO structure: %w", err)
|
|
}
|
|
|
|
// Generate ISO using genisoimage
|
|
cmd := exec.Command("genisoimage",
|
|
"-o", image.Path,
|
|
"-R", "-J", "-joliet-long",
|
|
"-b", "isolinux/isolinux.bin",
|
|
"-no-emul-boot", "-boot-load-size", "4",
|
|
"-boot-info-table",
|
|
"-c", "isolinux/boot.cat",
|
|
"-V", format.Options["volume_id"].(string),
|
|
tempDir)
|
|
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("genisoimage failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) generateQCOW2Image(image *GeneratedImage, blueprint *Blueprint, variant string, format ImageFormat) error {
|
|
// Create base raw image first
|
|
rawImage := &GeneratedImage{
|
|
Name: image.Name + "-raw",
|
|
Format: "raw",
|
|
Path: filepath.Join(mfg.config.OutputDir, image.Name+"-raw.img"),
|
|
}
|
|
|
|
if err := mfg.generateRAWImage(rawImage, blueprint, variant, format); err != nil {
|
|
return fmt.Errorf("failed to generate base raw image: %w", err)
|
|
}
|
|
defer os.Remove(rawImage.Path)
|
|
|
|
// Convert to QCOW2
|
|
cmd := exec.Command("qemu-img", "convert",
|
|
"-f", "raw",
|
|
"-O", "qcow2",
|
|
"-c", // Enable compression
|
|
"-o", "compression_type=zlib",
|
|
rawImage.Path, image.Path)
|
|
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("qemu-img convert failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) generateRAWImage(image *GeneratedImage, blueprint *Blueprint, variant string, format ImageFormat) error {
|
|
// Create disk image using dd
|
|
size := mfg.config.Size
|
|
if size == "" {
|
|
size = "10G"
|
|
}
|
|
|
|
cmd := exec.Command("dd", "if=/dev/zero", "of="+image.Path, "bs=1M", "count=10240")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("dd failed: %w", err)
|
|
}
|
|
|
|
// Create filesystem
|
|
if err := mfg.createFilesystem(image.Path, blueprint, variant); err != nil {
|
|
return fmt.Errorf("failed to create filesystem: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) generateVMDKImage(image *GeneratedImage, blueprint *Blueprint, variant string, format ImageFormat) error {
|
|
// Create base raw image first
|
|
rawImage := &GeneratedImage{
|
|
Name: image.Name + "-raw",
|
|
Format: "raw",
|
|
Path: filepath.Join(mfg.config.OutputDir, image.Name+"-raw.img"),
|
|
}
|
|
|
|
if err := mfg.generateRAWImage(rawImage, blueprint, variant, format); err != nil {
|
|
return fmt.Errorf("failed to generate base raw image: %w", err)
|
|
}
|
|
defer os.Remove(rawImage.Path)
|
|
|
|
// Convert to VMDK
|
|
cmd := exec.Command("qemu-img", "convert",
|
|
"-f", "raw",
|
|
"-O", "vmdk",
|
|
"-o", "subformat=monolithicSparse",
|
|
rawImage.Path, image.Path)
|
|
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("qemu-img convert to VMDK failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) generateTARImage(image *GeneratedImage, blueprint *Blueprint, variant string, format ImageFormat) error {
|
|
// Create temporary directory for TAR contents
|
|
tempDir, err := os.MkdirTemp("", "debian-tar-*")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temp directory: %w", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Create TAR structure
|
|
if err := mfg.createTARStructure(tempDir, blueprint, variant); err != nil {
|
|
return fmt.Errorf("failed to create TAR structure: %w", err)
|
|
}
|
|
|
|
// Create TAR archive
|
|
cmd := exec.Command("tar", "-czf", image.Path, "-C", tempDir, ".")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("tar creation failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) createISOStructure(tempDir string, blueprint *Blueprint, variant string) error {
|
|
// Create basic ISO structure
|
|
dirs := []string{
|
|
"isolinux",
|
|
"boot",
|
|
"live",
|
|
"casper",
|
|
"dists",
|
|
"pool",
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(filepath.Join(tempDir, dir), 0755); err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
// Create isolinux configuration
|
|
isolinuxConfig := `DEFAULT live
|
|
TIMEOUT 300
|
|
PROMPT 1
|
|
LABEL live
|
|
menu label ^Live System
|
|
kernel /casper/vmlinuz
|
|
append boot=casper initrd=/casper/initrd.lz
|
|
`
|
|
|
|
isolinuxPath := filepath.Join(tempDir, "isolinux", "isolinux.cfg")
|
|
if err := os.WriteFile(isolinuxPath, []byte(isolinuxConfig), 0644); err != nil {
|
|
return fmt.Errorf("failed to write isolinux config: %w", err)
|
|
}
|
|
|
|
// Create basic kernel and initrd placeholders
|
|
kernelPath := filepath.Join(tempDir, "casper", "vmlinuz")
|
|
initrdPath := filepath.Join(tempDir, "casper", "initrd.lz")
|
|
|
|
if err := os.WriteFile(kernelPath, []byte("placeholder"), 0644); err != nil {
|
|
return fmt.Errorf("failed to create kernel placeholder: %w", err)
|
|
}
|
|
if err := os.WriteFile(initrdPath, []byte("placeholder"), 0644); err != nil {
|
|
return fmt.Errorf("failed to create initrd placeholder: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) createFilesystem(imagePath string, blueprint *Blueprint, variant string) error {
|
|
// Create ext4 filesystem
|
|
cmd := exec.Command("mkfs.ext4", imagePath)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("mkfs.ext4 failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) createTARStructure(tempDir string, blueprint *Blueprint, variant string) error {
|
|
// Create basic TAR structure
|
|
dirs := []string{
|
|
"etc",
|
|
"usr",
|
|
"var",
|
|
"home",
|
|
"root",
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(filepath.Join(tempDir, dir), 0755); err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
// Create basic configuration files
|
|
hostnamePath := filepath.Join(tempDir, "etc", "hostname")
|
|
hostnameContent := fmt.Sprintf("%s-%s", blueprint.Name, variant)
|
|
if err := os.WriteFile(hostnamePath, []byte(hostnameContent), 0644); err != nil {
|
|
return fmt.Errorf("failed to write hostname: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) checkTools(requiredTools []string) error {
|
|
var missingTools []string
|
|
|
|
for _, tool := range requiredTools {
|
|
if !mfg.isToolAvailable(tool) {
|
|
missingTools = append(missingTools, tool)
|
|
}
|
|
}
|
|
|
|
if len(missingTools) > 0 {
|
|
return fmt.Errorf("missing required tools: %s", strings.Join(missingTools, ", "))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) isToolAvailable(tool string) bool {
|
|
cmd := exec.Command("which", tool)
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) calculateImageSize(image *GeneratedImage) error {
|
|
info, err := os.Stat(image.Path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stat image: %w", err)
|
|
}
|
|
|
|
image.Size = info.Size()
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) generateChecksum(image *GeneratedImage) error {
|
|
cmd := exec.Command("sha256sum", image.Path)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("sha256sum failed: %w", err)
|
|
}
|
|
|
|
// Parse checksum from output (format: "checksum filename")
|
|
parts := strings.Fields(string(output))
|
|
if len(parts) >= 1 {
|
|
image.Checksum = parts[0]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) validateImage(image *GeneratedImage) error {
|
|
validator, exists := mfg.validators[image.Format]
|
|
if !exists {
|
|
return fmt.Errorf("no validator for format: %s", image.Format)
|
|
}
|
|
|
|
// Run validation commands
|
|
for _, command := range validator.Commands {
|
|
result := mfg.runValidationCommand(image, command)
|
|
image.ValidationResults = append(image.ValidationResults, result)
|
|
|
|
if result.Status == "failed" {
|
|
mfg.logger.Warnf("Validation command %s failed: %s", command, result.Message)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) runValidationCommand(image *GeneratedImage, command string) ValidationResult {
|
|
result := ValidationResult{
|
|
Validator: command,
|
|
Status: "success",
|
|
Message: "Validation passed",
|
|
Timestamp: time.Now(),
|
|
Details: make(map[string]interface{}),
|
|
}
|
|
|
|
// Run command based on type
|
|
switch command {
|
|
case "file":
|
|
result = mfg.runFileCommand(image)
|
|
case "qemu-img":
|
|
result = mfg.runQemuImgCommand(image)
|
|
case "isoinfo":
|
|
result = mfg.runIsoInfoCommand(image)
|
|
default:
|
|
result.Status = "skipped"
|
|
result.Message = "Command not implemented"
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) runFileCommand(image *GeneratedImage) ValidationResult {
|
|
result := ValidationResult{
|
|
Validator: "file",
|
|
Status: "success",
|
|
Message: "File type validation passed",
|
|
Timestamp: time.Now(),
|
|
Details: make(map[string]interface{}),
|
|
}
|
|
|
|
cmd := exec.Command("file", image.Path)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
result.Status = "failed"
|
|
result.Message = fmt.Sprintf("file command failed: %v", err)
|
|
return result
|
|
}
|
|
|
|
result.Details["file_type"] = strings.TrimSpace(string(output))
|
|
return result
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) runQemuImgCommand(image *GeneratedImage) ValidationResult {
|
|
result := ValidationResult{
|
|
Validator: "qemu-img",
|
|
Status: "success",
|
|
Message: "QEMU image validation passed",
|
|
Timestamp: time.Now(),
|
|
Details: make(map[string]interface{}),
|
|
}
|
|
|
|
cmd := exec.Command("qemu-img", "info", image.Path)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
result.Status = "failed"
|
|
result.Message = fmt.Sprintf("qemu-img info failed: %v", err)
|
|
return result
|
|
}
|
|
|
|
result.Details["qemu_info"] = strings.TrimSpace(string(output))
|
|
return result
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) runIsoInfoCommand(image *GeneratedImage) ValidationResult {
|
|
result := ValidationResult{
|
|
Validator: "isoinfo",
|
|
Status: "success",
|
|
Message: "ISO info validation passed",
|
|
Timestamp: time.Now(),
|
|
Details: make(map[string]interface{}),
|
|
}
|
|
|
|
cmd := exec.Command("isoinfo", "-d", "-i", image.Path)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
result.Status = "failed"
|
|
result.Message = fmt.Sprintf("isoinfo failed: %v", err)
|
|
return result
|
|
}
|
|
|
|
result.Details["iso_info"] = strings.TrimSpace(string(output))
|
|
return result
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) ListSupportedFormats() []ImageFormat {
|
|
var formats []ImageFormat
|
|
for _, format := range mfg.formats {
|
|
formats = append(formats, format)
|
|
}
|
|
return formats
|
|
}
|
|
|
|
func (mfg *MultiFormatGenerator) GetFormatInfo(format string) (*ImageFormat, error) {
|
|
imageFormat, exists := mfg.formats[format]
|
|
if !exists {
|
|
return nil, fmt.Errorf("unsupported format: %s", format)
|
|
}
|
|
return &imageFormat, nil
|
|
}
|
|
|
|
// Helper functions
|
|
func generateImageID() string {
|
|
return fmt.Sprintf("img-%d", time.Now().UnixNano())
|
|
}
|
|
|
|
// Blueprint types (imported from blueprintapi)
|
|
type Blueprint struct {
|
|
Name string `json:"name"`
|
|
Packages BlueprintPackages `json:"packages"`
|
|
Users []BlueprintUser `json:"users"`
|
|
Customizations BlueprintCustomizations `json:"customizations"`
|
|
}
|
|
|
|
type BlueprintPackages struct {
|
|
Include []string `json:"include"`
|
|
}
|
|
|
|
type BlueprintUser struct {
|
|
Name string `json:"name"`
|
|
Shell string `json:"shell"`
|
|
}
|
|
|
|
type BlueprintCustomizations struct {
|
|
Hostname string `json:"hostname"`
|
|
Timezone string `json:"timezone"`
|
|
}
|