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", } }