130 lines
3.6 KiB
Go
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",
|
|
}
|
|
}
|