debian-forge-cli/cmd/image-builder/enhanced_build.go
2025-08-26 10:33:28 -07:00

130 lines
3.6 KiB
Go

package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/osbuild/image-builder-cli/pkg/progress"
"github.com/osbuild/images/pkg/imagefilter"
)
type enhancedBuildOptions struct {
OutputDir string
StoreDir string
OutputBasename string
Formats []string
Blueprint string
WriteManifest bool
WriteBuildlog bool
ValidateOnly bool
}
func enhancedBuildImage(pbar progress.ProgressBar, res *imagefilter.Result, osbuildManifest []byte, opts *enhancedBuildOptions) ([]string, error) {
if opts == nil {
opts = &enhancedBuildOptions{}
}
var outputs []string
basename := basenameFor(res, opts.OutputBasename)
// Handle blueprint if provided
if opts.Blueprint != "" {
if err := validateBlueprint(opts.Blueprint); err != nil {
return nil, fmt.Errorf("blueprint validation failed: %w", err)
}
if opts.ValidateOnly {
return []string{"blueprint validation passed"}, nil
}
}
// Build for each requested format
formats := opts.Formats
if len(formats) == 0 {
formats = []string{res.ImgType.Name()}
}
for _, format := range formats {
output, err := buildSingleFormat(pbar, res, osbuildManifest, basename, format, opts)
if err != nil {
return outputs, fmt.Errorf("failed to build %s format: %w", format, err)
}
outputs = append(outputs, output)
}
return outputs, nil
}
func buildSingleFormat(pbar progress.ProgressBar, res *imagefilter.Result, osbuildManifest []byte, basename, format string, opts *enhancedBuildOptions) (string, error) {
// Create output directory
if err := os.MkdirAll(opts.OutputDir, 0755); err != nil {
return "", fmt.Errorf("cannot create output directory: %w", err)
}
// Write manifest if requested
if opts.WriteManifest {
manifestPath := filepath.Join(opts.OutputDir, fmt.Sprintf("%s-%s.osbuild-manifest.json", basename, format))
if err := os.WriteFile(manifestPath, osbuildManifest, 0644); err != nil {
return "", fmt.Errorf("cannot write manifest: %w", err)
}
}
// Configure osbuild options
osbuildOpts := &progress.OSBuildOptions{
StoreDir: opts.StoreDir,
OutputDir: opts.OutputDir,
}
// Handle build log
if opts.WriteBuildlog {
logPath := filepath.Join(opts.OutputDir, fmt.Sprintf("%s-%s.buildlog", basename, format))
f, err := os.Create(logPath)
if err != nil {
return "", fmt.Errorf("cannot create buildlog: %w", err)
}
defer f.Close()
osbuildOpts.BuildLog = f
}
// Run osbuild
if err := progress.RunOSBuild(pbar, osbuildManifest, res.ImgType.Exports(), osbuildOpts); err != nil {
return "", err
}
// Rename output file
pipelineDir := filepath.Join(opts.OutputDir, res.ImgType.Exports()[0])
srcName := filepath.Join(pipelineDir, res.ImgType.Filename())
imgExt := strings.SplitN(res.ImgType.Filename(), ".", 2)[1]
dstName := filepath.Join(opts.OutputDir, fmt.Sprintf("%s-%s.%s", basename, format, imgExt))
if err := os.Rename(srcName, dstName); err != nil {
return "", fmt.Errorf("cannot rename artifact to final name: %w", err)
}
// Clean up pipeline directory
_ = os.Remove(pipelineDir)
return dstName, nil
}
func validateBlueprint(blueprintPath string) error {
// Read and parse blueprint
data, err := os.ReadFile(blueprintPath)
if err != nil {
return fmt.Errorf("cannot read blueprint: %w", err)
}
// Basic validation - check if it's valid YAML/JSON
if !strings.Contains(string(data), "packages:") && !strings.Contains(string(data), "users:") {
return fmt.Errorf("blueprint appears to be invalid - missing required sections")
}
return nil
}
func getSupportedFormats() []string {
return []string{
"qcow2", "raw", "vmdk", "iso", "tar", "container",
}
}