package phases import ( "fmt" "os" "path/filepath" "time" "deb-bootc-compose/internal/types" ) // OutputPhase handles generating final output artifacts type OutputPhase struct { PhaseBase } // NewOutputPhase creates a new OutputPhase func NewOutputPhase() *OutputPhase { return &OutputPhase{ PhaseBase: NewPhaseBase("output", []string{"ostree"}), } } // Run executes the output phase func (p *OutputPhase) Run(engine types.Engine) error { logger := engine.GetLogger() logger.Printf("Starting output generation phase") // Get output manager outputManager := engine.GetOutputManager() if outputManager == nil { logger.Printf("No output manager configured, skipping output phase") return nil } // Get treefile and process variants treefile := engine.GetTreefile() for _, variant := range treefile.Variants { for _, arch := range variant.Architectures { logger.Printf("Generating output for variant %s on architecture %s", variant.Name, arch) // Create output work directory outputWorkDir := filepath.Join(engine.GetWorkDir(), "variants", variant.Name, arch, "output") if err := os.MkdirAll(outputWorkDir, 0755); err != nil { return fmt.Errorf("failed to create output work directory: %w", err) } // Generate outputs for this variant/architecture if err := p.generateOutputs(engine, outputWorkDir, variant, arch); err != nil { return fmt.Errorf("failed to generate outputs for variant %s on architecture %s: %w", variant.Name, arch, err) } logger.Printf("Completed output generation for variant %s on architecture %s", variant.Name, arch) } } logger.Printf("Output generation phase completed successfully") return nil } // generateOutputs generates all configured output formats func (p *OutputPhase) generateOutputs(engine types.Engine, workDir string, variant types.Variant, arch string) error { logger := engine.GetLogger() outputManager := engine.GetOutputManager() // Get OSTree reference ref := engine.GetTreefile().GetOSTreeRef(variant.Name, arch) // Get output configuration outputConfig := engine.GetTreefile().Output // Generate container image if requested if p.containsFormat(outputConfig.Formats, "container") { logger.Printf("Generating container image for variant %s on architecture %s", variant.Name, arch) containerConfig := map[string]interface{}{ "base_image": outputConfig.Container.BaseImage, "labels": outputConfig.Container.Labels, "entrypoint": outputConfig.Container.Entrypoint, "cmd": outputConfig.Container.CMD, } containerPath := filepath.Join(workDir, "container") if err := outputManager.GenerateContainerImage(ref, containerPath, containerConfig); err != nil { logger.Printf("Warning: failed to generate container image: %v", err) } else { logger.Printf("Successfully generated container image") } } // Generate disk image if requested if p.containsFormat(outputConfig.Formats, "disk") { logger.Printf("Generating disk image for variant %s on architecture %s", variant.Name, arch) diskConfig := map[string]interface{}{ "size": outputConfig.DiskImage.Size, "formats": outputConfig.DiskImage.Formats, "bootloader": outputConfig.DiskImage.Bootloader, "kernel": outputConfig.DiskImage.Kernel, "initramfs": outputConfig.DiskImage.Initramfs, } diskPath := filepath.Join(workDir, "disk") if err := outputManager.GenerateDiskImage(ref, diskPath, diskConfig); err != nil { logger.Printf("Warning: failed to generate disk image: %v", err) } else { logger.Printf("Successfully generated disk image") } } // Generate tarball if requested if p.containsFormat(outputConfig.Formats, "tarball") { logger.Printf("Generating tarball for variant %s on architecture %s", variant.Name, arch) tarballConfig := map[string]interface{}{ "compression": "gzip", "format": "tar.gz", } tarballPath := filepath.Join(workDir, "tarball") if err := outputManager.GenerateTarball(ref, tarballPath, tarballConfig); err != nil { logger.Printf("Warning: failed to generate tarball: %v", err) } else { logger.Printf("Successfully generated tarball") } } // Write output summary if err := p.writeOutputSummary(workDir, variant, arch, outputConfig.Formats); err != nil { return fmt.Errorf("failed to write output summary: %w", err) } return nil } // containsFormat checks if a format is in the list func (p *OutputPhase) containsFormat(formats []string, format string) bool { for _, f := range formats { if f == format { return true } } return false } // writeOutputSummary writes a summary of generated outputs func (p *OutputPhase) writeOutputSummary(workDir string, variant types.Variant, arch string, formats []string) error { summaryFile := filepath.Join(workDir, "output-summary.txt") summary := fmt.Sprintf(`Output Generation Summary Variant: %s Architecture: %s Timestamp: %s Formats: %v Generated outputs: `, variant.Name, arch, time.Now().Format(time.RFC3339), formats) for _, format := range formats { summary += fmt.Sprintf(" - %s\n", format) } if err := os.WriteFile(summaryFile, []byte(summary), 0644); err != nil { return fmt.Errorf("failed to write output summary: %w", err) } return nil }