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) }