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
613 lines
17 KiB
Go
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",
|
|
}
|
|
}
|