debian-forge-composer/internal/schema/debian_schema_manager.go
robojerk 4eeaa43c39
Some checks failed
Tests / 🛃 Unit tests (push) Failing after 13s
Tests / 🗄 DB tests (push) Failing after 19s
Tests / 🐍 Lint python scripts (push) Failing after 1s
Tests / ⌨ Golang Lint (push) Failing after 1s
Tests / 📦 Packit config lint (push) Failing after 1s
Tests / 🔍 Check source preparation (push) Failing after 1s
Tests / 🔍 Check for valid snapshot urls (push) Failing after 1s
Tests / 🔍 Check for missing or unused runner repos (push) Failing after 1s
Tests / 🐚 Shellcheck (push) Failing after 1s
Tests / 📦 RPMlint (push) Failing after 1s
Tests / Gitlab CI trigger helper (push) Failing after 1s
Tests / 🎀 kube-linter (push) Failing after 1s
Tests / 🧹 cloud-cleaner-is-enabled (push) Successful in 3s
Tests / 🔍 Check spec file osbuild/images dependencies (push) Failing after 1s
did stuff
2025-08-26 10:34:42 -07:00

613 lines
17 KiB
Go

package schema
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/sirupsen/logrus"
)
// DebianSchemaManager handles Debian-adapted blue-build schemas
type DebianSchemaManager struct {
logger *logrus.Logger
config *SchemaConfig
schemas map[string]DebianSchema
validations map[string]SchemaValidation
adaptations map[string]SchemaAdaptation
mu sync.RWMutex
}
// SchemaConfig holds schema configuration
type SchemaConfig struct {
Enabled bool `json:"enabled"`
SchemasPath string `json:"schemas_path"`
Validation bool `json:"validation"`
Adaptations bool `json:"adaptations"`
Metadata map[string]string `json:"metadata"`
}
// DebianSchema represents a Debian-adapted schema
type DebianSchema struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Version string `json:"version"`
Source string `json:"source"`
Adapted bool `json:"adapted"`
Enabled bool `json:"enabled"`
Metadata map[string]interface{} `json:"metadata"`
}
// SchemaValidation represents schema validation rules
type SchemaValidation struct {
ID string `json:"id"`
SchemaID string `json:"schema_id"`
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Rules map[string]interface{} `json:"rules"`
Enabled bool `json:"enabled"`
Metadata map[string]interface{} `json:"metadata"`
}
// SchemaAdaptation represents schema adaptation from blue-build
type SchemaAdaptation struct {
ID string `json:"id"`
OriginalID string `json:"original_id"`
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Changes []string `json:"changes"`
Status string `json:"status"`
Enabled bool `json:"enabled"`
Metadata map[string]interface{} `json:"metadata"`
}
// NewDebianSchemaManager creates a new Debian schema manager
func NewDebianSchemaManager(config *SchemaConfig, logger *logrus.Logger) *DebianSchemaManager {
manager := &DebianSchemaManager{
logger: logger,
config: config,
schemas: make(map[string]DebianSchema),
validations: make(map[string]SchemaValidation),
adaptations: make(map[string]SchemaAdaptation),
}
// Initialize Debian schemas
manager.initializeDebianSchemas()
manager.initializeSchemaValidations()
manager.initializeSchemaAdaptations()
return manager
}
// initializeDebianSchemas initializes Debian-specific schemas
func (dsm *DebianSchemaManager) initializeDebianSchemas() {
// Recipe schema (Debian-adapted)
dsm.schemas["recipe-v1"] = DebianSchema{
ID: "recipe-v1",
Name: "Debian Recipe Schema v1",
Description: "Schema for Debian atomic image recipes",
Type: "recipe",
Version: "1.0.0",
Source: "debian-adapted",
Adapted: true,
Enabled: true,
Metadata: map[string]interface{}{
"base_schema": "blue-build-recipe-v1",
"target_os": "debian",
},
}
// Module schema (Debian-adapted)
dsm.schemas["module-v1"] = DebianSchema{
ID: "module-v1",
Name: "Debian Module Schema v1",
Description: "Schema for Debian atomic image modules",
Type: "module",
Version: "1.0.0",
Source: "debian-adapted",
Adapted: true,
Enabled: true,
Metadata: map[string]interface{}{
"base_schema": "blue-build-module-v1",
"target_os": "debian",
},
}
// Stage schema (Debian-adapted)
dsm.schemas["stage-v1"] = DebianSchema{
ID: "stage-v1",
Name: "Debian Stage Schema v1",
Description: "Schema for Debian atomic image build stages",
Type: "stage",
Version: "1.0.0",
Source: "debian-adapted",
Adapted: true,
Enabled: true,
Metadata: map[string]interface{}{
"base_schema": "blue-build-stage-v1",
"target_os": "debian",
},
}
// Debian-specific schemas
dsm.schemas["debian-package-v1"] = DebianSchema{
ID: "debian-package-v1",
Name: "Debian Package Schema v1",
Description: "Schema for Debian package management",
Type: "debian-package",
Version: "1.0.0",
Source: "debian-native",
Adapted: false,
Enabled: true,
Metadata: map[string]interface{}{
"package_manager": "apt",
"package_format": "deb",
},
}
dsm.schemas["debian-repository-v1"] = DebianSchema{
ID: "debian-repository-v1",
Name: "Debian Repository Schema v1",
Description: "Schema for Debian repository management",
Type: "debian-repository",
Version: "1.0.0",
Source: "debian-native",
Adapted: false,
Enabled: true,
Metadata: map[string]interface{}{
"repository_type": "deb",
"key_format": "gpg",
},
}
}
// initializeSchemaValidations initializes schema validation rules
func (dsm *DebianSchemaManager) initializeSchemaValidations() {
// Recipe validation
dsm.validations["recipe-validation"] = SchemaValidation{
ID: "recipe-validation",
SchemaID: "recipe-v1",
Name: "Recipe Validation Rules",
Description: "Validation rules for Debian recipe schemas",
Type: "validation",
Rules: map[string]interface{}{
"required_fields": []string{"name", "description", "base-image", "modules"},
"field_types": map[string]string{
"name": "string",
"description": "string",
"base-image": "string",
"modules": "array",
},
"constraints": map[string]interface{}{
"name_min_length": 3,
"name_max_length": 50,
},
},
Enabled: true,
}
// Module validation
dsm.validations["module-validation"] = SchemaValidation{
ID: "module-validation",
SchemaID: "module-v1",
Name: "Module Validation Rules",
Description: "Validation rules for Debian module schemas",
Type: "validation",
Rules: map[string]interface{}{
"required_fields": []string{"type"},
"field_types": map[string]string{
"type": "string",
},
"valid_types": []string{"apt", "dpkg", "debian-release", "debian-kernel", "debian-initramfs"},
},
Enabled: true,
}
// Debian package validation
dsm.validations["debian-package-validation"] = SchemaValidation{
ID: "debian-package-validation",
SchemaID: "debian-package-v1",
Name: "Debian Package Validation Rules",
Description: "Validation rules for Debian package schemas",
Type: "validation",
Rules: map[string]interface{}{
"required_fields": []string{"packages"},
"field_types": map[string]string{
"packages": "array",
},
"constraints": map[string]interface{}{
"package_name_format": "^[a-z0-9][a-z0-9+.-]*$",
},
},
Enabled: true,
}
}
// initializeSchemaAdaptations initializes adaptations from blue-build schemas
func (dsm *DebianSchemaManager) initializeSchemaAdaptations() {
// Recipe schema adaptation
dsm.adaptations["recipe-adaptation"] = SchemaAdaptation{
ID: "recipe-adaptation",
OriginalID: "blue-build-recipe-v1",
Name: "Recipe Schema Adaptation",
Description: "Adapt blue-build recipe schema for Debian",
Type: "adaptation",
Changes: []string{
"Replace Fedora base images with Debian base images",
"Update platform definitions for Debian",
"Adapt module types for Debian compatibility",
"Update validation rules for Debian requirements",
},
Status: "completed",
Enabled: true,
Metadata: map[string]interface{}{
"original_schema": "blue-build-recipe-v1",
"target_schema": "debian-recipe-v1",
"compatibility": "high",
},
}
// Module schema adaptation
dsm.adaptations["module-adaptation"] = SchemaAdaptation{
ID: "module-adaptation",
OriginalID: "blue-build-module-v1",
Name: "Module Schema Adaptation",
Description: "Adapt blue-build module schema for Debian",
Type: "adaptation",
Changes: []string{
"Replace DNF module with APT module",
"Replace RPM-OSTree module with DPKG module",
"Add Debian-specific module types",
"Update validation rules for Debian modules",
},
Status: "completed",
Enabled: true,
Metadata: map[string]interface{}{
"original_schema": "blue-build-module-v1",
"target_schema": "debian-module-v1",
"compatibility": "high",
},
}
// Stage schema adaptation
dsm.adaptations["stage-adaptation"] = SchemaAdaptation{
ID: "stage-adaptation",
OriginalID: "blue-build-stage-v1",
Name: "Stage Schema Adaptation",
Description: "Adapt blue-build stage schema for Debian",
Type: "adaptation",
Changes: []string{
"Update base image references for Debian",
"Adapt package manager commands",
"Update file paths for Debian structure",
"Ensure Debian compatibility in build stages",
},
Status: "completed",
Enabled: true,
Metadata: map[string]interface{}{
"original_schema": "blue-build-stage-v1",
"target_schema": "debian-stage-v1",
"compatibility": "high",
},
}
}
// GetSchema returns a schema by ID
func (dsm *DebianSchemaManager) GetSchema(schemaID string) (*DebianSchema, error) {
dsm.mu.RLock()
defer dsm.mu.RUnlock()
schema, exists := dsm.schemas[schemaID]
if !exists {
return nil, fmt.Errorf("schema not found: %s", schemaID)
}
return &schema, nil
}
// GetValidation returns a validation by ID
func (dsm *DebianSchemaManager) GetValidation(validationID string) (*SchemaValidation, error) {
dsm.mu.RLock()
defer dsm.mu.RUnlock()
validation, exists := dsm.validations[validationID]
if !exists {
return nil, fmt.Errorf("validation not found: %s", validationID)
}
return &validation, nil
}
// GetAdaptation returns an adaptation by ID
func (dsm *DebianSchemaManager) GetAdaptation(adaptationID string) (*SchemaAdaptation, error) {
dsm.mu.RLock()
defer dsm.mu.RUnlock()
adaptation, exists := dsm.adaptations[adaptationID]
if !exists {
return nil, fmt.Errorf("adaptation not found: %s", adaptationID)
}
return &adaptation, nil
}
// ListSchemas returns all available schemas
func (dsm *DebianSchemaManager) ListSchemas() []DebianSchema {
dsm.mu.RLock()
defer dsm.mu.RUnlock()
schemas := make([]DebianSchema, 0, len(dsm.schemas))
for _, schema := range dsm.schemas {
if schema.Enabled {
schemas = append(schemas, schema)
}
}
return schemas
}
// ListValidations returns all available validations
func (dsm *DebianSchemaManager) ListValidations() []SchemaValidation {
dsm.mu.RLock()
defer dsm.mu.RUnlock()
validations := make([]SchemaValidation, 0, len(dsm.validations))
for _, validation := range dsm.validations {
if validation.Enabled {
validations = append(validations, validation)
}
}
return validations
}
// ListAdaptations returns all available adaptations
func (dsm *DebianSchemaManager) ListAdaptations() []SchemaAdaptation {
dsm.mu.RLock()
defer dsm.mu.RUnlock()
adaptations := make([]SchemaAdaptation, 0, len(dsm.adaptations))
for _, adaptation := range dsm.adaptations {
if adaptation.Enabled {
adaptations = append(adaptations, adaptation)
}
}
return adaptations
}
// ValidateSchema validates a schema against its validation rules
func (dsm *DebianSchemaManager) ValidateSchema(schemaID string, data map[string]interface{}) error {
schema, err := dsm.GetSchema(schemaID)
if err != nil {
return err
}
// Get validation rules for this schema
validation, err := dsm.getValidationForSchema(schemaID)
if err != nil {
return fmt.Errorf("no validation rules found for schema: %s", schemaID)
}
// Apply validation rules
return dsm.applyValidationRules(validation, data)
}
// getValidationForSchema gets validation rules for a specific schema
func (dsm *DebianSchemaManager) getValidationForSchema(schemaID string) (*SchemaValidation, error) {
for _, validation := range dsm.validations {
if validation.SchemaID == schemaID && validation.Enabled {
return &validation, nil
}
}
return nil, fmt.Errorf("validation not found for schema: %s", schemaID)
}
// applyValidationRules applies validation rules to data
func (dsm *DebianSchemaManager) applyValidationRules(validation *SchemaValidation, data map[string]interface{}) error {
rules := validation.Rules
// Check required fields
if requiredFields, ok := rules["required_fields"].([]string); ok {
for _, field := range requiredFields {
if _, exists := data[field]; !exists {
return fmt.Errorf("required field missing: %s", field)
}
}
}
// Check field types
if fieldTypes, ok := rules["field_types"].(map[string]string); ok {
for field, expectedType := range fieldTypes {
if value, exists := data[field]; exists {
if err := dsm.validateFieldType(field, value, expectedType); err != nil {
return err
}
}
}
}
// Check constraints
if constraints, ok := rules["constraints"].(map[string]interface{}); ok {
for constraint, value := range constraints {
if err := dsm.validateConstraint(constraint, data, value); err != nil {
return err
}
}
}
return nil
}
// validateFieldType validates a field's type
func (dsm *DebianSchemaManager) validateFieldType(field string, value interface{}, expectedType string) error {
switch expectedType {
case "string":
if _, ok := value.(string); !ok {
return fmt.Errorf("field %s must be a string", field)
}
case "array":
if _, ok := value.([]interface{}); !ok {
return fmt.Errorf("field %s must be an array", field)
}
case "integer":
if _, ok := value.(int); !ok {
return fmt.Errorf("field %s must be an integer", field)
}
case "boolean":
if _, ok := value.(bool); !ok {
return fmt.Errorf("field %s must be a boolean", field)
}
default:
return fmt.Errorf("unknown field type: %s", expectedType)
}
return nil
}
// validateConstraint validates a constraint
func (dsm *DebianSchemaManager) validateConstraint(constraint string, data map[string]interface{}, constraintValue interface{}) error {
switch constraint {
case "name_min_length":
if name, ok := data["name"].(string); ok {
if minLength, ok := constraintValue.(int); ok {
if len(name) < minLength {
return fmt.Errorf("name must be at least %d characters long", minLength)
}
}
}
case "name_max_length":
if name, ok := data["name"].(string); ok {
if maxLength, ok := constraintValue.(int); ok {
if len(name) > maxLength {
return fmt.Errorf("name must be at most %d characters long", maxLength)
}
}
}
}
return nil
}
// CreateSchemaTemplate creates a template for a schema
func (dsm *DebianSchemaManager) CreateSchemaTemplate(schemaID string) (map[string]interface{}, error) {
schema, err := dsm.GetSchema(schemaID)
if err != nil {
return nil, err
}
// Create schema-specific templates
switch schema.Type {
case "recipe":
return dsm.createRecipeTemplate()
case "module":
return dsm.createModuleTemplate()
case "stage":
return dsm.createStageTemplate()
case "debian-package":
return dsm.createDebianPackageTemplate()
case "debian-repository":
return dsm.createDebianRepositoryTemplate()
default:
return nil, fmt.Errorf("unknown schema type: %s", schema.Type)
}
}
// createRecipeTemplate creates a recipe schema template
func (dsm *DebianSchemaManager) createRecipeTemplate() map[string]interface{} {
return map[string]interface{}{
"name": "debian-atomic-example",
"description": "Example Debian atomic image",
"base-image": "debian:bookworm-slim",
"image-version": "latest",
"platforms": []string{
"linux/amd64",
"linux/arm64",
},
"modules": []map[string]interface{}{
{
"type": "debian-release",
"release": "bookworm",
},
{
"type": "apt",
"install": map[string]interface{}{
"packages": []string{
"curl",
"wget",
},
},
},
},
}
}
// createModuleTemplate creates a module schema template
func (dsm *DebianSchemaManager) createModuleTemplate() map[string]interface{} {
return map[string]interface{}{
"type": "apt",
"repos": map[string]interface{}{
"files": []string{
"https://example.com/debian-repo.list",
},
},
"install": map[string]interface{}{
"packages": []string{
"package1",
"package2",
},
},
}
}
// createStageTemplate creates a stage schema template
func (dsm *DebianSchemaManager) createStageTemplate() map[string]interface{} {
return map[string]interface{}{
"name": "debian-setup",
"base-image": "debian:bookworm-slim",
"commands": []string{
"apt update",
"apt install -y curl wget",
},
}
}
// createDebianPackageTemplate creates a Debian package schema template
func (dsm *DebianSchemaManager) createDebianPackageTemplate() map[string]interface{} {
return map[string]interface{}{
"type": "debian-package",
"packages": []string{
"curl",
"wget",
"git",
},
"repositories": []string{
"main",
"contrib",
"non-free",
},
}
}
// createDebianRepositoryTemplate creates a Debian repository schema template
func (dsm *DebianSchemaManager) createDebianRepositoryTemplate() map[string]interface{} {
return map[string]interface{}{
"type": "debian-repository",
"name": "example-repo",
"url": "https://example.com/debian",
"distribution": "bookworm",
"components": []string{
"main",
"contrib",
},
"key": "https://example.com/debian-repo.gpg",
}
}