deb-bootc-image-builder-new/bib/cmd/debian-bootc-image-builder/image.go
2025-09-05 07:10:12 -07:00

302 lines
No EOL
9 KiB
Go

package main
import (
"fmt"
"os"
"debian-bootc-image-builder/internal/apt"
"debian-bootc-image-builder/internal/container"
"debian-bootc-image-builder/internal/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
// GibiByte represents 1 GiB in bytes
const GibiByte = 1024 * 1024 * 1024
// ManifestConfig contains configuration for manifest generation
type ManifestConfig struct {
Architecture string
Config *config.Config
ImageTypes []string
Imgref string
BuildImgref string
RootfsMinsize uint64
DistroDefPaths []string
SourceInfo interface{} // Placeholder for osinfo.Info
BuildSourceInfo interface{} // Placeholder for osinfo.Info
RootFSType string
UseLibrepo bool
}
// makeManifest creates an OSBuild manifest for Debian images
func makeManifest(c *ManifestConfig, solver *apt.Solver, cacheRoot string) (map[string]interface{}, map[string][]apt.RepoConfig, error) {
logrus.Debugf("Creating manifest for architecture: %s", c.Architecture)
// Create a simple OSBuild manifest structure with only APT stages
manifest := map[string]interface{}{
"version": "2",
"pipelines": []map[string]interface{}{
{
"name": "build",
"runner": "org.osbuild.linux",
"stages": []map[string]interface{}{
{
"type": "org.osbuild.apt.depsolve",
"options": map[string]interface{}{
"packages": []string{"base-files", "systemd", "linux-image-amd64"},
},
},
{
"type": "org.osbuild.apt",
"options": map[string]interface{}{
"packages": []string{"base-files", "systemd", "linux-image-amd64"},
},
},
},
},
},
}
// Depsolve packages for each package set
depsolvedSets := make(map[string]apt.DepsolveResult)
depsolvedRepos := make(map[string][]apt.RepoConfig)
// Load package definitions for the image type
packageSet, err := c.loadPackageDefinitions()
if err != nil {
return nil, nil, fmt.Errorf("cannot load package definitions: %w", err)
}
res, err := solver.Depsolve(packageSet, 0)
if err != nil {
return nil, nil, fmt.Errorf("cannot depsolve packages: %w", err)
}
depsolvedSets["build"] = *res
depsolvedRepos["build"] = res.Repos
// Add resolved packages to manifest
if pipelines, ok := manifest["pipelines"].([]map[string]interface{}); ok {
for _, pipeline := range pipelines {
if pipeline["name"] == "build" {
if stages, ok := pipeline["stages"].([]map[string]interface{}); ok {
// Update depsolve stage with resolved packages
if len(stages) > 0 && stages[0]["type"] == "org.osbuild.apt.depsolve" {
if options, ok := stages[0]["options"].(map[string]interface{}); ok {
packages := make([]string, len(res.Packages))
for i, pkg := range res.Packages {
packages[i] = pkg.Name
}
options["packages"] = packages
// APT depsolve stage doesn't accept repositories option
// Repository configuration is handled by the APT stage
}
}
// Update apt stage with resolved packages
if len(stages) > 1 && stages[1]["type"] == "org.osbuild.apt" {
if options, ok := stages[1]["options"].(map[string]interface{}); ok {
packages := make([]string, len(res.Packages))
for i, pkg := range res.Packages {
packages[i] = pkg.Name
}
options["packages"] = packages
}
}
}
break
}
}
}
return manifest, depsolvedRepos, nil
}
// manifestFromCobra creates a manifest from command line arguments
func manifestFromCobra(cmd *cobra.Command, args []string, pbar interface{}) (map[string]interface{}, interface{}, error) {
if len(args) != 1 {
return nil, nil, fmt.Errorf("expected exactly one argument (container image reference)")
}
imgref := args[0]
logrus.Debugf("Processing container image: %s", imgref)
// Get command line flags
userConfigFile, _ := cmd.Flags().GetString("config")
imgTypes, _ := cmd.Flags().GetStringArray("type")
aptCacheRoot, _ := cmd.Flags().GetString("aptcache")
targetArch, _ := cmd.Flags().GetString("target-arch")
rootFs, _ := cmd.Flags().GetString("rootfs")
buildImgref, _ := cmd.Flags().GetString("build-container")
distroDefPaths, _ := cmd.Flags().GetStringArray("distro-def-path")
useLibrepo, _ := cmd.Flags().GetBool("librepo")
// Load configuration (optional - use defaults if not found)
cfg, err := config.LoadConfig(userConfigFile)
if err != nil {
logrus.Warnf("Could not load configuration: %v, using defaults", err)
cfg = nil // Use defaults
}
// Set default values
if aptCacheRoot == "" {
aptCacheRoot = "/aptcache"
}
if targetArch == "" {
targetArch = "amd64"
}
if len(imgTypes) == 0 {
imgTypes = []string{"qcow2"}
}
if len(distroDefPaths) == 0 {
distroDefPaths = []string{"./data/defs"}
}
// Create container instance
container, err := container.New(imgref)
if err != nil {
return nil, nil, fmt.Errorf("cannot create container: %w", err)
}
defer func() {
if err := container.Stop(); err != nil {
logrus.Warnf("error stopping container: %v", err)
}
}()
// Get container size (simplified)
cntSize := uint64(10 * GibiByte) // Default size
// Get root filesystem type
var rootfsType string
if rootFs != "" {
rootfsType = rootFs
} else {
rootfsType, err = container.DefaultRootfsType()
if err != nil {
return nil, nil, fmt.Errorf("cannot get rootfs type for container: %w", err)
}
if rootfsType == "" {
rootfsType = "ext4"
}
}
// Initialize APT in container
if err := container.InitAPT(); err != nil {
return nil, nil, fmt.Errorf("cannot initialize APT: %w", err)
}
// Create solver
solver, err := container.NewContainerSolver(aptCacheRoot, targetArch, nil)
if err != nil {
return nil, nil, fmt.Errorf("cannot create solver: %w", err)
}
// Create manifest configuration
var rootfsMinsize uint64
if cfg != nil {
rootfsMinsize = cntSize * uint64(cfg.GetContainerSizeMultiplier())
} else {
// Default multiplier when no config is available
rootfsMinsize = cntSize * 2 // Default 2x multiplier
}
manifestConfig := &ManifestConfig{
Architecture: targetArch,
Config: cfg,
ImageTypes: imgTypes,
Imgref: imgref,
BuildImgref: buildImgref,
RootfsMinsize: rootfsMinsize,
DistroDefPaths: distroDefPaths,
SourceInfo: nil, // Placeholder
BuildSourceInfo: nil, // Placeholder
RootFSType: rootfsType,
UseLibrepo: useLibrepo,
}
// Generate manifest
manifest, repos, err := makeManifest(manifestConfig, solver, aptCacheRoot)
if err != nil {
return nil, nil, fmt.Errorf("cannot generate manifest: %w", err)
}
return manifest, repos, nil
}
// getContainerSize gets the size of a container (simplified implementation)
func getContainerSize(imgref string) (uint64, error) {
// This is a simplified implementation
// In a real implementation, this would:
// 1. Inspect the container image
// 2. Calculate the actual size
// 3. Return the size in bytes
logrus.Debugf("Getting container size for: %s", imgref)
return 10 * GibiByte, nil // Default 10GB
}
// getDistroAndRunner returns the distribution and runner for Debian
func getDistroAndRunner(osRelease interface{}) (string, interface{}, error) {
// This is a simplified implementation for Debian
// In a real implementation, this would parse os-release information
return "debian", &DebianRunner{}, nil
}
// DebianRunner represents a Debian-specific runner
type DebianRunner struct {
Version string
}
// String returns the string representation of the runner
func (r *DebianRunner) String() string {
return "debian"
}
// PackageDefinition represents a package definition structure
type PackageDefinition struct {
Packages []string `yaml:"packages"`
}
// loadPackageDefinitions loads package definitions for the specified image types
func (c *ManifestConfig) loadPackageDefinitions() ([]string, error) {
// For now, use the first image type to determine packages
if len(c.ImageTypes) == 0 {
return []string{"base-files", "systemd", "linux-image-amd64"}, nil
}
imageType := c.ImageTypes[0]
// Try to load from package definition files
for _, defPath := range c.DistroDefPaths {
defFile := fmt.Sprintf("%s/debian-trixie.yaml", defPath)
if packages, err := c.loadPackagesFromFile(defFile, imageType); err == nil {
return packages, nil
}
}
// Fallback to default packages
logrus.Warnf("Could not load package definitions for %s, using defaults", imageType)
return []string{"base-files", "systemd", "linux-image-amd64"}, nil
}
// loadPackagesFromFile loads packages from a YAML definition file
func (c *ManifestConfig) loadPackagesFromFile(filename, imageType string) ([]string, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("cannot read package definition file %s: %w", filename, err)
}
var defs map[string]PackageDefinition
if err := yaml.Unmarshal(data, &defs); err != nil {
return nil, fmt.Errorf("cannot parse package definition file %s: %w", filename, err)
}
if pkgDef, exists := defs[imageType]; exists {
return pkgDef.Packages, nil
}
return nil, fmt.Errorf("no package definition found for image type %s", imageType)
}