deb-bootc-image-builder/bib/internal/builder/recipe.go
robojerk 126ee1a849
Some checks failed
particle-os CI / Test particle-os (push) Failing after 1s
particle-os CI / Integration Test (push) Has been skipped
particle-os CI / Security & Quality (push) Failing after 1s
Test particle-os Basic Functionality / test-basic (push) Failing after 1s
particle-os CI / Build and Release (push) Has been skipped
cleanup
2025-08-27 12:30:24 -07:00

149 lines
4 KiB
Go

package particle_os
import (
"fmt"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
// Recipe represents a particle-os recipe configuration
type Recipe struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
BaseImage string `yaml:"base-image"`
ImageVersion string `yaml:"image-version"`
Stages []Stage `yaml:"stages"`
Output OutputConfig `yaml:"output"`
Metadata map[string]interface{} `yaml:"metadata,omitempty"`
}
// Stage represents a build stage in the recipe
type Stage struct {
Type string `yaml:"type"`
Options map[string]interface{} `yaml:"options,omitempty"`
Inputs map[string]interface{} `yaml:"inputs,omitempty"`
Devices map[string]interface{} `yaml:"devices,omitempty"`
Mounts []interface{} `yaml:"mounts,omitempty"`
}
// User represents a user account configuration
type User struct {
Password string `yaml:"password,omitempty"`
Shell string `yaml:"shell,omitempty"`
Groups []string `yaml:"groups,omitempty"`
UID int `yaml:"uid"`
GID int `yaml:"gid"`
Home string `yaml:"home,omitempty"`
Comment string `yaml:"comment,omitempty"`
}
// UsersOptions represents the options for the users stage
type UsersOptions struct {
Users map[string]User `yaml:"users"`
DefaultShell string `yaml:"default_shell,omitempty"`
DefaultHome string `yaml:"default_home,omitempty"`
}
// OutputConfig defines the output image configuration
type OutputConfig struct {
Formats []string `yaml:"formats"`
Size string `yaml:"size"`
Path string `yaml:"path,omitempty"`
}
// LoadRecipe loads a recipe from a YAML file
func LoadRecipe(recipePath string) (*Recipe, error) {
data, err := os.ReadFile(recipePath)
if err != nil {
return nil, fmt.Errorf("failed to read recipe file: %w", err)
}
var recipe Recipe
if err := yaml.Unmarshal(data, &recipe); err != nil {
return nil, fmt.Errorf("failed to parse recipe YAML: %w", err)
}
// Validate required fields
if recipe.Name == "" {
return nil, fmt.Errorf("recipe must have a name")
}
if recipe.BaseImage == "" {
return nil, fmt.Errorf("recipe must specify a base-image")
}
if len(recipe.Stages) == 0 {
return nil, fmt.Errorf("recipe must have at least one stage")
}
return &recipe, nil
}
// SaveRecipe saves a recipe to a YAML file
func (r *Recipe) SaveRecipe(outputPath string) error {
data, err := yaml.Marshal(r)
if err != nil {
return fmt.Errorf("failed to marshal recipe: %w", err)
}
// Ensure output directory exists
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
if err := os.WriteFile(outputPath, data, 0644); err != nil {
return fmt.Errorf("failed to write recipe file: %w", err)
}
return nil
}
// Validate checks if the recipe is valid
func (r *Recipe) Validate() error {
// Check for required fields
if r.Name == "" {
return fmt.Errorf("recipe name is required")
}
if r.BaseImage == "" {
return fmt.Errorf("base-image is required")
}
if len(r.Stages) == 0 {
return fmt.Errorf("at least one stage is required")
}
// Validate stages
for i, stage := range r.Stages {
if stage.Type == "" {
return fmt.Errorf("stage %d: type is required", i)
}
}
// Validate output
if len(r.Output.Formats) == 0 {
return fmt.Errorf("at least one output format is required")
}
return nil
}
// GetStageByType returns all stages of a specific type
func (r *Recipe) GetStageByType(stageType string) []Stage {
var stages []Stage
for _, stage := range r.Stages {
if stage.Type == stageType {
stages = append(stages, stage)
}
}
return stages
}
// HasStage checks if the recipe has a stage of the specified type
func (r *Recipe) HasStage(stageType string) bool {
for _, stage := range r.Stages {
if stage.Type == stageType {
return true
}
}
return false
}