302 lines
No EOL
9 KiB
Go
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)
|
|
} |