did stuff
This commit is contained in:
parent
3f2346b201
commit
ee02c74250
10 changed files with 1511 additions and 0 deletions
260
cmd/image-builder/blueprint.go
Normal file
260
cmd/image-builder/blueprint.go
Normal 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
|
||||
}
|
||||
130
cmd/image-builder/enhanced_build.go
Normal file
130
cmd/image-builder/enhanced_build.go
Normal 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",
|
||||
}
|
||||
}
|
||||
64
cmd/image-builder/enhanced_build_cmd.go
Normal file
64
cmd/image-builder/enhanced_build_cmd.go
Normal 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
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue