did stuff

This commit is contained in:
robojerk 2025-08-26 10:33:28 -07:00
parent 3f2346b201
commit ee02c74250
10 changed files with 1511 additions and 0 deletions

View file

@ -0,0 +1,260 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"gopkg.in/yaml.v3"
)
type DebianBlueprint struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
Version string `yaml:"version" json:"version"`
Variant string `yaml:"variant" json:"variant"`
Architecture string `yaml:"architecture" json:"architecture"`
Packages BlueprintPackages `yaml:"packages" json:"packages"`
Users []BlueprintUser `yaml:"users" json:"users"`
Groups []BlueprintGroup `yaml:"groups" json:"groups"`
Services []BlueprintService `yaml:"services" json:"services"`
Files []BlueprintFile `yaml:"files" json:"files"`
Customizations BlueprintCustomizations `yaml:"customizations" json:"customizations"`
Created time.Time `yaml:"created" json:"created"`
Modified time.Time `yaml:"modified" json:"modified"`
}
type BlueprintPackages struct {
Include []string `yaml:"include" json:"include"`
Exclude []string `yaml:"exclude" json:"exclude"`
Groups []string `yaml:"groups" json:"groups"`
}
type BlueprintUser struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
Password string `yaml:"password" json:"password"`
Key string `yaml:"key" json:"key"`
Home string `yaml:"home" json:"home"`
Shell string `yaml:"shell" json:"shell"`
Groups []string `yaml:"groups" json:"groups"`
UID int `yaml:"uid" json:"uid"`
GID int `yaml:"gid" json:"gid"`
}
type BlueprintGroup struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
GID int `yaml:"gid" json:"gid"`
}
type BlueprintService struct {
Name string `yaml:"name" json:"name"`
Enabled bool `yaml:"enabled" json:"enabled"`
Masked bool `yaml:"masked" json:"masked"`
}
type BlueprintFile struct {
Path string `yaml:"path" json:"path"`
User string `yaml:"user" json:"user"`
Group string `yaml:"group" json:"group"`
Mode string `yaml:"mode" json:"mode"`
Data string `yaml:"data" json:"data"`
EnsureParents bool `yaml:"ensure_parents" json:"ensure_parents"`
}
type BlueprintCustomizations struct {
Hostname string `yaml:"hostname" json:"hostname"`
Kernel BlueprintKernel `yaml:"kernel" json:"kernel"`
Timezone string `yaml:"timezone" json:"timezone"`
Locale string `yaml:"locale" json:"locale"`
Firewall BlueprintFirewall `yaml:"firewall" json:"firewall"`
SSH BlueprintSSH `yaml:"ssh" json:"ssh"`
}
type BlueprintKernel struct {
Name string `yaml:"name" json:"name"`
Append string `yaml:"append" json:"append"`
Remove string `yaml:"remove" json:"remove"`
}
type BlueprintFirewall struct {
Services []string `yaml:"services" json:"services"`
Ports []string `yaml:"ports" json:"ports"`
}
type BlueprintSSH struct {
KeyFile string `yaml:"key_file" json:"key_file"`
User string `yaml:"user" json:"user"`
}
func loadBlueprint(path string) (*DebianBlueprint, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot read blueprint file: %w", err)
}
var blueprint DebianBlueprint
// Try YAML first
if err := yaml.Unmarshal(data, &blueprint); err != nil {
// Try JSON if YAML fails
if err := json.Unmarshal(data, &blueprint); err != nil {
return nil, fmt.Errorf("cannot parse blueprint file (neither YAML nor JSON): %w", err)
}
}
// Set timestamps if not present
if blueprint.Created.IsZero() {
blueprint.Created = time.Now()
}
blueprint.Modified = time.Now()
return &blueprint, nil
}
func saveBlueprint(blueprint *DebianBlueprint, path string, format string) error {
var data []byte
var err error
switch strings.ToLower(format) {
case "yaml", "yml":
data, err = yaml.Marshal(blueprint)
case "json":
data, err = json.MarshalIndent(blueprint, "", " ")
default:
return fmt.Errorf("unsupported format: %s", format)
}
if err != nil {
return fmt.Errorf("cannot marshal blueprint: %w", err)
}
// Ensure directory exists
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("cannot create directory: %w", err)
}
if err := os.WriteFile(path, data, 0644); err != nil {
return fmt.Errorf("cannot write blueprint file: %w", err)
}
return nil
}
func validateBlueprintStructure(blueprint *DebianBlueprint) error {
var errors []string
if blueprint.Name == "" {
errors = append(errors, "name is required")
}
if blueprint.Variant == "" {
errors = append(errors, "variant is required")
}
if blueprint.Architecture == "" {
errors = append(errors, "architecture is required")
}
// Validate variant
validVariants := []string{"bookworm", "sid", "testing", "backports"}
validVariant := false
for _, v := range validVariants {
if blueprint.Variant == v {
validVariant = true
break
}
}
if !validVariant {
errors = append(errors, fmt.Sprintf("invalid variant: %s (valid: %v)", blueprint.Variant, validVariants))
}
// Validate architecture
validArchs := []string{"amd64", "arm64", "armel", "armhf", "i386", "mips64el", "mipsel", "ppc64el", "s390x"}
validArch := false
for _, a := range validArchs {
if blueprint.Architecture == a {
validArch = true
break
}
}
if !validArch {
errors = append(errors, fmt.Sprintf("invalid architecture: %s (valid: %v)", blueprint.Architecture, validArchs))
}
if len(errors) > 0 {
return fmt.Errorf("blueprint validation failed: %s", strings.Join(errors, "; "))
}
return nil
}
func createDefaultBlueprint(name, variant, architecture string) *DebianBlueprint {
return &DebianBlueprint{
Name: name,
Description: fmt.Sprintf("Debian %s %s blueprint", variant, architecture),
Version: "1.0.0",
Variant: variant,
Architecture: architecture,
Packages: BlueprintPackages{
Include: []string{"task-minimal"},
Exclude: []string{},
Groups: []string{},
},
Users: []BlueprintUser{
{
Name: "debian",
Description: "Default user",
Home: "/home/debian",
Shell: "/bin/bash",
Groups: []string{"users"},
},
},
Groups: []BlueprintGroup{
{
Name: "users",
Description: "Default users group",
},
},
Services: []BlueprintService{
{
Name: "ssh",
Enabled: true,
},
},
Customizations: BlueprintCustomizations{
Hostname: fmt.Sprintf("%s-%s", name, variant),
Timezone: "UTC",
Locale: "en_US.UTF-8",
Kernel: BlueprintKernel{
Name: "linux-image-amd64",
},
},
Created: time.Now(),
Modified: time.Now(),
}
}
func listBlueprints(directory string) ([]string, error) {
files, err := os.ReadDir(directory)
if err != nil {
return nil, fmt.Errorf("cannot read directory: %w", err)
}
var blueprints []string
for _, file := range files {
if !file.IsDir() {
ext := strings.ToLower(filepath.Ext(file.Name()))
if ext == ".yaml" || ext == ".yml" || ext == ".json" {
blueprints = append(blueprints, file.Name())
}
}
}
return blueprints, nil
}

View file

@ -0,0 +1,130 @@
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",
}
}

View file

@ -0,0 +1,64 @@
package main
import (
"fmt"
"github.com/osbuild/images/pkg/imagefilter"
"github.com/spf13/cobra"
)
func cmdEnhancedBuild(cmd *cobra.Command, args []string) error {
// Simplified enhanced build command
fmt.Println("Enhanced build command - placeholder implementation")
fmt.Println("This would integrate with the existing build system")
return nil
}
func generateEnhancedManifest(cmd *cobra.Command, res *imagefilter.Result) ([]byte, error) {
// This would integrate with the existing manifest generation logic
// For now, return a placeholder
return []byte(`{"placeholder": "manifest"}`), nil
}
func cmdBlueprint(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("blueprint command requires subcommand")
}
subcommand := args[0]
switch subcommand {
case "create":
return cmdBlueprintCreate(cmd, args[1:])
case "validate":
return cmdBlueprintValidate(cmd, args[1:])
case "list":
return cmdBlueprintList(cmd, args[1:])
case "show":
return cmdBlueprintShow(cmd, args[1:])
default:
return fmt.Errorf("unknown blueprint subcommand: %s", subcommand)
}
}
func cmdBlueprintCreate(cmd *cobra.Command, args []string) error {
fmt.Println("Blueprint create command - placeholder implementation")
return nil
}
func cmdBlueprintValidate(cmd *cobra.Command, args []string) error {
fmt.Println("Blueprint validate command - placeholder implementation")
return nil
}
func cmdBlueprintList(cmd *cobra.Command, args []string) error {
fmt.Println("Blueprint list command - placeholder implementation")
return nil
}
func cmdBlueprintShow(cmd *cobra.Command, args []string) error {
fmt.Println("Blueprint show command - placeholder implementation")
return nil
}
// Note: Helper functions are defined in other files to avoid conflicts

View file

@ -506,6 +506,71 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support.
buildCmd.Flags().String("output-name", "", "set specific output basename")
rootCmd.AddCommand(buildCmd)
buildCmd.Flags().AddFlagSet(uploadCmd.Flags())
// Enhanced build command with multi-format support
enhancedBuildCmd := &cobra.Command{
Use: "enhanced-build <image-type>",
Short: "Enhanced build with multi-format support and blueprint management",
RunE: cmdEnhancedBuild,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
}
enhancedBuildCmd.Flags().AddFlagSet(manifestCmd.Flags())
enhancedBuildCmd.Flags().StringArray("formats", nil, "output formats (qcow2, raw, vmdk, iso, tar, container)")
enhancedBuildCmd.Flags().Bool("validate-only", false, "only validate blueprint, don't build")
enhancedBuildCmd.Flags().Bool("with-manifest", false, `export osbuild manifest`)
enhancedBuildCmd.Flags().Bool("with-buildlog", false, `export osbuild buildlog`)
enhancedBuildCmd.Flags().String("cache", "/var/cache/image-builder/store", `osbuild directory to cache intermediate build artifacts"`)
enhancedBuildCmd.Flags().String("output-name", "", "set specific output basename")
rootCmd.AddCommand(enhancedBuildCmd)
// Blueprint management commands
blueprintCmd := &cobra.Command{
Use: "blueprint",
Short: "Manage Debian blueprints",
SilenceUsage: true,
Args: cobra.MinimumNArgs(1),
}
blueprintCreateCmd := &cobra.Command{
Use: "create <name> <variant> <architecture>",
Short: "Create a new Debian blueprint",
RunE: cmdBlueprintCreate,
SilenceUsage: true,
Args: cobra.ExactArgs(3),
}
blueprintCreateCmd.Flags().String("output", "", "output file path")
blueprintCreateCmd.Flags().String("format", "yaml", "output format (yaml, json)")
blueprintCmd.AddCommand(blueprintCreateCmd)
blueprintValidateCmd := &cobra.Command{
Use: "validate <blueprint-file>",
Short: "Validate a Debian blueprint",
RunE: cmdBlueprintValidate,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
}
blueprintCmd.AddCommand(blueprintValidateCmd)
blueprintListCmd := &cobra.Command{
Use: "list [directory]",
Short: "List available blueprints",
RunE: cmdBlueprintList,
SilenceUsage: true,
Args: cobra.MaximumNArgs(1),
}
blueprintCmd.AddCommand(blueprintListCmd)
blueprintShowCmd := &cobra.Command{
Use: "show <blueprint-file>",
Short: "Show blueprint details",
RunE: cmdBlueprintShow,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
}
blueprintCmd.AddCommand(blueprintShowCmd)
rootCmd.AddCommand(blueprintCmd)
// add after the rest of the uploadCmd flag set is added to avoid
// that build gets a "--to" parameter
uploadCmd.Flags().String("to", "", "upload to the given cloud")