From 288ed0a24973ca9216e130845995b838ca6ad955 Mon Sep 17 00:00:00 2001 From: Lukas Greve Date: Fri, 27 Mar 2026 12:13:51 +0100 Subject: [PATCH] refactor: Simplify template logic - reduce from 6 to 3 categories, remove variant_type, keep validation, remove tests Simplified template logic: reduced from 6 categories (required, modifiers, optional, versioned, conditional, flags) to 3 categories (required, modifiers, optional). Removed variant_type and replaced with modifier-based approach (version, bootloader replaced, hardware_support). All validation methods kept for recipe generation and validation. Removed tests directory (tests/test_recipe_generator.py, tests/integration/, tests/container/). Removed non-ISO workflows (validate-recipes, test-generation, container-tests, validate-ingredients). Updated Makefile, requirements.txt, and documentation. All 20 recipes successfully generated, validated, and flattened. --- .gitea/workflows/container-tests.yaml | 38 --- .gitea/workflows/test-generation.yaml | 51 ---- .gitea/workflows/validate-ingredients.yaml | 42 --- .gitea/workflows/validate-recipes.yaml | 50 ---- DEVELOPMENT.md | 116 -------- DEVELOPMENT_QUICK.md | 11 +- README.md | 149 +--------- ..._encrypted_hardware-support_server_43.cfg} | 0 ...ypted_hardware-support_server_rawhide.cfg} | 0 ..._bare-metal_gnome_encrypted_server_43.cfg} | 0 ...-metal_gnome_encrypted_server_rawhide.cfg} | 0 ...etal_gnome_hardware-support_server_43.cfg} | 0 ...gnome_hardware-support_server_rawhide.cfg} | 0 ...=> install_bare-metal_gnome_server_43.cfg} | 0 ...stall_bare-metal_gnome_server_rawhide.cfg} | 0 ...sor_amdcpu_hardware-support_server_43.cfg} | 0 ...r_intelcpu_hardware-support_server_43.cfg} | 0 ..._encrypted_hardware-support_server_43.cfg} | 0 ...ypted_hardware-support_server_rawhide.cfg} | 0 ...all_virtual_gnome_encrypted_server_43.cfg} | 0 ...irtual_gnome_encrypted_server_rawhide.cfg} | 0 ...tual_gnome_hardware-support_server_43.cfg} | 0 ...gnome_hardware-support_server_rawhide.cfg} | 0 ...fg => install_virtual_gnome_server_43.cfg} | 0 ... install_virtual_gnome_server_rawhide.cfg} | 0 ...rvisor-desktop_amdcpu_gnome_server_43.cfg} | 0 ...isor-desktop_intelcpu_gnome_server_43.cfg} | 0 ...> live_bare-metal_hardware-support_43.cfg} | 0 dishes/live_bare-metal_server_hw_43.cfg | 114 -------- recipe-generator/Makefile | 17 +- .../__pycache__/cli.cpython-314.pyc | Bin 16837 -> 22887 bytes .../generate_recipe.cpython-314.pyc | Bin 39257 -> 1021 bytes .../__pycache__/manifest.cpython-314.pyc | Bin 3923 -> 7922 bytes ...ython-update-package-names.cpython-314.pyc | Bin 0 -> 2556 bytes .../recipe_generator.cpython-314.pyc | Bin 21862 -> 32281 bytes .../__pycache__/validators.cpython-314.pyc | Bin 8883 -> 16749 bytes recipe-generator/cli.py | 12 +- recipe-generator/recipe_generator.py | 29 +- recipe-generator/recipe_templates.yaml | 77 +++-- recipe-generator/recipes_manifest.yaml | 52 ++-- recipe-generator/requirements.txt | 1 - ..._encrypted_hardware-support_server_43.cfg} | 0 ...ypted_hardware-support_server_rawhide.cfg} | 0 ..._bare-metal_gnome_encrypted_server_43.cfg} | 0 ...-metal_gnome_encrypted_server_rawhide.cfg} | 0 ...etal_gnome_hardware-support_server_43.cfg} | 0 ...gnome_hardware-support_server_rawhide.cfg} | 0 ...=> install_bare-metal_gnome_server_43.cfg} | 0 ...stall_bare-metal_gnome_server_rawhide.cfg} | 0 ...sor_amdcpu_hardware-support_server_43.cfg} | 0 ...r_intelcpu_hardware-support_server_43.cfg} | 0 ..._encrypted_hardware-support_server_43.cfg} | 0 ...ypted_hardware-support_server_rawhide.cfg} | 0 ...all_virtual_gnome_encrypted_server_43.cfg} | 0 ...irtual_gnome_encrypted_server_rawhide.cfg} | 0 ...tual_gnome_hardware-support_server_43.cfg} | 0 ...gnome_hardware-support_server_rawhide.cfg} | 0 ...fg => install_virtual_gnome_server_43.cfg} | 0 ... install_virtual_gnome_server_rawhide.cfg} | 0 ...rvisor-desktop_amdcpu_gnome_server_43.cfg} | 0 ...isor-desktop_intelcpu_gnome_server_43.cfg} | 0 ...> live_bare-metal_hardware-support_43.cfg} | 0 recipes/live_bare-metal_server_hw_43.cfg | 20 -- ...ipe_generator.cpython-314-pytest-8.4.2.pyc | Bin 43079 -> 0 bytes tests/container/Containerfile | 25 -- tests/container/README.md | 129 --------- tests/container/run-tests.sh | 43 --- .../desktop-hypervisor-intelcpu-rawhide.cfg | 24 -- .../desktop-hypervisor-intelgpu-rawhide.cfg | 24 -- .../desktop-hypervisor_rawhide.cfg | 24 -- .../expected_recipes/live-desktop_rawhide.cfg | 23 -- .../expected_recipes/live-server_rawhide.cfg | 20 -- .../expected_recipes/virtual-desktop_43.cfg | 20 -- .../virtual-server_rawhide.cfg | 20 -- .../conftest.cpython-314-pytest-8.4.2.pyc | Bin 2411 -> 0 bytes ...t_integration.cpython-314-pytest-8.4.2.pyc | Bin 14153 -> 0 bytes tests/integration/conftest.py | 50 ---- tests/integration/test_integration.py | 97 ------- tests/test_recipe_generator.py | 269 ------------------ 79 files changed, 83 insertions(+), 1464 deletions(-) delete mode 100644 .gitea/workflows/container-tests.yaml delete mode 100644 .gitea/workflows/test-generation.yaml delete mode 100644 .gitea/workflows/validate-ingredients.yaml delete mode 100644 .gitea/workflows/validate-recipes.yaml rename dishes/{install_bare-metal_desktop_encrypted_43.cfg => install_bare-metal_gnome_encrypted_hardware-support_server_43.cfg} (100%) rename dishes/{install_bare-metal_desktop_encrypted_hw_rawhide.cfg => install_bare-metal_gnome_encrypted_hardware-support_server_rawhide.cfg} (100%) rename dishes/{install_bare-metal_desktop_encrypted_hw_43.cfg => install_bare-metal_gnome_encrypted_server_43.cfg} (100%) rename dishes/{install_bare-metal_desktop_encrypted_rawhide.cfg => install_bare-metal_gnome_encrypted_server_rawhide.cfg} (100%) rename dishes/{install_bare-metal_desktop_43.cfg => install_bare-metal_gnome_hardware-support_server_43.cfg} (100%) rename dishes/{install_bare-metal_desktop_hw_rawhide.cfg => install_bare-metal_gnome_hardware-support_server_rawhide.cfg} (100%) rename dishes/{install_bare-metal_desktop_hw_43.cfg => install_bare-metal_gnome_server_43.cfg} (100%) rename dishes/{install_bare-metal_desktop_rawhide.cfg => install_bare-metal_gnome_server_rawhide.cfg} (100%) rename dishes/{install_bare-metal_hypervisor_amdcpu_hw_43.cfg => install_bare-metal_hypervisor_amdcpu_hardware-support_server_43.cfg} (100%) rename dishes/{install_bare-metal_hypervisor_intelcpu_hw_43.cfg => install_bare-metal_hypervisor_intelcpu_hardware-support_server_43.cfg} (100%) rename dishes/{install_virtual_desktop_encrypted_43.cfg => install_virtual_gnome_encrypted_hardware-support_server_43.cfg} (100%) rename dishes/{install_virtual_desktop_encrypted_hw_rawhide.cfg => install_virtual_gnome_encrypted_hardware-support_server_rawhide.cfg} (100%) rename dishes/{install_virtual_desktop_encrypted_hw_43.cfg => install_virtual_gnome_encrypted_server_43.cfg} (100%) rename dishes/{install_virtual_desktop_encrypted_rawhide.cfg => install_virtual_gnome_encrypted_server_rawhide.cfg} (100%) rename dishes/{install_virtual_desktop_43.cfg => install_virtual_gnome_hardware-support_server_43.cfg} (100%) rename dishes/{install_virtual_desktop_hw_rawhide.cfg => install_virtual_gnome_hardware-support_server_rawhide.cfg} (100%) rename dishes/{install_virtual_desktop_hw_43.cfg => install_virtual_gnome_server_43.cfg} (100%) rename dishes/{install_virtual_desktop_rawhide.cfg => install_virtual_gnome_server_rawhide.cfg} (100%) rename dishes/{install_virtual_hypervisor-desktop_amdcpu_43.cfg => install_virtual_hypervisor-desktop_amdcpu_gnome_server_43.cfg} (100%) rename dishes/{install_virtual_hypervisor-desktop_intelcpu_43.cfg => install_virtual_hypervisor-desktop_intelcpu_gnome_server_43.cfg} (100%) rename dishes/{live_bare-metal_desktop_hw_43.cfg => live_bare-metal_hardware-support_43.cfg} (100%) delete mode 100644 dishes/live_bare-metal_server_hw_43.cfg create mode 100644 recipe-generator/__pycache__/python-update-package-names.cpython-314.pyc rename recipes/{install_bare-metal_desktop_encrypted_43.cfg => install_bare-metal_gnome_encrypted_hardware-support_server_43.cfg} (100%) rename recipes/{install_bare-metal_desktop_encrypted_hw_rawhide.cfg => install_bare-metal_gnome_encrypted_hardware-support_server_rawhide.cfg} (100%) rename recipes/{install_bare-metal_desktop_encrypted_hw_43.cfg => install_bare-metal_gnome_encrypted_server_43.cfg} (100%) rename recipes/{install_bare-metal_desktop_encrypted_rawhide.cfg => install_bare-metal_gnome_encrypted_server_rawhide.cfg} (100%) rename recipes/{install_bare-metal_desktop_43.cfg => install_bare-metal_gnome_hardware-support_server_43.cfg} (100%) rename recipes/{install_bare-metal_desktop_hw_rawhide.cfg => install_bare-metal_gnome_hardware-support_server_rawhide.cfg} (100%) rename recipes/{install_bare-metal_desktop_hw_43.cfg => install_bare-metal_gnome_server_43.cfg} (100%) rename recipes/{install_bare-metal_desktop_rawhide.cfg => install_bare-metal_gnome_server_rawhide.cfg} (100%) rename recipes/{install_bare-metal_hypervisor_amdcpu_hw_43.cfg => install_bare-metal_hypervisor_amdcpu_hardware-support_server_43.cfg} (100%) rename recipes/{install_bare-metal_hypervisor_intelcpu_hw_43.cfg => install_bare-metal_hypervisor_intelcpu_hardware-support_server_43.cfg} (100%) rename recipes/{install_virtual_desktop_encrypted_43.cfg => install_virtual_gnome_encrypted_hardware-support_server_43.cfg} (100%) rename recipes/{install_virtual_desktop_encrypted_hw_rawhide.cfg => install_virtual_gnome_encrypted_hardware-support_server_rawhide.cfg} (100%) rename recipes/{install_virtual_desktop_encrypted_hw_43.cfg => install_virtual_gnome_encrypted_server_43.cfg} (100%) rename recipes/{install_virtual_desktop_encrypted_rawhide.cfg => install_virtual_gnome_encrypted_server_rawhide.cfg} (100%) rename recipes/{install_virtual_desktop_43.cfg => install_virtual_gnome_hardware-support_server_43.cfg} (100%) rename recipes/{install_virtual_desktop_hw_rawhide.cfg => install_virtual_gnome_hardware-support_server_rawhide.cfg} (100%) rename recipes/{install_virtual_desktop_hw_43.cfg => install_virtual_gnome_server_43.cfg} (100%) rename recipes/{install_virtual_desktop_rawhide.cfg => install_virtual_gnome_server_rawhide.cfg} (100%) rename recipes/{install_virtual_hypervisor-desktop_amdcpu_43.cfg => install_virtual_hypervisor-desktop_amdcpu_gnome_server_43.cfg} (100%) rename recipes/{install_virtual_hypervisor-desktop_intelcpu_43.cfg => install_virtual_hypervisor-desktop_intelcpu_gnome_server_43.cfg} (100%) rename recipes/{live_bare-metal_desktop_hw_43.cfg => live_bare-metal_hardware-support_43.cfg} (100%) delete mode 100644 recipes/live_bare-metal_server_hw_43.cfg delete mode 100644 tests/__pycache__/test_recipe_generator.cpython-314-pytest-8.4.2.pyc delete mode 100644 tests/container/Containerfile delete mode 100644 tests/container/README.md delete mode 100755 tests/container/run-tests.sh delete mode 100644 tests/fixtures/expected_recipes/desktop-hypervisor-intelcpu-rawhide.cfg delete mode 100644 tests/fixtures/expected_recipes/desktop-hypervisor-intelgpu-rawhide.cfg delete mode 100644 tests/fixtures/expected_recipes/desktop-hypervisor_rawhide.cfg delete mode 100644 tests/fixtures/expected_recipes/live-desktop_rawhide.cfg delete mode 100644 tests/fixtures/expected_recipes/live-server_rawhide.cfg delete mode 100644 tests/fixtures/expected_recipes/virtual-desktop_43.cfg delete mode 100644 tests/fixtures/expected_recipes/virtual-server_rawhide.cfg delete mode 100644 tests/integration/__pycache__/conftest.cpython-314-pytest-8.4.2.pyc delete mode 100644 tests/integration/__pycache__/test_integration.cpython-314-pytest-8.4.2.pyc delete mode 100644 tests/integration/conftest.py delete mode 100644 tests/integration/test_integration.py delete mode 100644 tests/test_recipe_generator.py diff --git a/.gitea/workflows/container-tests.yaml b/.gitea/workflows/container-tests.yaml deleted file mode 100644 index a5d9794..0000000 --- a/.gitea/workflows/container-tests.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: container-tests - -on: - push: - paths: - - 'tests/**/*.py' - - 'tests/container/**' - pull_request: - paths: - - 'tests/**/*.py' - -jobs: - test: - runs-on: fedora - container: - image: git.phyllo.me/devops/fedora-runner-image:latest - - steps: - - uses: https://git.phyllo.me/devops/checkout@v5 - with: - fetch-depth: 0 - - - name: Build test container - run: | - cd recipe-generator - podman build -t phyllo/test-runner ../tests/container/ - - - name: Run tests in container - run: | - podman run --rm -v $(pwd):/phyllomeos:ro phyllo/test-runner - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: container-test-results - path: _pytest_cache/ - if-no-files-found: ignore diff --git a/.gitea/workflows/test-generation.yaml b/.gitea/workflows/test-generation.yaml deleted file mode 100644 index b71eb60..0000000 --- a/.gitea/workflows/test-generation.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: test-generation - -on: - push: - paths: - - 'recipe-generator/**/*.py' - - 'recipe-generator/**/*.yaml' - - 'ingredients/**/*.ks' - pull_request: - paths: - - 'recipe-generator/**/*.py' - - 'recipe-generator/**/*.yaml' - -jobs: - generate: - runs-on: fedora - container: - image: git.phyllo.me/devops/fedora-runner-image:latest - - steps: - - uses: https://git.phyllo.me/devops/checkout@v5 - with: - fetch-depth: 0 - - - name: Install dependencies - run: | - pip install PyYAML pykickstart - - - name: Generate all variants - run: | - cd recipe-generator - python3 generate_recipe.py \ - --manifest recipes_manifest.yaml \ - --output-dir ../recipes/ - - - name: Verify 16 recipes created - run: | - count=$(ls recipes/*.cfg | wc -l) - if [ $count -ne 16 ]; then - echo "Expected 16 recipes, got $count" - exit 1 - fi - echo "✓ Generated $count recipes" - - - name: Upload generated recipes - uses: actions/upload-artifact@v3 - with: - name: generated-recipes - path: recipes/ - if-no-files-found: error - retention-days: 7 diff --git a/.gitea/workflows/validate-ingredients.yaml b/.gitea/workflows/validate-ingredients.yaml deleted file mode 100644 index 7532b75..0000000 --- a/.gitea/workflows/validate-ingredients.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: validate-ingredients - -on: - push: - paths: - - 'ingredients/**/*.ks' - pull_request: - paths: - - 'ingredients/**/*.ks' - -jobs: - validate: - runs-on: fedora - container: - image: git.phyllo.me/devops/fedora-runner-image:latest - - steps: - - uses: https://git.phyllo.me/devops/checkout@v5 - with: - fetch-depth: 0 - - - name: Install pykickstart - run: pip install pykickstart - - - name: Validate all ingredients - run: | - errors=0 - for ingredient in $(find ingredients -name "*.ks"); do - if python3 -c " - from pykickstart.parser import KickstartParser - from pykickstart.version import makeVersion, DEVEL - parser = KickstartParser(makeVersion(DEVEL)) - parser.readKickstart(open('$ingredient').read()) - " 2>/dev/null; then - echo "✓ $ingredient" - else - echo "✗ $ingredient" - errors=$((errors + 1)) - fi - done - echo "Ingredient validation complete: $errors errors" - exit $errors diff --git a/.gitea/workflows/validate-recipes.yaml b/.gitea/workflows/validate-recipes.yaml deleted file mode 100644 index dd7122d..0000000 --- a/.gitea/workflows/validate-recipes.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: validate-recipes - -on: - push: - branches: [main, develop] - pull_request: - branches: [main] - -jobs: - validate: - runs-on: fedora - container: - image: git.phyllo.me/devops/fedora-runner-image:latest - - steps: - - uses: https://git.phyllo.me/devops/checkout@v5 - with: - fetch-depth: 0 - - - name: Install dependencies - run: | - pip install PyYAML pytest pykickstart - - - name: Generate all recipes - run: | - cd recipe-generator - make generate-recipes - - - name: Validate recipes (strict mode) - run: | - cd recipe-generator - python3 generate_recipe.py --validate ../recipes/*.cfg --strict - - - name: Run unit tests - run: | - cd recipe-generator - make test - - - name: Run integration tests - run: | - cd recipe-generator - make test-integration - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: test-results - path: _pytest_cache/ - if-no-files-found: ignore diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ba84780..d707172 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -392,122 +392,6 @@ done --- -## Testing - -Phyllome OS uses a comprehensive test suite with 36+ tests covering unit, integration, and regression scenarios. - -### Test Suite Structure - -| Test File | Tests | Coverage | -|-----------|-------|----------| -| `tests/test_recipe_generator.py` | 36 | Unit tests for RecipeGenerator | -| `tests/integration/test_integration.py` | 5+ | End-to-end workflow tests | -| `tests/integration/test_ingredients.py` | ~15 | Fragment validation | -| `tests/integration/test_recipe_composition.py` | ~10 | Recipe generation | -| `tests/integration/test_semantic_validation.py` | ~10 | pykickstart validation | -| `tests/integration/test_golden_masters.py` | ~5 | Regression tests | - -### Running Tests - -```bash -cd scripts - -# All tests (unit + integration) -make test - -# Unit tests only -python3 -m pytest tests/test_recipe_generator.py -v - -# Integration tests only -make test-integration - -# Containerized tests -make test-container -# or: podman run --rm -v .:/phyllomeos:ro phyllo/test-runner -``` - -**Expected output:** -``` -============================= test session starts ============================= -collected 41 items - -tests/test_recipe_generator.py ............. [ 29%] -tests/integration/test_integration.py ..... [ 43%] -... - -============================== 41 passed in 2.34s ============================== -``` - -### Fragment Validation Test - -```bash -# Test all ingredients with pykickstart -for fragment in $(find ingredients -name "*.ks"); do - python3 -c " -from pykickstart.parser import KickstartParser -from pykickstart.version import makeVersion, DEVEL -parser = KickstartParser(makeVersion(DEVEL)) -parser.readKickstart(open('$fragment').read()) -" && echo "✓ $fragment" || echo "✗ $fragment" -done -``` - -### Adding New Tests - -**Unit test example:** - -```python -# tests/test_recipe_generator.py - -def test_generate_recipe_with_new_modifier(): - """Test recipe generation with custom modifier.""" - content = self.generator.generate_recipe( - 'virtual-desktop', '43', - desktop='gnome', - storage='standard', - security='secure' - ) - assert '# A recipe for a virtual desktop' in content - assert '%ksappend ingredients/shared/desktop/gnome/packages.ks' in content -``` - -**Integration test example:** - -```python -# tests/integration/test_recipe_composition.py - -def test_manifest_generates_correct_count(): - """Test manifest generates expected number of recipes.""" - result = subprocess.run( - ['python3', 'generate_recipe.py', - '--manifest', 'recipes_manifest.yaml', - '--output-dir', '../recipes/'], - capture_output=True, text=True - ) - assert result.returncode == 0 - recipe_count = len(list(Path('../recipes').glob('*.cfg'))) - assert recipe_count == 16 -``` - -### Golden Master Tests - -Golden masters (expected outputs) are stored in `tests/fixtures/expected_recipes/`: - -```bash -# View expected output for virtual-desktop -cat tests/fixtures/expected_recipes/virtual-desktop_43.cfg -``` - -**To update golden masters** (after intentional changes): - -```bash -# Generate and compare -python3 -m pytest tests/integration/test_golden_masters.py -v -``` - ---- - -## CI/CD The project uses Gitea Actions for automated testing and building. diff --git a/DEVELOPMENT_QUICK.md b/DEVELOPMENT_QUICK.md index 642108f..c166354 100644 --- a/DEVELOPMENT_QUICK.md +++ b/DEVELOPMENT_QUICK.md @@ -7,10 +7,10 @@ This is a quick-reference guide for developers. For comprehensive coverage, see ```bash # Prerequisites sudo dnf install qemu libvirt virt-install pykickstart -pip install PyYAML pytest +pip install PyYAML # Verify setup -cd recipe-generator && make generate-recipes && make test +cd recipe-generator && make generate-recipes ``` ## Core Workflows @@ -35,13 +35,6 @@ new-package cd recipe-generator && make generate-recipes && make validate-recipes ``` -### Run Tests - -```bash -cd recipe-generator -make test # All tests -make test-integration # Integration only -make test-container # Containerized ``` ### Validate Fragments diff --git a/README.md b/README.md index 1d61d03..b40e51a 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,9 @@ Provided that some dependencies are met (`libvirt` is running on your computer, QEMU is installed, etc), one could run the following script to deploy virtual machines, including Phyllome OS itself. -- Make the script executable: +- : ``` -chmod +x deploy-vm.sh ``` - Execute it and pick `virtual-desktop-hypervisor` when prompted: @@ -49,149 +48,3 @@ Each ingredient represents a feature or a set of integrated features, such as a - Ingredients prefixed with *live* such as `live-core.cfg` are to be used with live editions only - *core* ingredients are meant be used in all their respective recipes, *base* ingredients, recommended but optional, and extra provides more stuff (sic) -## Development - -Using a pull request, you can suggest a modification to an existing ingredient or create a new ingredient from scratch. - -### Requirements - -- `qemu` -- `libvirt` -- `virt-install` -- `pykickstart` - -### Example 1: add a new package and include it into a recipe - -- Add [Luanti](https://www.luanti.org/), a free and open-source sandbox video game engine formerly known as Minetest, as a standalone ingredient, using the `echo` command - -``` -echo "%packages --exclude-weakdeps # Beginning of the package section. Does not include weak dependencies - -luanti # Multiplayer infinite-world block sandbox with survival mode - -%end # End of the packages section" > ingredients/extra-luanti.cfg -``` - -Instead of creating a recipe from scratch, let's make a copy of the `virtual-desktop.cfg` recipe, which provide a Desktop environment necessary for *luanti* to function - -``` -cp recipes/virtual-desktop.cfg recipes/virtual-desktop-luanti.cfg -``` - -- Add the extra ingredient to the new recipe: - -``` -echo "%include ../ingredients/extra-luanti.cfg # Sandbox video game engine" >> recipes/virtual-desktop-luanti.cfg -``` - -#### Flatten - -- Prepare the dish by following the recipe, a process called 'flattening' - -``` -ksflatten -c recipes/virtual-desktop-luanti.cfg -o dishes/virtual-desktop-luanti.cfg -``` - -> If any errors are detected, go back and fix them. - -It is time to test the new dish! - -#### Kickstart - -- Make the `deploy-vm.sh` script executable - -``` -chmod +x deploy-vm.sh -``` - -- Execute the script - -``` -./deploy-vm.sh -``` - -- Select the new dish, *virtual-desktop-luanti* - -``` -[...] -Available files: -1. desktop-hypervisor-amdcpu -[...] -14. virtual-desktop-luanti -``` - -- When the installation is done, the machine will shut down - -- Start it again, and ensure that Luanti has correctly been installed - -That's it ! - -### Example 2: Create a new recipe from the existing list of ingredients - -The file `recipes/_list-of-ingredients.cfg` can be copied and edited to create your own remix of Phyllome OS, which itself is a remix of Fedora. - -``` -cp recipes/_list-of-ingredients.cfg recipes/my-new-distro.cfg -``` - -Then edit the said file to include your favorite ingredient - -``` -nano recipes/my-new-distro.cfg -``` - -``` -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# The list of ingredients for composing Phyllome OS -# Uncomment lines with "%include" to enable ingredient - -# Installation method -# Exactly one option has to be picked -# %include ../ingredients/core.cfg # Text mode -# %include ../ingredients/live-core.cfg # For live systems only -# Documentation: https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#graphical-or-text-or-cmdline - -# Storage configuration -# Exactly one option has to be picked -# WARNING !!! Will erase local disks! -# %include ../ingredients/core-storage.cfg # Basic ext4 partition layout for UEFI-based systems -# %include ../ingredients/live-core-storage.cfg # For live systems only -# Documentation: https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#part-or-partition -[...] -``` - -- Once you are done, you can [flatten](#flatten) the file and [kickstart](#kickstart) it as explained in the previous section. - -## FAQ - -If multiple dishes are affected by your ingredient, you can flatten them all - -- Navigate to the recipes' directory - -``` -cd recipes -``` - -- Then use the following - -``` -for filename in *.cfg; do ksflatten -c "$filename" -o "../dishes/$filename"; done -``` - -The following message can safetly be ignored: - -``` -/usr/lib/python3.13/site-packages/pykickstart/commands/partition.py:461: KickstartParseWarning: A partition with the mountpoint / has already been defined. -``` - -## Acknowledgement - -Thanks to the main contributors of the official Fedora kickstart files repository, and related tools: - -> Adam Miller, Bastien Nocera, Bruno Wolff III, Bryan Kearney, Chitlesh Goorah, Christoph Wickert, Colin Walters, Fabian Affolter, Igor Pires Soares, Jens Petersen, Jeremy Katz, Jeroen van Meeuwen Jesse Keating, Luya Tshimbalanga, Matthias Clasen, Pedro Silva, Rahul Sundaram, Sebastian Dziallas Sebastian Vahl, wart. More information here : https://pagure.io/fedora-kickstarts \ No newline at end of file diff --git a/dishes/install_bare-metal_desktop_encrypted_43.cfg b/dishes/install_bare-metal_gnome_encrypted_hardware-support_server_43.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_encrypted_43.cfg rename to dishes/install_bare-metal_gnome_encrypted_hardware-support_server_43.cfg diff --git a/dishes/install_bare-metal_desktop_encrypted_hw_rawhide.cfg b/dishes/install_bare-metal_gnome_encrypted_hardware-support_server_rawhide.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_encrypted_hw_rawhide.cfg rename to dishes/install_bare-metal_gnome_encrypted_hardware-support_server_rawhide.cfg diff --git a/dishes/install_bare-metal_desktop_encrypted_hw_43.cfg b/dishes/install_bare-metal_gnome_encrypted_server_43.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_encrypted_hw_43.cfg rename to dishes/install_bare-metal_gnome_encrypted_server_43.cfg diff --git a/dishes/install_bare-metal_desktop_encrypted_rawhide.cfg b/dishes/install_bare-metal_gnome_encrypted_server_rawhide.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_encrypted_rawhide.cfg rename to dishes/install_bare-metal_gnome_encrypted_server_rawhide.cfg diff --git a/dishes/install_bare-metal_desktop_43.cfg b/dishes/install_bare-metal_gnome_hardware-support_server_43.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_43.cfg rename to dishes/install_bare-metal_gnome_hardware-support_server_43.cfg diff --git a/dishes/install_bare-metal_desktop_hw_rawhide.cfg b/dishes/install_bare-metal_gnome_hardware-support_server_rawhide.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_hw_rawhide.cfg rename to dishes/install_bare-metal_gnome_hardware-support_server_rawhide.cfg diff --git a/dishes/install_bare-metal_desktop_hw_43.cfg b/dishes/install_bare-metal_gnome_server_43.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_hw_43.cfg rename to dishes/install_bare-metal_gnome_server_43.cfg diff --git a/dishes/install_bare-metal_desktop_rawhide.cfg b/dishes/install_bare-metal_gnome_server_rawhide.cfg similarity index 100% rename from dishes/install_bare-metal_desktop_rawhide.cfg rename to dishes/install_bare-metal_gnome_server_rawhide.cfg diff --git a/dishes/install_bare-metal_hypervisor_amdcpu_hw_43.cfg b/dishes/install_bare-metal_hypervisor_amdcpu_hardware-support_server_43.cfg similarity index 100% rename from dishes/install_bare-metal_hypervisor_amdcpu_hw_43.cfg rename to dishes/install_bare-metal_hypervisor_amdcpu_hardware-support_server_43.cfg diff --git a/dishes/install_bare-metal_hypervisor_intelcpu_hw_43.cfg b/dishes/install_bare-metal_hypervisor_intelcpu_hardware-support_server_43.cfg similarity index 100% rename from dishes/install_bare-metal_hypervisor_intelcpu_hw_43.cfg rename to dishes/install_bare-metal_hypervisor_intelcpu_hardware-support_server_43.cfg diff --git a/dishes/install_virtual_desktop_encrypted_43.cfg b/dishes/install_virtual_gnome_encrypted_hardware-support_server_43.cfg similarity index 100% rename from dishes/install_virtual_desktop_encrypted_43.cfg rename to dishes/install_virtual_gnome_encrypted_hardware-support_server_43.cfg diff --git a/dishes/install_virtual_desktop_encrypted_hw_rawhide.cfg b/dishes/install_virtual_gnome_encrypted_hardware-support_server_rawhide.cfg similarity index 100% rename from dishes/install_virtual_desktop_encrypted_hw_rawhide.cfg rename to dishes/install_virtual_gnome_encrypted_hardware-support_server_rawhide.cfg diff --git a/dishes/install_virtual_desktop_encrypted_hw_43.cfg b/dishes/install_virtual_gnome_encrypted_server_43.cfg similarity index 100% rename from dishes/install_virtual_desktop_encrypted_hw_43.cfg rename to dishes/install_virtual_gnome_encrypted_server_43.cfg diff --git a/dishes/install_virtual_desktop_encrypted_rawhide.cfg b/dishes/install_virtual_gnome_encrypted_server_rawhide.cfg similarity index 100% rename from dishes/install_virtual_desktop_encrypted_rawhide.cfg rename to dishes/install_virtual_gnome_encrypted_server_rawhide.cfg diff --git a/dishes/install_virtual_desktop_43.cfg b/dishes/install_virtual_gnome_hardware-support_server_43.cfg similarity index 100% rename from dishes/install_virtual_desktop_43.cfg rename to dishes/install_virtual_gnome_hardware-support_server_43.cfg diff --git a/dishes/install_virtual_desktop_hw_rawhide.cfg b/dishes/install_virtual_gnome_hardware-support_server_rawhide.cfg similarity index 100% rename from dishes/install_virtual_desktop_hw_rawhide.cfg rename to dishes/install_virtual_gnome_hardware-support_server_rawhide.cfg diff --git a/dishes/install_virtual_desktop_hw_43.cfg b/dishes/install_virtual_gnome_server_43.cfg similarity index 100% rename from dishes/install_virtual_desktop_hw_43.cfg rename to dishes/install_virtual_gnome_server_43.cfg diff --git a/dishes/install_virtual_desktop_rawhide.cfg b/dishes/install_virtual_gnome_server_rawhide.cfg similarity index 100% rename from dishes/install_virtual_desktop_rawhide.cfg rename to dishes/install_virtual_gnome_server_rawhide.cfg diff --git a/dishes/install_virtual_hypervisor-desktop_amdcpu_43.cfg b/dishes/install_virtual_hypervisor-desktop_amdcpu_gnome_server_43.cfg similarity index 100% rename from dishes/install_virtual_hypervisor-desktop_amdcpu_43.cfg rename to dishes/install_virtual_hypervisor-desktop_amdcpu_gnome_server_43.cfg diff --git a/dishes/install_virtual_hypervisor-desktop_intelcpu_43.cfg b/dishes/install_virtual_hypervisor-desktop_intelcpu_gnome_server_43.cfg similarity index 100% rename from dishes/install_virtual_hypervisor-desktop_intelcpu_43.cfg rename to dishes/install_virtual_hypervisor-desktop_intelcpu_gnome_server_43.cfg diff --git a/dishes/live_bare-metal_desktop_hw_43.cfg b/dishes/live_bare-metal_hardware-support_43.cfg similarity index 100% rename from dishes/live_bare-metal_desktop_hw_43.cfg rename to dishes/live_bare-metal_hardware-support_43.cfg diff --git a/dishes/live_bare-metal_server_hw_43.cfg b/dishes/live_bare-metal_server_hw_43.cfg deleted file mode 100644 index 5f6dae3..0000000 --- a/dishes/live_bare-metal_server_hw_43.cfg +++ /dev/null @@ -1,114 +0,0 @@ -# Generated by pykickstart v3.69 -#version=DEVEL -# Keyboard layouts -keyboard --xlayouts='ch (fr)' -# System language -lang en_US.UTF-8 -# Network information -network --bootproto=dhcp --device=link --hostname=phyllome-alpha --activate -# Shutdown after installation -shutdown -repo --name="fedora" --mirrorlist=https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-43&arch=x86_64 -repo --name="updates" --mirrorlist=https://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f43&arch=x86_64 -repo --name="rpmfusion-nonfree" --mirrorlist=https://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-fedora-$releasever&arch=$basearch --includepkgs="rpmfusion-nonfree-release" -repo --name="rpmfusion-nonfree-updates" --mirrorlist=https://mirrors.rpmfusion.org/mirrorlist?repo=nonfree-fedora-updates-released-$releasever&arch=$basearch --includepkgs="rpmfusion-nonfree-release" -# System services -services --enabled="NetworkManager,systemd-resolved" -# System timezone -timezone Europe/Zurich --utc -# Use network installation -url --mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-43&arch=x86_64" -# System bootloader configuration -bootloader --location=mbr --timeout=1 -# Clear the Master Boot Record -zerombr -# Partition clearing information -clearpart --all --initlabel -# Disk partitioning information -part / --fstype="ext4" --size=5120 -part / --size=8576 - -%post --logfile=/mnt/sysimage/root/live-core-post.log - -# Enable livesys services -systemctl enable livesys.service -systemctl enable livesys-late.service - -# enable tmpfs for /tmp -systemctl enable tmp.mount - -# make it so that we don't do writing to the overlay for things which -# are just tmpdirs/caches -# note https://bugzilla.redhat.com/show_bug.cgi?id=1135475 -cat >> /etc/fstab << EOF -vartmp /var/tmp tmpfs defaults 0 0 -EOF - -# work around for poor key import UI in PackageKit -rm -f /var/lib/rpm/__db* -echo "Packages within this LiveCD" -rpm -qa --qf '%{size}\t%{name}-%{version}-%{release}.%{arch}\n' |sort -rn -# Note that running rpm recreates the rpm db files which aren't needed or wanted -rm -f /var/lib/rpm/__db* - -# go ahead and pre-make the man -k cache (#455968) -/usr/bin/mandb - -# make sure there aren't core files lying around -rm -f /core* - -# remove random seed, the newly installed instance should make it's own -rm -f /var/lib/systemd/random-seed - -# convince readahead not to collect -# FIXME: for systemd - -echo 'File created by kickstart. See systemd-update-done.service(8).' \ - | tee /etc/.updated >/var/.updated - -# Drop the rescue kernel and initramfs, we don't need them on the live media itself. -# See bug 1317709 -rm -f /boot/*-rescue* - -# Disable network service here, as doing it in the services line -# fails due to RHBZ #1369794 -systemctl disable network - -# Remove machine-id on pre generated images -rm -f /etc/machine-id -touch /etc/machine-id - -%end - -%post --logfile=/mnt/sysimage/root/post-live-session.log - -# set livesys session type -sed -i 's/^livesys_session=.*/livesys_session="gnome"/' /etc/sysconfig/livesys - -%end - -%post - -# Import RPM Fusion PGP Key. Courtesy of https://github.com/rpmfusion/rpmfusion-nonfree-remix-kickstarts/blob/master/rpmfusion-nonfree-live-base.ks -echo "== RPM Fusion Nonfree: Base section ==" -echo "Importing RPM Fusion keys" -rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-nonfree-fedora-*-primary -echo "List of packages from RPM Fusion Nonfree:" -rpm -qa --qf '%{NAME} %{SIGGPG:pgpsig} %{SIGPGP:pgpsig} \n' | grep -e 3DE8C682E38EE9BC0FDFEA47FCAE2EA87F858107 | awk ' { print $1 } ' | sort -echo "List of incuded RPM Fusion packages with their size:" -rpm -q --qf '%{SIZE} %{NAME}\n' $(rpm -qa --qf '%{NAME} %{SIGGPG:pgpsig} %{SIGPGP:pgpsig} \n' | grep -e 3DE8C682E38EE9BC0FDFEA47FCAE2EA87F858107 | awk ' { print $1 } ') | sort -n -echo - -%end - -%packages -@anaconda-tools -aajohan-comfortaa-fonts -dracut-live -glibc-all-langpacks -kernel -kernel-modules -kernel-modules-extra -livesys-scripts - -%end diff --git a/recipe-generator/Makefile b/recipe-generator/Makefile index db5c174..5e313b9 100644 --- a/recipe-generator/Makefile +++ b/recipe-generator/Makefile @@ -1,4 +1,4 @@ -.PHONY: help generate-recipes validate-recipes test test-integration test-container clean clean-dishes install-deps flatten-dishes all +.PHONY: help generate-recipes validate-recipes clean clean-dishes install-deps flatten-dishes all help: @echo "Phyllome OS Recipe Generator" @@ -6,9 +6,6 @@ help: @echo "Available targets:" @echo " generate-recipes - Generate all recipes from manifest" @echo " validate-recipes - Validate existing recipes" - @echo " test - Run pytest test suite (unit + integration)" - @echo " test-integration - Run integration tests only" - @echo " test-container - Run all tests in container" @echo " all - Generate, validate, and flatten all recipes (default)" @echo " flatten-dishes - Flatten all recipes to dishes" @echo " clean-dishes - Remove flattened dishes" @@ -31,16 +28,6 @@ validate-recipes: python3 generate_recipe.py \ --validate ../recipes/*.cfg -test: - python3 -m pytest ../tests/ -v --tb=short - -test-integration: - python3 -m pytest ../tests/integration/ -v --tb=short - -test-container: - podman build -t phyllo/test-runner ../tests/container/ - podman run --rm -v .:/phyllomeos:ro phyllo/test-runner - flatten-dishes: @echo "Flattening recipes to dishes..." @python ../bin/ksflatten-relative ../recipes ../dishes @@ -52,5 +39,3 @@ clean-dishes: clean: rm -f ../recipes/*.cfg @echo "Generated recipes removed. Edit recipes_manifest.yaml and run 'make generate-recipes' to regenerate. Run 'make clean-dishes' to remove flattened dishes." - - diff --git a/recipe-generator/__pycache__/cli.cpython-314.pyc b/recipe-generator/__pycache__/cli.cpython-314.pyc index 34a49ef4c28d447e62c9a60e79302082c67a7ba3..d4aee2967350484f7e9d4a52f5f05179e04eb44b 100644 GIT binary patch delta 8373 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 bR206xxKABN%Kycrhti%Kdn*W)1?>J02QW$o delta 2425 zcmZve3v63g8Gz5ZH+Jm!{f@7lhaEeK6E`7^(>$Grx;z#qbK>2)DT`RcnijJ)M0Sga z)2cxr)!V8j*$->FY1Gvaix}H745J!?c8rWNGG>z%yIEjH6jdOkI%-SUgb?T4JX8?p zO5gvVbN~N8k9+=e?(HAqZZXkkqbB>B+*RUWn zu9h52>Y0c7w&dXO2FoShv=|sB%E51n%(y0T(smegLbN{#(Q;y>)z9>QY$}V%a*Ph{ z*vIR$YU2gPydsBiN$Wj?HEo+>q&8;MTv}~QscrGrq`mR?>ZT2rv~Ji?p(E#pr^`OB z{5Bn=ZF`Vfx-tX{kLIXY+%Jfwe$tpY=g7xQv=J7ZjWAvLSB>B}AvpdyPmwj4V9I4@ zvuYE>UGGH>^fHjKW!{xvnme$(AY#isj;bz@X;o_*oKNK?m1w(shYiI z;^5V$0rs{`1OIBWRc2+T1=~k5%eq`O+xJZ0-0(TyCo;os?YxgHtlW4{46V&S7`#E| z5UC`f+pP;@=Z3(ftv)9x*Hl|F$&YNk3G0aQ6AZylkrIXMK+)Z7!qf6LR`nq?DdnYs z0yjrZps1}L%V4-Ig5|K>)*$_WyqELxfC5-=H#Wg#uZ@)tSzunQt1Ad7J=?J-HN0kL zm5<2C+9F=Vs{^WX5>wFBRD*ZCPLY;JLxoURU=b}-J9x9BSaK{u)zJ$0Sxdzk9j_ZQ z)4}|A;k6#J=Cn-eM_pSQLY{49(!d-3zZb9A9i)-h^G5fMD~)90O}oQBEQ~=$vs5Sn z4PPKf0WGf})WOyEJr=zh^^iS1SvX4OxNRlj3*~4p1Mjp~0kk&2(GEw2nauIQezt9S zvwPQ%MB&KXYrFJ3NBwsuSAx2+Zx*2kdNBO2#VGlLTZQKWMzD1@Vl5o%ERrP=lA>a= z&wW1ldS^BMC%o4AH1t&v<6{${qvMeiHE^ms3T#iiZ4Ir-zd9BUjZK_*{KVAc_?l{` z7CHIFiLvA3(ACp}pMzM>tT>+xG%SIl-YRT?zFuCnCfkZGc!mL9?k&U5!`%ZF@JX*$ zY9Z$3<)x*iu-WV6gblA0PRy*n@WSBy;LKo-k%;u`(9}0$EWaXh1@HI}4}mA^AqE?QDX z-!(HqtT(ID&7Ph;9XlQ8Um1O6G(NiS@GKolH~3NwzE#ISQfHbEs25}9W)o`W%%TIZb`7ylN zf{g8HgEwXHeik`M7V5lVz>8is4Y)*aONh4oIho&3@E#HquCQ9aMY`gW4YWvCJbC2p zN()Qr?eJ^a4?47jztv(&oyV_WKU9c_{-H(Y-vi(Ajl0Yw*CKY(2R&*W*t9{B;5V!`DhlMugk$b;1|IpoAFJU>(>(gd_5zkfc| zVPOO0@oZ;T2^yulQ78fZwtf4snRty1H`okO^KQP3l}17n$DZ0roSHDb#9);_<6R(1 zc!*+{AS&B&Inq+6hEQ3L=lJC1MTIPsd|y(y96#pxSQ%d6mL+x zM)5jLApAYB1RVz9)7>QBZw8SIU20m8W+WQ$2WQBnlR*cYRhr<_pq0($%YZvLS3u<1 z>9f-_(;0Z{;DP#E5=HFTdF7{f8<3_4Z@Q7TC9P@xEK*4NdACr~FBZS+6chehr0tiB z-j~bztY-FDsFhH`OI^mm=p5`;cBt?W@|uVj7CPXqZ~MV+*-8C2(!14t6PUg}&KdBX5;J zsA~fh7CZgcnuRdP3_5++(@f}Ot2F|h3&oP&p~_)E3&>Dm!F@2wG+qfMhb>%QYvF^A z=~#*}m0D+t0=ZI6Bo{$8OpQGs!U^??!-`XrgcB4KtfMa4O9pVr-3)?-gfLK2Bj5)V zbrZScLp$Y?`iSPx3@eS&r^7EBpIXTdrjlBK>gla43baAbKQM|uP)O#lWI{b6j26@` z4t9b#GH$8l;Kk7cq9oq4+O5>0pY)@7H&kKXOVco?>8pNsekDyaBhqvp6OauTFYIex zVw!$sm*HN`udjJUeDmIug(v$qT;qryw?%)y<0y(QIya*3pL;C2`7_@5^6`6o>qocu O`PoIxy0>kQqW%KN^G19C literal 39257 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 delta 689 zcmYjOKTH!*9DdjKN_%au?X|+S(0cSwT7>pYa1q-?!hjAPM52j{IpHi9jyrO9pms3m zpfwJL{7l5HIx%X9n8w-3K@&Ta0VOHSwh&Sq7vEhMe#7^@-~aEucU=4#r^jg@g|PiC zewA$QP5Pm&lG%Kjf%lGEgco{_o3=^`64;Ad~)I&Y2>>GSO zS72vUWnI(^9ShPVLBR)ES{A1$xN1)mIgsrslDe)KDtwPbeGBT!JxQ@t>v?!L{ExMk zHTwb?6dtU)T0`fnvrYIkQec_^_KknjUmIuyvU>scFf;^Y?1H@&$-t#pfleZpYv)mb zt@?Y%L3kW{oA#R8;~1WyJ;%hU1Aq6a6aURAg2x1TAj!@zV Y#r6^V8^v14({)Z01bc!^)PlI~Kg<-tJpcdz diff --git a/recipe-generator/__pycache__/python-update-package-names.cpython-314.pyc b/recipe-generator/__pycache__/python-update-package-names.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa2f62f7114de9e4d078a9664029b8a6e0165ddc GIT binary patch literal 2556 zcmcgtO-vhC5Poa#+Ft))dojiVV;hLgPhye~hZsmJp+FHB8q0~QR_cNrdoi1WZLQaG zaC0ENgs7DQO(kemDQ?v&?xo^LdT7$Lk&>M3n8eORb7} z*?BYbzM1*<`A~&UjbPloH^G|>sWn=#(mh_T0+exnq1d)-WOWlQb!z**yDGIGcPc ze&S#7e+3>gf7SMhl0=h|haLHDB19q^^g?_*khwVQ=m_F9vPx{7vh_iw#ft2Yi+=Z;r=o4HR=-(Q;JAgZ zhN^zQfBuF66Vz&-+>M;y!+IA}BEC)Q|B2JIrW48)Hf)?qYEeKZR)6PL0lJEI9KLPK zTba9+0WkN69evUV3=%uuerNcP@9q{ookT`6`^39Pjbv?EXl`{ynyq}MP5+y>?8x4G<7yPIT_@pK2EfD z!LPaPP)oYHkKwA|6?5HNMxl#GXiJQLnRRR)o2sS(tP z@-kSe%Ip7{Hrgql`c#c#e&OJs;%wM2h9dK^3210pF&Jpa{mn zB`lDM4V)~K5ae7kDkwN6k(}TKHOD}9(NHYR2#N#~N;M~mi;yt$)b_RjP_)0?lR7E(p~vAq4*Vq?+Xv25=s87%Yq zIekX?#L&E^K}Kp_juaHbw2Nq9@f+IicTwoFx4v8c>Bye0RsCH&6$ zmvW36#AX!FHO8XGn%7t}*s`Ynxz%=K;G2P*t9$8#+w?8EVD-%mKhs-F&i10yn|FGb zj0I%{Jl{Ik zdi`*bI+&*pX3iFg|u8mO6$S~F~5bz~Zgglm~_W$Q~gab@W8(CmR_yjBF;lqX#4 z{TP%Mx|$aoh+zMz6NR#-dF3YEw5MW z76=7r@rwzj-zsr&wsyjNw?@?5JzfR#@0Om50hjELrUsbrH5dog r#QhFx&_+CYRzCR`Ou~rJVO2r(V_ew literal 0 HcmV?d00001 diff --git a/recipe-generator/__pycache__/recipe_generator.cpython-314.pyc b/recipe-generator/__pycache__/recipe_generator.cpython-314.pyc index 7ea27fa099dc616d3c93b04a48a8b64aee27efeb..2a5a6883ca3fee8e8757cfe5b445b778c26a69b1 100644 GIT binary patch literal 32281 zcmeHwdvIIVdFKTQkRS*Oe2Sz+N!9vK_lkTklS0ONtPI!tO-dwA0=Fqbo(y zc$)sP`}@x0;sOLII&s^{Od;{&+PrwU_*?B0CZda5W^bjlX9 zohptMpR&j7?78K1NvxztaaqrsVq2<|CdE}yt+)#N^LT49hs)BUbQF0tG=D6t1yn8I z3q}I!SYR@sd1Jxwq#6p31${j>+r{xr$PZ0sn~cx9S?Z5m>M2sHy6gQheF|r zfO`H#^>Wa6IU1vvYwL?%kH!KMJ+{*^^=eqV992WX%K_D^M#B-b5DWUev4CGimPiOG zH843AoD84^Z*0J3+ts7G0$x9wm+$?7(col|dKy(nweW;!(IYcNd#-yYLblyK>ItuJ zJgZzFI$%>(wHuXwWh$tl-QeUHzJh_tSX6EIhOToa(D2GhQj{fXfU9m^i(vUslM@W&=(Mc^arAsW8nzKbS0>TCnr#@OO3|Dns*E% zydDXNO3p`UJl;@dZ)7)A>VRLx(qJ`MW9k)eXeuDP;702xo;{esfFBL1SNPlRXap-g z8uY0VZ){u^Jr5h)F4)!tsc4r?j+*CSv*?=wG)@Gk+0Ku4vBs%rcq$f|iqW<=bUHYR zEgr%q1+dwoJ}nrDp&f5bjZB3?v};(aT5xO}CG~BUo{b(`hbdL;otzBEczd`tUjQ_A zCxXIaZ`CkiJ5?R^IqK-%XgV#W>+3?Wq!31DveoOW~41FUBGcD zOBcRU7_+*pcvr;ki(EFOi&@&{YIBv~ZVTOoOtE5@19zqDo!wQ2bU90xxGIpYWa%xg zDx|lvw8K@6bPY?Fx@wW$#?oc3I;87ay4=-(^mdl6a5W;WvUH`Z3F&5*u5z^?-OAEi zT{~jcE<13(Gv#nGTz`_Idf4NviEUQ~w^|4xp(cK3@Q+8FhsvhSq05!xA1ZVttO8ezL|WCe^eJ99#hvxAWg2{#AR_4Ek% z4YWJ6sEo5qb@uPURY1Z)=h!5;B0La;{-6`9r(KVL%W?ARThJ#-5~4AU0DE2eZb>M)n_e4kMZqkv>UC!<)-qfVznH4NSC=7`+9`O zicfH`lxBDwT`zewjvI{Ibb&q2yySZ3Rwm`#VYV(|ATLc1`?zQh^QM3M zG;yAwG1Gx;umT(&(Z)D?nWn{TmIqV@@r$T;pgcI~p z&{@X5QC&F!xW{%5WoydGsd9X!k=uFh_TCU%fsNcU_zkkLYx7H?W z8xpnM%eCF}lPk3cXUxB{MXN;i3`jQP0cP9uNM#k}hGbX1%iNoXm9S_f$d)n_E6ZRw zRiLpQMPiMjIdJJPX{Gq$0L{jtmD4+du@~vZHe3kUIxUqC=`wfx0e#J1WOuxWMwlx^ zeg<=Y3i!CG)DhI_u!MKw(=WcA;@giN)hicGk-VV_K%B8gk;853Di%q6Me;7`Q0dO2 z{rik4^Q_5Zn#!BX_b9TBNdBb)S@MEarqbU{iaTFIbHt3fcNJc=pcV6GT8UT=Tc|BpKBa=D zpu$JeVf8+P=^(yCqI`WzaM@C4HzdC^5r~b4DL0czb8lzHQ3S5VG{{Jt2XgAwI*A(~ z2D*n)TfKfs0Epp+#Mm7QTnU6!W4X(L>s(GCA%<^$CPISl=mK#D5~9uVRMexk`yj9Z zzj#NY;m{N)4rrzpASV9`comxntn+&I6-Hb?17#*2UMq<3w`L3Z=4hdOqLR~*x9Am%Zf^_ z1?66L8hDLflCXPxqhp}jJv}|IWEInSK9qZ%+9#o&(#U%QJ35?=Cl4H)6AT>2ANwu&6PTgwFXlU z84a@b6j*-1gJ4cb#A5;$qg=KhS*Bv6-TS{~N?F0+fU}z%JEzrR15Y=d(6q2Npk5S= zJVXhSa`id~)ah!G<+PzjRIg6w4X7POsp4SNBOy|&Mx7~hI1-plnMt>kDvEkX1B{xb z$_7#Mxo~Wd`pW7|St8)5AWH@?u2d22F!sEoKqF+Q0bePp61gEgv`bQ^(Ucitqc>#= zLDv8`luOOj^n)$|7=t2JUwhRVE!32}k3yqcP!Vy6b34s;#$69uz1| zI}+-yWp&rW@q6n2TP2^ARo$AHA4`<&yH~bvvFVfQx;tKk+3mFo6e=1=pL@@G({ zvSzmUUPbGYz4g;VJVEAvcx+Wln;t89MU|h}%a$tlE=(+Vmda1W?Sr4#OK+9Eb@CHO z#anN@`9?ZR=lsz8;l<{~FDxEgDt|t1KO!>z;>mw_U_m`Uiaw9df4%C+A!W7`mk;x+ zk91l-+*V5I&f+7_7JazSO6fzb$JG2k+EGr|{U*8|s6SSh|HpL}q%$Zw%_ESj=}{}2 zmUSt5o(pn!FR=5s1XVBQIK@L!TPh!jB2_51h}MXzo=V+sB2%=9HA>W7C3>#T9V6O*e^qBG z8;R*;4P{=y0vHH!#Ee|jldO*$6J^XNNnga`wp=L6%8XmoVqh%f`{AQt3PitU4UHg; z7jyzHbD{=|%mJp$?ENe7GAFtAfE2jv3Mrg+f{;l;p5bI2i%ME5bO#fVd!QkWfvPZl z8q@I!x+260qL-vDh3_RDWD;P=<++SV&_gd-RSbC(jGs8OT#fi5Q`8;WA3+~2fp@}>XGqaxf+Ph(bep54F;;RCDtz7q z%ZrCO^Fr?*^FYx7^)diVn+o&}dP6v%ASJti2%*mfKNb!#9kg*ape{Z_nmpPEVF0@p zCGH@T(K{l*LQ;O>^hkEiu^Bj6LvPPCHOR#wr8^hit)wa%$ux%%K729;Z;7wkdygV7cO2~j6 zkh@FS*Km3u zxsp9pd@mI*()UQpD)B(d5&~-*6}(AXm*7oOR*6d1O6T&;P1G5U;R4R&ImKT6&g9#Z z@!~GVIP@*lEtD*kABx)#Gk)NgPu{PnzH{jIp}Ed`6+JW72R5awDpA_JT-rRhe}3p* z%fL$Mvon^Ywd5`PoAz556IC6{RUPx~i(gu)IvKZ~0=H6H`PTI}ug_MkI2xC%jh~O4 zuRVkAYb5kC2H;0UbV~3t5Z5bDhUu)`xVNuXsRT3$^TSt)ves$0hx1 ztl%YF`R78uOr^hdSv=33mrI|tmN8f-pFuKqla3?bI|31$X^Pq2%id2+$~3?tLjbf% zIk;j=AA^({1HGh&{s2_;OsIn@brL8FPjPYsDGo=vAgOec16qZ%N{}jGI1pnAL4I4N zDjE89IvE}hgd&g#{Mf+3(d(>unlKvAhQ;>5&b9IGj-XFhsdY&j#n~#3xUL2v+=_Fi z46=jABH>)3QZcS81|pSs z5=W6zzBP=|FnWPVkAR(;h~T_|@HAtX&f72>DLuwWuk8TdFj>Cn>Wp z3@vT}0m-DlBcIDpv>8(ce7sTG5D=EQq;!DZG`KR=?L8UTknP8P$ahnD+^m#0C(7EE z%i31T+Gh%q)-7+9yje1vH~Z{t_tMt(xV1w(x-~*dL`T(3$y$6uS}%0M^ayGqRJyK| zVRl(ud9DIiVXql`%gXeJ+98zT8biXo9K#%>6q7TKQR-=`#6Ll%XfG~=V6Ns{Y96xd zy!iq9UWoGV4`Td#>B5(I20jwPH1b0XKSomWXt0uM>ro=+OAs{pN5?W2cm9T0D1W%c zWoknqXeV_6MAo9frld!awjia-U=aGof=jxn;V!tOYY}BGrzq@iW&)2cP`C?n2@JBI zQfzotF$ye^!lAs21(CwxDH5T^Fxq*LCI7iNl75OYW=o%L}S%GMf{ zGnW;mNZAg(=LJ&qW_@JuN|h6)!T+ZySAk%)H{I3?RaqHv3#rs1=OAJmemCcA7WC?~ zNx(03y|zg4P~Gs)euffOl*l`CmR8MGV4N{`vGFcqH>{O?gSCPt0>Wu}U5hSll_Qj6 z(%1F3Dev%au>QZz(*G@^%|b&xg=x+2ntBdXS$83z3i~@!;w~BG>kGkj_?}_Cv4ZQa zcgd#KyTq{G`}umG`9G+&P#@E7HVy`5elTX}*XIUdCroEl1@LpGgM|G=;&I7k~ll(8nI1_*I%uy?;h z5Msz%n$DYsl0A9Z`)>?m1Duz-9n=f|9d_q%muB?gr(-XF+Xxr_J{k#!34Rvz`R(O1 z|5H)U`~ZJ{gTKGc*pVBp(0^Tjg|b)0{~liz?AzbOzAbZ?@%0hVZpTO+?y`(EDmScs zd3wdy)Xed1`9BlB1?S){7Z^TFX}86I;~_Y$yGrGo!tvz>9RH5kwWc%wWz%c)-8E}e zgKfb+Yi9EI_!{ZdvGL9y{1v|MBW01Fjurc|Va0xC&5F@_u16z3`-uuhdlNpe<0T1T?U=422rJgjt%|T(m!SRIK&ie7|?9TpENn!sI@bpi`Ekh0izYF;T z*IIUkv6_L#ZM0H98Kq_GWT3S6f1$Jngd>vf+?4k3HoZnaIi*wZQP{frW&m)7NwbV~g9M4Kg_B%bD}lFa zG(g}MsQ;k1u^TCp+h)82%`ajcO+L{KqEs0&?s=47kN5iQoXZt4AEf6jnJR)3ke?yw z$7@VS&5!8i`4T%z(9fNqcxM`Zc|IS8?wgeTD7XRIFPI2;M2IdFA9LU)D z0u=u6K$)Pkt2K7y=z;K(!AXO`(pyvwn>P3)>1P?FKo@PpsMW{ad7{B<@W#PWG+Dbc zWq*{E^w7x*moE62O=-w19LY|6;ic2A6UPVCBiVjllVMW3b5|&KVk(Z=2bkfxbpDEX zeV1|a3O^a>(__qUN)378NE5S>+riNJapZiEA5_!f^O{ESu$LW8^Y-A!;KpFlAWi878Ur`JdtKNA z+)&}s=L?L7L*&;Kof?6&UkoOO{)|S`^FUVRk#O&*UY^=n(+i%be-G~j+#imeKX?4J z9QUzx{MIrZ=Q26~%QxwvaS+CRbh(%VzkB+(mRg<6gH3W93h()HR^?{W_q$SPS+$J5Ed)aY{#Fr_oRdP7#;hG(>yx5 zqj5?nk9F)yxK)DgpQr=6Zg&G427^^8HZS>itlc^59LMQ6P4~%@Cv-5nz*!kgU<^aH z@dI|Nv;v(j(#xkzo|LIuI|!tnvPyf+i>V^rvIXYk>~y|ZM-$#q%EAmUJGQ_|EfCXy z)|FH~a9PS0jIuLKIPJi$kU6%In@_3$){`*b&`zwkG!Q`jNP0)i;jt0k4d83h3A*O@XW21z4Vd3@JfY`C0*=xH^VbzjN;Pxs}S+nWAK4+pUgeYds7Zsv7Q`y?u6W+q`SFvTx@2Cyv_L)^C^- zjvaR$JN{a2oxAwn%kRGYJFl#$dlrt|Q}+pT&08h+9rde@_GD?z9mj3QTnE`vE~(GP zOP@=YR^GARw$3)JmO39+DOI~3)GIAr@Abahn`qv%Y~Ma}@_|)p?s%{9-NuEA1>Z{3 zz>&IR z%HTWE1Y?Zap7}TK)gGF8;jvvQ-8Q@P8~%i&^RA=wecR%J@A?vbFWl{WAz4u$uh^Mv z?YY-_;MUM5)lGBV?^hVj&~gk9g9a7`uKm6pCwt;#m%{K0tV@nntqj^k~|e8{jEK@2wAJ;48^|LR0+ngxtzE{@${?5fOeb1cOJ#=sP(8u*V zZXHin*R57}B-?l2Yu`Ux@=4u}xrz5LCh7+6)(w2zwr4Ic*|OsshZaWuU^3a-_1^h+ z&o2Z&$Xkpp^&gM7o=CQ~zjx~0Q}eEGo<$Fx{pjKL&bMEgzp>hTDp}w9&iS{`&j%Mv z7QVLBbuwO$N9sGL-aa+=%4+?d$5l#0+rtwkrKSJDh)F@+&%OKHf^G5WO3UFy%ZcTd z6N#3S%l5{ZlS#+cRYyy*rs-bI&RdqpHl=d=>}%g{O;q&Wt?2!Z?SsAF>yLlorH{H2 z`-kuDA5K=+&)MSDJ29Yzz4zJ=d@e(pzY%}xfF4?uzSE|+UwP6 zx?8pD{ntLI!ldsTy1Q@aoNbSnT-V=m-5v`%lH&Pbb^Ee&6~n>q7bO5omNi zLqOdVZ#u9TT5`Ep54qFObawI3Qr`=4^^An3`c-uwCg0h$K_3&Y;bi}zME~*S{^P6t zgUdC2v$naj1RJ6Ek0)xYFQZUwC7wYcO7a zQi8{&)%x9!%apw*O^<8R(|E6H*Fs=1_6HLmckG!hN!GQ;>-rZPRu33z$_;3R%Fh0W zV*s&JrU%zd66Ms*kE~Sg0y1i7OEh#ZH*~Ky^v(?3ukT3I_bu1=Emkkp_pQ_)nHfqt z>gSGc0@)NR+xs3=D%Fj5LbpS62N#-Gw(gxNPPX(UTKbn;`V%et7&PB^Y<=r%Z+>ky zI(KZv(LR4V?$`^^CIq$6vJhQqe)e9?bGI$PJ@K*~9OiFZ7|wYNGcF^Nd0}ztyRq+G z{2-L*fAMbri^+cNwyI>AS}hyHDTU zefr~O=dBl#K!9Ift?o@acP;F^=R7!T|0e~iDy5|zFs0+etS-<+u4d$)S;$IU&T31{~L>bJGUYj-V#K5AXvcP@>%&n_HVYCjsU zIVKTz{c24Qpnlge)8oo?4>`u>lX!3U=|2zr_rS+HdT$LSt6SsMeG9Lw?lsi8mKdzo z^gb?B+WH=P0sqfD(130kF}v#+dHc0Md2>|Uyr*33AV zY0bF|Uj%3 zwQdXuURy}$2FvFTlwrPeQgVSDfnuwO`-+W#$6Xk9HicSwa-amYLn7Ld@w4(i4sLDqO`_>!KB#b`CJ7L0?6ifb3%7iHp-5h3}OJQmI0b zH&y5jF}WcM4HW128G{e=XoyH2*>DbfLE~$-pI}c!jDE9Xcf^b9fENDkp(T4$vb^d} z=k3l}-`t*+a_3B8vba26+=#dLDt0W{cYv3h$>C@GnZ1%U*b+V63~-h9Jt;=X3@~7_ zv_E9m`4=Cs@5Md*d+EZ9`~^n>PzUNCnk5+Wlsqyc1r(Ljx!AbTdrZtv$XJJ{%uQ~- z*x~dc-yoOy1;vkJ7-j}V?PPsm=q8X#qcT*D`~}EgzCnJ2!5s|k3bU%it;;HFq&cCF z9|9rTe@bpwEkj+90oTo2w5H~bMrM<_F|sl!S#ynE?<>5sa`&%T)}oK*Ro+8arg>h$ zB_M77fhpy>3V1(^#xTADsLXM&O9#TTUZGHiCsyvhW|%svT+usJ>n@PxH(E7!fgA<% zLdy*|MLOJA-PTRjYMebKmuV`X(XQ881Vwns{x1R^HiEA$o9L-SuhZtXxk{f}XXz&D zEYs^Ob{Dfe?j_HS5+;)8haAXRIp&G7Y?qIHp zyPVMMw!<~PYPZpuV7sY@6DNNj)k34Ta*7d zBTg>3QfrFGzaz(dP)&Fpg|8CvN8Lh@DjH8TPmwJP1zjbFoutQni zfF5=-;1POq@@6B?K#%V6mVT4=6!cuaGT~twZ~2x5DG*#8J4t}j65P_%=xFd722NUG zxUE5J&A1T3ij!mFzw5R2IXgLlpNyfzK=pmxTi}1`TQEEM+(H$`|23&0{1CPTNLs6w!?Zh4$K( zUU=f#m=nx&dHSIYqnC1|pK+&bs|3wdKH~hPEDU5)g#t=bMf%LAO69?ZJpP#0pLB}@ zkyHtVkwH)$50vOrkyHVP)ain5es6;|JVxI30d3mOJ%#+;^laKp!=}(R6um)v9asa7 z9mJJtIO|n3(z0tW(Zx*{5FO#-@f=beTR7vohn}W2>lQlLi)z>KG?h0VOPR)5z1j`R zc9Sk&r_0aL<>zsUZoviGYe>Dku)g-Px6ZzKcDDAO zy(L*vJNwc~Nwa)bAGfzbHEyqZ>)@LQXX_KiZFh^?l8&0UZoGM8_R@-D=Tgs+xZ?=8 z=Q{^)ADpXARCM00=uB4DlG1#sY45$tefRmzTz$N<2i@RRE!FN!6nEY&?o5_#TdM1V z4q@gfsn(l+0~+d^Jwo>uj zj5S$Yace4GT%VP1O;wN&Ze5NSH_3c;ar+L`SFa}Ox|i#^=YvZJ&aBj(U9#3Bi|b}% zE5$7$=TdEF+}?$(2xC;UbGc^c{DFn=O3mO*NwTVcT)dU{_{M+s#Wx zM`x!BiGdpad3PZ=apg=C>@j{pq>+VLlUQWlu}a2l<>v~ekY--sdGs=@Y0~V!Wg|;a z$rMG3F6JRd6TCWJvQ5D=YbtLLBH&kkH`99LT;3T71k|gkyojwyF)Ifzu&F_RvOjL4|IKlp^Gr&X zXhAOPNf_}tr)pJVil3+l<_rcv0 zsv8zCL10hPD2-g_dKuVcFjdn83^mBkf`blDMGLfKp?ZrLEsJmauo46ya?*Rj0RM%PXm;) zXb4$QnvOdtx7dJ6h@5Pp#1pXFB6JsYpVSmgrl9nM7$s_-@IvbtV8MhucXcpv)bjc8dF3C}s%3or?5n)7}Dd(SSLPJQ}l( zFw>lHlM1kt2oOU4%N-8RwEb%oNR{cectq8RL&Y!+j2a3>-%>@?53)~)riyv@*o{WI zO6@he+@{J*ms4B#Oz4$aXqLmN;>o~OSWhtVEM*Et3E5^@FK~{}!5f!(qic;AvNAB* zc^}o?RGvIn4_RzGv*{`MX3OuRkuTjATbTlt?FoO5Yq!o&i}`3#Hdee^+V!XiMIUx5HR^+X>?G#z ze^{aH8Zxo46@O$YJJyjIy%eNjQ_PAn=A*%9yAj2;V|1Z4+2~Z}>qaicVd;9P;4hO)=V&EPG4beg_S9EfyWQK&Xd-eIslZV{|H9VZEb z9gm8~4qT)?kBUfUFtdR*5h*$7hz1%72#h3W>v8M}#98ETVc4g(13k;I2wjkJc>om> zvq=cmZNrQa@R_azwIDDbP}FtaF19wXNAxlZy1DX!0^>tvA_K?suu5bK1$7J=0aD-} zY^I>!D0s_*8k~IOFfRkzWF@f4v#?JZk+I0$MD7C=T}~Q$;dF?_I)F-n)zD!>paRnJ z^RRK?ZFxj+8NPr_sbJT|pCW=iEwhYZkSde8^(d)prM5L$7<7RH1(3kdp{i}ppJ;n# zx$T*i%KbR>chn#R+_Ix-u6@PPxn%A9@p*RRHb_||cf;csY<@5NnsG1KEu9Y)Txc)g zi}nJ(HrNX_Y?y|XTx zyfGW2JVIwfW%T^`5e5p_-%hf27hr*5Y+;cqIGPT0AWbY~(*x>|;1ttTlAX)RmHx>g z(xj1O(2>W<*Ef+i2H9mVHvlx*QMPo1>i}pqhyXZOLxQ_?=jQ}ch1k;+5hNXHXLH!~ zT_Z$oA&A0d4uLQ5HGiGmZ2(dK7}$0Cf4L=U6_6r^mI;7_KhK2?wzRrAsBTUg-2}R1 z*x3kJqOk!WXBIe0+zX6F%H+=vvhp>^vK-fG?-GoOC@uevp8hs2nF!7yTlVmYS<9qk zWnhadklQw;YRBBg#EyN-JNB(qK4U~~&h;RXFfmZ&c_j< zv4ki&TrPDC3=hK0FiqVB2y!!m$n|tECFmNVw**YSL0*7VRswgWokCp4#)-lS-kBp1Nw*HR|t#E;SJI^?nM{zlNzz*|h zufdQB=IQt`Cpttigg9H%r8LJNUcuSL@Y7c^|N5sunc}o@gtDwS(2-SOa89G=gxbG{ zpCw^iog=>n+FwG2!G3lMKQ{%=84|zIT}4z5hM4b@R0&zuT#m};f*YaDlEIDqCp=hd zmXlGIffnZl!Sa?itvH$!j=p6_-$M0@O9MERiz4Zm?u0ZRz^~rW@4K-rr+WelN5HIo?FA!G z6wMa%Y5+BO?CpYQ7s;!5@8J>?!s7uO~$ni3VxhprjD__n?iJtjfk$q^2|d8mjzIJe0^|?eSI{<9&Ui>zRV^v zmw2Cx?2|R39u!#)D*@Q~eL z8?wWMW^Hn@p=}z8BU&>q5YI?@NEOjopkxB3{UdrTxYs|Sd~D%q_e3+f*V?S{c?cmh z^MX`|iy_Z6`?_vc1}5F8oaqoMs&Bo%Qr?m%?_MtNUbwnc-n~+O7^bxJ>jf>#j+VKT zD~^3j)_rRMow|mRJoTX2sI=19@N|B5bJ~S`w4$Fdmsq+9gu55^5I}b?LSKb1-aEj* z{t>+^dGr$ibY5v5Re-&Gy{Vi7`izdf~9`JfU0*t4eHmHVPLeW4bZjh=$ap1aU5E*9%2v$ z(XSf4XCdl4Oy@dk=m*_MLyjMGvw6r11DY75@k@PQ!I_VE>QF7$Z( zVV}pN)lp?Nba@_^6z3K}f5qO`NHWo$r3>+7+Aq@OH|g>_bRi`;Lm3)PsYZe(BY)aQ zl=>cBmT*Z`c|5R-faKr-m#+m!5Hli*#!2`Le0gqCWVt3C@x?&p$CE>id%}iqib4RtS%*Cw=6q@#I`Std*X;kcZR2 zC(Stjcv26|^%E8PLb?n*Q6VHg88m^Bda@nl=2KkwR_2|desmO0Ki@(cG5PU$WJ*;% z_@wVLQ~VuWJ+cP=s8{VpAu8zcAdNeo##N?RO}&)o5?wvL(}!6f(vRpx`k&0G{)|Ye zmr#Xxe~F&)3aGJkDMQ-$6y?w+E+RLzul7zKLJxXX{IsKomGktX-Y%+-rSzxt1aGLk zO5dJd8Xrbb)xsLrCy77SPS6~eYODAnxE-CMpAG{^5YdNe0VphKNr~>H3Sh}gl#_S> z&B6+ZZHd!9elo~Z=1g_SAhL~e4$_5zR3@0Etk3aXa!C72WC5yFqWf`qkZ&@X9_Ce> x%ujYJrk1}^YQC>Dd}_9s@*XL4*`k>2KeSmZ^Ols-Pqzk(O@|*TbY(E}{{fr_h&uoP delta 6075 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 JAxPQG{{y?>n0NpH diff --git a/recipe-generator/__pycache__/validators.cpython-314.pyc b/recipe-generator/__pycache__/validators.cpython-314.pyc index ccd4d8dea936018efb78be8613592e0762d23761..1a33c2fae864195cfb9decabc2d1c50d648b3d7f 100644 GIT binary patch literal 16749 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 delta 2007 zcmah~ZA@Eb6n@`+(N9`Szdzs>7;X7zVPzmgRu~`MM3BJ5Or5U41)4!iZVPkOWsN3= zWtu=vvTQNN>Jn2EVo1=0Ka2XqKbjcW5K3LL&an7PQ^J-Sf2i+!%UGN-yUF{U^M2g- zob#M>?)~i@ywY7>CB@?5#}UFfE1v3OMwuUY(|20Y@6SP4twRzchc5seRiv^T*S zX`RpSos3Mg-l^Gh!Kk-0GRAu6LR`!{GkIZZiflxpUXC3N&9HS7Y?$SOu?XjdE7Der zni$zDA&@|MgvIV%3dvJMKvrtC$o91S-piUWk%eavRiMu?Y|8@0{M%BpoIq)t3+m*a^QH##OwHHwDyeP7bdcPvJYTY}84zOyz;FczdTFin#ZAq??lVZ=kTXD$DTuWYGI?C-+SaYwj`iA(&Dzd&ook^xZLGXytlc&- zcZ`l?-Iuy8W792T)6ej=>4wsb>bmf*7*)6B${}n%hs}^NcS~&4sWv!d@mZBLWF3L` z(6*@Up@=Y;mlQ&W^OgoYKoaII(R4<_F*Y0<4o-!}XaeHx@K(7U#MTOu#W_3#-KCBr z|3^4yqV~)bD7jG*JgTbVC=;9w2Fgl{hp1Xg!9W1M^>x6_GPT4(SIPm`l~r*ki9Srm zL4NEsKLnD(92F@UnZZ4|Zl#9l6*%Ml`@XVFp?H|lLI@tY{3(fp%T=dsHN;d#7#thp zXq|8k1l)3t&t@O|RdpJ-5yuS>c1l&eSAUWNd`91O4uaFAf?u4K5;fA<_H>|hO}^$u zy3;sH`{Jsp%Pd-v5a224=ls-wh5`+~KudsX6b8sR$j|fp`FRw2;e`MsEG|4p5>;I? z#7+mpvC!z_M7g2e?S@Zld(3_G&VB-!qB)j}h9Y6^fMCDt0yRYEw3!T)TLP3@B2uHg zSPqfJmC3q3EQr5BuB~}m3&7tE2^+P@Q99kSeg5de2!&zz@NnQ#8&NZs|9I}1D-;{c zd$vdpQGStDH!sPFRI+1fyJO4lUAKFei*DPWo$tS^w%@V4Ql(_FDBrQWmh`vn-Ua!N zzB2jBZGHWGPg<$nQdX}kt5eMzN?)!RX$c-W; zhPpEi37=J<62V8laRu)&AB+Qv8@-1{iAU6{gMt)`t9KEB@h>}2Al{(RrEz-ajexopJcEI=ka#>oTzjkIr zQJsc|{u(R+N4vGxXiwTVjGnZq^kda^RZ6;SU!D7@p=Yf=uof6us~X)jjb#gaYgH#U zO(*vh$kw*2MwTNv8&YZ$eLV5~ZY|dZT>JUA`47Tb>KFCsiCku+&rL_hW~bO=+`D9# z@)M=CkP~8zw^74=RDK_s?jq+pa^6RtyXbIU(t$DQaf H@m~A`bv+LF diff --git a/recipe-generator/cli.py b/recipe-generator/cli.py index 39263b8..bada2e6 100644 --- a/recipe-generator/cli.py +++ b/recipe-generator/cli.py @@ -396,7 +396,7 @@ def generate_from_manifest(args: argparse.Namespace, generator: RecipeGenerator) # Generate all recipes from the manifest for recipe_config in manifest.get('recipes', []): - recipe_type = recipe_config['name'] + recipe_type = recipe_config.get('recipe_type', recipe_config['name']) if recipe_type not in generator.templates: print(f"Error: Unknown recipe type in manifest: {recipe_type}", file=sys.stderr) sys.exit(1) @@ -411,12 +411,8 @@ def generate_from_manifest(args: argparse.Namespace, generator: RecipeGenerator) # Extract version and other modifiers from variant version = variant['version'] modifiers = {k: v for k, v in variant.items() if k not in ['name', 'version']} - variant_subname = variant.get('name', '') - - # Add variant name as modifier if present - if variant_subname: - modifiers['variant_type'] = variant_subname - modifiers['variant_subname'] = variant_subname + modifiers = {k: v for k, v in variant.items() if k not in ['name', 'version']} + # name kept for manifest organization only, not passed to generator # Generate the recipe content content = generator.generate(recipe_type, version, **modifiers) @@ -472,7 +468,7 @@ def generate_single(args: argparse.Namespace, generator: RecipeGenerator) -> Non # Build modifiers dictionary from command-line arguments # Only include non-default values to keep filenames clean modifiers = { - 'variant_type': 'desktop', + 'desktop': args.desktop if args.desktop else None, 'storage': args.storage if args.storage != 'standard' else None, 'security': args.security if args.security != 'secure' else None, diff --git a/recipe-generator/recipe_generator.py b/recipe-generator/recipe_generator.py index b9c54f7..e1cbbc3 100644 --- a/recipe-generator/recipe_generator.py +++ b/recipe-generator/recipe_generator.py @@ -494,12 +494,6 @@ class RecipeGenerator: Returns: Filename string ending in .cfg """ - # Extract variant subname if present - # Used to distinguish between desktop/server/hypervisor variants - variant_subname = modifiers.get('variant_subname', '') - if not variant_subname: - variant_subname = modifiers.get('variant_type', '') - # Build base parts - start with recipe type parts = [recipe_type.replace('_', '-')] @@ -511,10 +505,13 @@ class RecipeGenerator: elif guest_agents is False: parts.append('bare-metal') - # Add variant_subname for install variants - # Only include recognized variant types - if variant_subname and variant_subname in ['desktop', 'server', 'hypervisor', 'hypervisor-desktop']: - parts.append(variant_subname) + # Add hypervisor indicator if present + if modifiers.get('hypervisor'): + if modifiers.get('hypervisor') in ['base', 'desktop']: + if modifiers.get('desktop'): + parts.append('hypervisor-desktop') + else: + parts.append('hypervisor') # Add hypervisor_type suffix # For hypervisors, include the type (kvm, xen, etc.) @@ -529,10 +526,9 @@ class RecipeGenerator: # Single hypervisor type parts.append(ht) - # Add desktop (non-GNOME only, since GNOME is default) - # GNOME is the default desktop, so we only note alternatives + # Add desktop desktop = self._get_modifier(modifiers, 'desktop') - if desktop and desktop != 'gnome': + if desktop: parts.append(desktop) # Add security suffix (devel only, since secure is default) @@ -551,12 +547,15 @@ class RecipeGenerator: # Hardware support detection is optional hardware_support = self._get_modifier(modifiers, 'hardware_support') if hardware_support is True: - parts.append('hw') + parts.append('hardware-support') # Add initial_setup suffix (non-server values) # Server is default, other setup types get noted initial_setup = self._get_modifier(modifiers, 'initial_setup') - if initial_setup and initial_setup != 'server': + if initial_setup == 'server': + # Server is the default for non-desktop, non-hypervisor + parts.append('server') + elif initial_setup and initial_setup != 'server': parts.append(f'{initial_setup}-setup') # Add bootloader suffix (systemd-boot only) diff --git a/recipe-generator/recipe_templates.yaml b/recipe-generator/recipe_templates.yaml index b3b5148..81108f1 100644 --- a/recipe-generator/recipe_templates.yaml +++ b/recipe-generator/recipe_templates.yaml @@ -9,24 +9,17 @@ templates: base: core required: - core: ingredients/core/base.ks - - version: ingredients/repo/fedora-43-mirrors.ks - - storage: ingredients/storage/standard.ks - - bootloader: ingredients/bootloader/grub.ks - - locale: ingredients/core/locale.ks - - services: ingredients/core/services.ks - - network: ingredients/core/network.ks - - packages: ingredients/packages/core-group.ks - - fedora-remix: ingredients/packages/fedora-remix.ks - - hand-picked: ingredients/packages/hand-picked.ks - - security: ingredients/core/security/enabled.ks - - initial-setup: ingredients/initial-setup/server/config.ks - optional: - hardware-support: ingredients/packages/hardware-support.ks - guest-agents: ingredients/guest-agents/base.ks - variant_type: - desktop: - server: - hypervisor: + - { path: ingredients/repo/fedora-43-mirrors.ks, replaceable: true } + - { path: ingredients/storage/standard.ks, replaceable: true } + - { path: ingredients/bootloader/grub.ks, replaceable: true } + - ingredients/core/locale.ks + - ingredients/core/services.ks + - ingredients/core/network.ks + - ingredients/packages/core-group.ks + - ingredients/packages/fedora-remix.ks + - ingredients/packages/hand-picked.ks + - { path: ingredients/core/security/enabled.ks, replaceable: true } + - { path: ingredients/initial-setup/server/config.ks, replaceable: true } modifiers: version: "43": ingredients/repo/fedora-43-mirrors.ks @@ -59,11 +52,14 @@ templates: - ingredients/hypervisor/base/services.ks - ingredients/hypervisor/base/post-scripts.ks desktop: - - ingredients/packages/virtual-machine-manager/packages.ks - - ingredients/packages/virtual-machine-manager/post-scripts.ks + - ingredients/packages/virtual-machine-manager/packages.ks + - ingredients/packages/virtual-machine-manager/post-scripts.ks bootloader: - grub: ingredients/bootloader/grub.ks - systemd-boot: ingredients/bootloader/systemd-boot.ks + grub: ingredients/bootloader/grub.ks + systemd-boot: ingredients/bootloader/systemd-boot.ks + optional: + hardware-support: ingredients/packages/hardware-support.ks + guest-agents: ingredients/guest-agents/base.ks # Live recipe - for live-desktop or live-server live: @@ -71,26 +67,16 @@ templates: base: live-core required: - live-core: ingredients/live/core/base.ks - - version: ingredients/repo/fedora-43-mirrors.ks - - storage: ingredients/live/core/storage.ks - - bootloader: ingredients/live/core/bootloader/grub.ks - - locale: ingredients/core/locale.ks - - services: ingredients/core/services.ks - - network: ingredients/core/network.ks - - packages: ingredients/live/core/packages.ks - - post: ingredients/live/post/base.ks - - session: ingredients/live/post/session.ks - - rpmfusion-nonfree: ingredients/repo/rpmfusion-nonfree.ks - optional: - variant_type: - desktop: - server: - hardware-support: ingredients/packages/hardware-support.ks - guest-agents: ingredients/guest-agents/base.ks - hypervisor: ingredients/live/hypervisor.ks - security: - secure: ingredients/core/security/enabled.ks - "off": ingredients/core/security/disabled.ks + - { path: ingredients/repo/fedora-43-mirrors.ks, replaceable: true } + - { path: ingredients/live/core/storage.ks, replaceable: true } + - { path: ingredients/live/core/bootloader/grub.ks, replaceable: true } + - ingredients/core/locale.ks + - ingredients/core/services.ks + - ingredients/core/network.ks + - ingredients/live/core/packages.ks + - ingredients/live/post/base.ks + - ingredients/live/post/session.ks + - ingredients/repo/rpmfusion-nonfree.ks modifiers: version: "43": ingredients/repo/fedora-43-mirrors.ks @@ -101,3 +87,10 @@ templates: bootloader: grub: ingredients/live/core/bootloader/grub.ks systemd-boot: ingredients/live/core/bootloader/systemd-boot.ks + optional: + hardware-support: ingredients/packages/hardware-support.ks + guest-agents: ingredients/guest-agents/base.ks + hypervisor: ingredients/live/hypervisor.ks + security: + secure: ingredients/core/security/enabled.ks + "off": ingredients/core/security/disabled.ks diff --git a/recipe-generator/recipes_manifest.yaml b/recipe-generator/recipes_manifest.yaml index 944f4c7..7f48a78 100644 --- a/recipe-generator/recipes_manifest.yaml +++ b/recipe-generator/recipes_manifest.yaml @@ -11,15 +11,15 @@ # Example: version: ["43", "rawhide"] + storage: ["standard", "encrypted"] # creates 4 variants: (43,standard), (43,encrypted), (rawhide,standard), (rawhide,encrypted) # -# NOTE: Boolean modifiers now generate unique filenames regardless of value (True/False). -# Each variant combination produces a distinct filename with appropriate suffixes. +# NOTE: The 'name' field is for organization only and doesn't affect generated filenames. +# The recipe type is always 'install' or 'live' from recipe_templates.yaml recipes: - # Install variants - desktop - - name: install + # Install desktop variants + - name: desktop + recipe_type: install variants: - - name: desktop - version: ["43", "rawhide"] + - version: ["43", "rawhide"] desktop: gnome storage: ["standard", "encrypted"] bootloader: grub @@ -28,11 +28,11 @@ recipes: initial-setup: server security: secure - # Install variants - server - - name: install + # Install server variants + - name: server + recipe_type: install variants: - - name: server - version: ["43", "rawhide"] + - version: ["43", "rawhide"] storage: standard bootloader: grub hardware-support: false @@ -40,11 +40,11 @@ recipes: initial-setup: server security: secure - # Install variants - server hypervisor - - name: install + # Install hypervisor variants + - name: hypervisor + recipe_type: install variants: - - name: hypervisor - version: 43 + - version: 43 storage: standard bootloader: grub hardware-support: true @@ -54,11 +54,11 @@ recipes: initial-setup: server security: secure - # Install variants - desktop-hypervisor - - name: install + # Install desktop-hypervisor variants + - name: desktop-hypervisor + recipe_type: install variants: - - name: hypervisor-desktop - version: 43 + - version: 43 desktop: gnome storage: standard bootloader: grub @@ -69,18 +69,18 @@ recipes: initial-setup: server security: secure - # Live variants - desktop - - name: live + # Live desktop variants + - name: desktop + recipe_type: live variants: - - name: desktop - version: 43 + - version: 43 hardware-support: true guest-agents: false - # Live variants - server - - name: live + # Live server variants + - name: server + recipe_type: live variants: - - name: server - version: 43 + - version: 43 hardware-support: true guest-agents: false diff --git a/recipe-generator/requirements.txt b/recipe-generator/requirements.txt index 8d55326..142159f 100644 --- a/recipe-generator/requirements.txt +++ b/recipe-generator/requirements.txt @@ -1,3 +1,2 @@ PyYAML>=6.0 -pytest>=7.0 pykickstart>=1.99 diff --git a/recipes/install_bare-metal_desktop_encrypted_43.cfg b/recipes/install_bare-metal_gnome_encrypted_hardware-support_server_43.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_encrypted_43.cfg rename to recipes/install_bare-metal_gnome_encrypted_hardware-support_server_43.cfg diff --git a/recipes/install_bare-metal_desktop_encrypted_hw_rawhide.cfg b/recipes/install_bare-metal_gnome_encrypted_hardware-support_server_rawhide.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_encrypted_hw_rawhide.cfg rename to recipes/install_bare-metal_gnome_encrypted_hardware-support_server_rawhide.cfg diff --git a/recipes/install_bare-metal_desktop_encrypted_hw_43.cfg b/recipes/install_bare-metal_gnome_encrypted_server_43.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_encrypted_hw_43.cfg rename to recipes/install_bare-metal_gnome_encrypted_server_43.cfg diff --git a/recipes/install_bare-metal_desktop_encrypted_rawhide.cfg b/recipes/install_bare-metal_gnome_encrypted_server_rawhide.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_encrypted_rawhide.cfg rename to recipes/install_bare-metal_gnome_encrypted_server_rawhide.cfg diff --git a/recipes/install_bare-metal_desktop_43.cfg b/recipes/install_bare-metal_gnome_hardware-support_server_43.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_43.cfg rename to recipes/install_bare-metal_gnome_hardware-support_server_43.cfg diff --git a/recipes/install_bare-metal_desktop_hw_rawhide.cfg b/recipes/install_bare-metal_gnome_hardware-support_server_rawhide.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_hw_rawhide.cfg rename to recipes/install_bare-metal_gnome_hardware-support_server_rawhide.cfg diff --git a/recipes/install_bare-metal_desktop_hw_43.cfg b/recipes/install_bare-metal_gnome_server_43.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_hw_43.cfg rename to recipes/install_bare-metal_gnome_server_43.cfg diff --git a/recipes/install_bare-metal_desktop_rawhide.cfg b/recipes/install_bare-metal_gnome_server_rawhide.cfg similarity index 100% rename from recipes/install_bare-metal_desktop_rawhide.cfg rename to recipes/install_bare-metal_gnome_server_rawhide.cfg diff --git a/recipes/install_bare-metal_hypervisor_amdcpu_hw_43.cfg b/recipes/install_bare-metal_hypervisor_amdcpu_hardware-support_server_43.cfg similarity index 100% rename from recipes/install_bare-metal_hypervisor_amdcpu_hw_43.cfg rename to recipes/install_bare-metal_hypervisor_amdcpu_hardware-support_server_43.cfg diff --git a/recipes/install_bare-metal_hypervisor_intelcpu_hw_43.cfg b/recipes/install_bare-metal_hypervisor_intelcpu_hardware-support_server_43.cfg similarity index 100% rename from recipes/install_bare-metal_hypervisor_intelcpu_hw_43.cfg rename to recipes/install_bare-metal_hypervisor_intelcpu_hardware-support_server_43.cfg diff --git a/recipes/install_virtual_desktop_encrypted_43.cfg b/recipes/install_virtual_gnome_encrypted_hardware-support_server_43.cfg similarity index 100% rename from recipes/install_virtual_desktop_encrypted_43.cfg rename to recipes/install_virtual_gnome_encrypted_hardware-support_server_43.cfg diff --git a/recipes/install_virtual_desktop_encrypted_hw_rawhide.cfg b/recipes/install_virtual_gnome_encrypted_hardware-support_server_rawhide.cfg similarity index 100% rename from recipes/install_virtual_desktop_encrypted_hw_rawhide.cfg rename to recipes/install_virtual_gnome_encrypted_hardware-support_server_rawhide.cfg diff --git a/recipes/install_virtual_desktop_encrypted_hw_43.cfg b/recipes/install_virtual_gnome_encrypted_server_43.cfg similarity index 100% rename from recipes/install_virtual_desktop_encrypted_hw_43.cfg rename to recipes/install_virtual_gnome_encrypted_server_43.cfg diff --git a/recipes/install_virtual_desktop_encrypted_rawhide.cfg b/recipes/install_virtual_gnome_encrypted_server_rawhide.cfg similarity index 100% rename from recipes/install_virtual_desktop_encrypted_rawhide.cfg rename to recipes/install_virtual_gnome_encrypted_server_rawhide.cfg diff --git a/recipes/install_virtual_desktop_43.cfg b/recipes/install_virtual_gnome_hardware-support_server_43.cfg similarity index 100% rename from recipes/install_virtual_desktop_43.cfg rename to recipes/install_virtual_gnome_hardware-support_server_43.cfg diff --git a/recipes/install_virtual_desktop_hw_rawhide.cfg b/recipes/install_virtual_gnome_hardware-support_server_rawhide.cfg similarity index 100% rename from recipes/install_virtual_desktop_hw_rawhide.cfg rename to recipes/install_virtual_gnome_hardware-support_server_rawhide.cfg diff --git a/recipes/install_virtual_desktop_hw_43.cfg b/recipes/install_virtual_gnome_server_43.cfg similarity index 100% rename from recipes/install_virtual_desktop_hw_43.cfg rename to recipes/install_virtual_gnome_server_43.cfg diff --git a/recipes/install_virtual_desktop_rawhide.cfg b/recipes/install_virtual_gnome_server_rawhide.cfg similarity index 100% rename from recipes/install_virtual_desktop_rawhide.cfg rename to recipes/install_virtual_gnome_server_rawhide.cfg diff --git a/recipes/install_virtual_hypervisor-desktop_amdcpu_43.cfg b/recipes/install_virtual_hypervisor-desktop_amdcpu_gnome_server_43.cfg similarity index 100% rename from recipes/install_virtual_hypervisor-desktop_amdcpu_43.cfg rename to recipes/install_virtual_hypervisor-desktop_amdcpu_gnome_server_43.cfg diff --git a/recipes/install_virtual_hypervisor-desktop_intelcpu_43.cfg b/recipes/install_virtual_hypervisor-desktop_intelcpu_gnome_server_43.cfg similarity index 100% rename from recipes/install_virtual_hypervisor-desktop_intelcpu_43.cfg rename to recipes/install_virtual_hypervisor-desktop_intelcpu_gnome_server_43.cfg diff --git a/recipes/live_bare-metal_desktop_hw_43.cfg b/recipes/live_bare-metal_hardware-support_43.cfg similarity index 100% rename from recipes/live_bare-metal_desktop_hw_43.cfg rename to recipes/live_bare-metal_hardware-support_43.cfg diff --git a/recipes/live_bare-metal_server_hw_43.cfg b/recipes/live_bare-metal_server_hw_43.cfg deleted file mode 100644 index 9ae9cd9..0000000 --- a/recipes/live_bare-metal_server_hw_43.cfg +++ /dev/null @@ -1,20 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A live recipe for live-desktop or live-server - -%include ingredients/live/core/base.ks -%include ingredients/repo/fedora-43-mirrors.ks -%include ingredients/live/core/storage.ks -%include ingredients/live/core/bootloader/grub.ks -%include ingredients/core/locale.ks -%include ingredients/core/services.ks -%include ingredients/core/network.ks -%include ingredients/live/core/packages.ks -%include ingredients/live/post/base.ks -%include ingredients/live/post/session.ks -%include ingredients/repo/rpmfusion-nonfree.ks \ No newline at end of file diff --git a/tests/__pycache__/test_recipe_generator.cpython-314-pytest-8.4.2.pyc b/tests/__pycache__/test_recipe_generator.cpython-314-pytest-8.4.2.pyc deleted file mode 100644 index 83956ac6db1fc1d7830553d6058804cfa600eb8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43079 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 YW~BE2Dz$y?JM3{i=XxNqKdkNl2TMsIq5uE@ diff --git a/tests/container/Containerfile b/tests/container/Containerfile deleted file mode 100644 index a5ddc8b..0000000 --- a/tests/container/Containerfile +++ /dev/null @@ -1,25 +0,0 @@ -# Phyllome OS Recipe Generator Test Container - -FROM fedora:43 -LABEL maintainer='Phyllome OS Team' - -# Install minimal test dependencies -RUN dnf -y install \ - pykickstart \ - python3-pytest \ - python3-pyyaml \ - make \ - git \ - && dnf clean all - -# Set working directory -WORKDIR /phyllomeos - -# Copy project files -COPY . /phyllomeos/ - -# Install Python dependencies -RUN pip install --no-cache-dir PyYAML pytest - -# Run tests by default -CMD ["bash", "-c", "cd /phyllomeos && pytest tests/ -v"] diff --git a/tests/container/README.md b/tests/container/README.md deleted file mode 100644 index f3b23f8..0000000 --- a/tests/container/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Phyllome OS Recipe Generator Test Container - -Minimal containerized testing environment for kickstart recipe validation. - -## Build - -```bash -cd /home/lukas/Code/virt/phyllomeos - -podman build -t phyllo/test-runner tests/container/ -# or: docker build -t phyllo/test-runner tests/container/ -``` - -## Run Tests - -```bash -# Run all tests (unit + integration + golden masters) -podman run --rm -v .:/phyllomeos:ro phyllo/test-runner - -# Run specific test file -podman run --rm -v .:/phyllomeos:ro phyllo/test-runner pytest tests/integration/test_ingredients.py -v - -# Interactive mode for debugging -podman run -it --rm -v .:/phyllomeos phyllo/test-runner bash -``` - -## Test Types - -### Unit Tests -- **Location:** `tests/test_recipe_generator.py` -- **Count:** 36 tests -- **Description:** Tests for recipe generator functionality (template loading, validation, version extraction) - -### Fragment Validation Tests -- **Location:** `tests/integration/test_ingredients.py` -- **Count:** ~15 tests -- **Description:** Validates all 54 ingredients with pykickstart, checks section structure - -### Recipe Composition Tests -- **Location:** `tests/integration/test_recipe_composition.py` -- **Count:** ~10 tests -- **Description:** Tests recipe generation, validates all 16 manifest variants - -### Semantic Validation Tests -- **Location:** `tests/integration/test_semantic_validation.py` -- **Count:** ~10 tests -- **Description:** pykickstart semantic validation, deprecated command detection - -### Golden Master Tests -- **Location:** `tests/integration/test_golden_masters.py` -- **Count:** ~5 tests -- **Description:** Regression tests comparing generated recipes against expected outputs - -## Test Structure - -``` -tests/ -├── test_recipe_generator.py # Unit tests (36 tests) -├── integration/ -│ ├── test_ingredients.py # Fragment validation (~15 tests) -│ ├── test_recipe_composition.py # Recipe generation (~10 tests) -│ ├── test_semantic_validation.py # pykickstart semantic (~10 tests) -│ ├── test_golden_masters.py # Regression tests (~5 tests) -│ └── conftest.py # Pytest fixtures -├── fixtures/ -│ ├── expected_recipes/ # 5 golden master files -│ └── sample_ingredients/ # Test fragment samples -└── container/ - ├── Containerfile # Test runner container definition - ├── run-tests.sh # Test entrypoint script - └── README.md # This file -``` - -## Container Contents - -The test runner container includes: - -- **OS:** Fedora 43 -- **Python packages:** - - `pykickstart` - Kickstart parsing and validation - - `pytest` - Test framework - - `PyYAML` - YAML parsing -- **System tools:** - - `make` - Build automation - - `git` - Version control - - `coreutils` - Basic utilities - -## Adding New Tests - -1. Add test file to `tests/integration/` -2. Follow naming convention: `test_*.py` -3. Use pytest fixtures from `conftest.py` -4. Run tests in container to verify - -## Troubleshooting - -### Container won't start -```bash -# Check if container builds -podman build tests/container/ - -# Run with verbose output -podman run --rm -v .:/phyllomeos:ro -e PYTEST_VERBOSITY=2 phyllo/test-runner -``` - -### Tests failing inside container -```bash -# Get interactive shell -podman run -it --rm -v .:/phyllomeos phyllo/test-runner bash - -# Run tests manually -cd /phyllomeos -pytest tests/integration/test_ingredients.py -v -``` - -### Permission denied errors -```bash -# Run with security options (for rootless podman) -podman run --rm --security-opt label=disable -v .:/phyllomeos:ro phyllo/test-runner -``` - -## CI/CD Integration - -This container will be used in Gitea Actions workflows for: -- Pull request validation -- Main branch testing -- Automated recipe generation checks - -See `.gitea/workflows/` for workflow definitions. diff --git a/tests/container/run-tests.sh b/tests/container/run-tests.sh deleted file mode 100755 index 12952e5..0000000 --- a/tests/container/run-tests.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# Run all tests inside the container environment -# Usage: podman run --rm -v .:/phyllomeos:ro phyllo/test-runner - -set -e - -echo "=============================================" -echo " Phyllome OS Recipe Generator Test Suite" -echo "=============================================" -echo "" - -# Navigate to project root -cd /phyllomeos - -echo "[1/3] Running unit tests (test_recipe_generator.py)..." -echo "----------------------------------------" -python3 -m pytest tests/test_recipe_generator.py -v -echo "✓ Unit tests passed!" -echo "" - -echo "[2/3] Running integration tests..." -echo "----------------------------------------" -python3 -m pytest tests/integration/ -v -echo "✓ Integration tests passed!" -echo "" - -echo "[3/3] Validating generated recipes..." -echo "----------------------------------------" -cd recipe-generator -python3 generate_recipe.py --validate ../recipes/*.cfg --strict -echo "✓ Recipe validation passed!" -echo "" - -echo "=============================================" -echo " ALL TESTS PASSED!" -echo "=============================================" -echo "" -echo "Summary:" -echo " - Unit tests: 36 tests (test_recipe_generator.py)" -echo " - Integration tests: Fragment validation, recipe composition, golden masters" -echo " - Fragment validation: 54 .ks files checked" -echo " - Recipe validation: All 16 manifest variants generated and validated" -echo "" diff --git a/tests/fixtures/expected_recipes/desktop-hypervisor-intelcpu-rawhide.cfg b/tests/fixtures/expected_recipes/desktop-hypervisor-intelcpu-rawhide.cfg deleted file mode 100644 index 3f29f8f..0000000 --- a/tests/fixtures/expected_recipes/desktop-hypervisor-intelcpu-rawhide.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A recipe for a desktop hypervisor - -%include ingredients/core/base.ks -%include ingredients/storage/standard.ks -%include ingredients/bootloader/grub.ks -%include ingredients/core/locale.ks -%include ingredients/core/services.ks -%include ingredients/core/network.ks -%include ingredients/packages/core-group.ks -%include ingredients/packages/fedora-remix.ks -%include ingredients/packages/hand-picked.ks -%include ingredients/core/security/enabled.ks -%include ingredients/packages/virtual-machine-manager/packages.ks -%include ingredients/packages/virtual-machine-manager/post-scripts.ks -%include ingredients/initial-setup/server/config.ks -%include ingredients/hypervisor/intelcpu.ks -%include ingredients/repo/rawhide-mirrors.ks diff --git a/tests/fixtures/expected_recipes/desktop-hypervisor-intelgpu-rawhide.cfg b/tests/fixtures/expected_recipes/desktop-hypervisor-intelgpu-rawhide.cfg deleted file mode 100644 index 3f29f8f..0000000 --- a/tests/fixtures/expected_recipes/desktop-hypervisor-intelgpu-rawhide.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A recipe for a desktop hypervisor - -%include ingredients/core/base.ks -%include ingredients/storage/standard.ks -%include ingredients/bootloader/grub.ks -%include ingredients/core/locale.ks -%include ingredients/core/services.ks -%include ingredients/core/network.ks -%include ingredients/packages/core-group.ks -%include ingredients/packages/fedora-remix.ks -%include ingredients/packages/hand-picked.ks -%include ingredients/core/security/enabled.ks -%include ingredients/packages/virtual-machine-manager/packages.ks -%include ingredients/packages/virtual-machine-manager/post-scripts.ks -%include ingredients/initial-setup/server/config.ks -%include ingredients/hypervisor/intelcpu.ks -%include ingredients/repo/rawhide-mirrors.ks diff --git a/tests/fixtures/expected_recipes/desktop-hypervisor_rawhide.cfg b/tests/fixtures/expected_recipes/desktop-hypervisor_rawhide.cfg deleted file mode 100644 index 3f29f8f..0000000 --- a/tests/fixtures/expected_recipes/desktop-hypervisor_rawhide.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A recipe for a desktop hypervisor - -%include ingredients/core/base.ks -%include ingredients/storage/standard.ks -%include ingredients/bootloader/grub.ks -%include ingredients/core/locale.ks -%include ingredients/core/services.ks -%include ingredients/core/network.ks -%include ingredients/packages/core-group.ks -%include ingredients/packages/fedora-remix.ks -%include ingredients/packages/hand-picked.ks -%include ingredients/core/security/enabled.ks -%include ingredients/packages/virtual-machine-manager/packages.ks -%include ingredients/packages/virtual-machine-manager/post-scripts.ks -%include ingredients/initial-setup/server/config.ks -%include ingredients/hypervisor/intelcpu.ks -%include ingredients/repo/rawhide-mirrors.ks diff --git a/tests/fixtures/expected_recipes/live-desktop_rawhide.cfg b/tests/fixtures/expected_recipes/live-desktop_rawhide.cfg deleted file mode 100644 index dbe4405..0000000 --- a/tests/fixtures/expected_recipes/live-desktop_rawhide.cfg +++ /dev/null @@ -1,23 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A recipe for a live desktop - -%include ingredients/shared/live/core/base.ks -%include ingredients/shared/live/core/storage.ks -%include ingredients/platform/generic-rawhide/bootloader/grub.ks -%include ingredients/shared/core/locale.ks -%include ingredients/shared/core/services.ks -%include ingredients/shared/core/network.ks -%include ingredients/shared/live/core/packages.ks -%include ingredients/shared/live/post/base.ks -%include ingredients/shared/live/post/session.ks -%include ingredients/shared/initial-setup/desktop/config.ks -%include ingredients/shared/desktop/gnome/config.ks -%include ingredients/shared/desktop/gnome/packages.ks -%include ingredients/shared/desktop/gnome/post-scripts.ks -%include ingredients/platform/generic-rawhide/repo/rawhide-mirrors.ks \ No newline at end of file diff --git a/tests/fixtures/expected_recipes/live-server_rawhide.cfg b/tests/fixtures/expected_recipes/live-server_rawhide.cfg deleted file mode 100644 index a128ffe..0000000 --- a/tests/fixtures/expected_recipes/live-server_rawhide.cfg +++ /dev/null @@ -1,20 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A recipe for a live server - -%include ingredients/shared/live/core/base.ks -%include ingredients/shared/live/core/storage.ks -%include ingredients/platform/generic-rawhide/bootloader/grub.ks -%include ingredients/shared/core/locale.ks -%include ingredients/shared/core/services.ks -%include ingredients/shared/core/network.ks -%include ingredients/shared/live/core/packages.ks -%include ingredients/shared/live/post/base.ks -%include ingredients/shared/live/post/session.ks -%include ingredients/shared/initial-setup/server/config.ks -%include ingredients/platform/generic-rawhide/repo/rawhide-mirrors.ks \ No newline at end of file diff --git a/tests/fixtures/expected_recipes/virtual-desktop_43.cfg b/tests/fixtures/expected_recipes/virtual-desktop_43.cfg deleted file mode 100644 index b312795..0000000 --- a/tests/fixtures/expected_recipes/virtual-desktop_43.cfg +++ /dev/null @@ -1,20 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A recipe for a virtual desktop - -%include ingredients/shared/core/base.ks -%include ingredients/shared/storage/standard.ks -%include ingredients/platform/generic-43/bootloader/grub.ks -%include ingredients/shared/core/locale.ks -%include ingredients/shared/core/services.ks -%include ingredients/shared/core/network.ks -%include ingredients/shared/packages/core-group.ks -%include ingredients/shared/packages/fedora-remix.ks -%include ingredients/shared/packages/hand-picked.ks -%include ingredients/shared/initial-setup/server/config.ks -%include ingredients/platform/generic-43/repo/fedora-mirrors.ks \ No newline at end of file diff --git a/tests/fixtures/expected_recipes/virtual-server_rawhide.cfg b/tests/fixtures/expected_recipes/virtual-server_rawhide.cfg deleted file mode 100644 index cb491f2..0000000 --- a/tests/fixtures/expected_recipes/virtual-server_rawhide.cfg +++ /dev/null @@ -1,20 +0,0 @@ -# __ ____ ____ _____ -# ____ / /_ __ __/ / /___ ____ ___ ___ / __ \/ ___/ -# / __ \/ __ \/ / / / / / __ \/ __ `__ \/ _ \ / / / /\__ \ -# / /_/ / / / / /_/ / / / /_/ / / / / / / __/ / /_/ /___/ / -# / .___/_/ /_/\__, /_/_/\____/_/ /_/ /_/\___/ \____//____/ -# /_/ /____/ - -# A recipe for a virtual server - -%include ingredients/shared/core/base.ks -%include ingredients/shared/storage/standard.ks -%include ingredients/platform/generic-43/bootloader/grub.ks -%include ingredients/shared/core/locale.ks -%include ingredients/shared/core/services.ks -%include ingredients/shared/core/network.ks -%include ingredients/shared/packages/core-group.ks -%include ingredients/shared/packages/fedora-remix.ks -%include ingredients/shared/packages/hand-picked.ks -%include ingredients/shared/initial-setup/server/config.ks -%include ingredients/platform/generic-rawhide/repo/rawhide-mirrors.ks \ No newline at end of file diff --git a/tests/integration/__pycache__/conftest.cpython-314-pytest-8.4.2.pyc b/tests/integration/__pycache__/conftest.cpython-314-pytest-8.4.2.pyc deleted file mode 100644 index 9ec58909809e1b038a0212517a734bc074529ab2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2411 zcmbVM-*4MQ96vj5ez}XoXz35J)>XT8=~%pqwGvV_A!V?3g4J@XN=VJDEV;I3ZXBJj zV+jJK0%;Gtl(#+Zb$WL!~+r%k8~6y#1r2;JBib&6XGP>Uw`j&cc1%we_YF4 zNFx}3fAO6Y8$hVce>x)`8mB)<2sM#~D7ud5778zFq5DOn2}eK z%PE=)eQBC5rlwF)y$QSUVU7jg==)~)V~$6?f&E{7G*ir2v3Jzo4pb|qO}+QuvCQd? z)%)H@#f!jF>#)2j2Xm9Cm@u#Oa2?B1Mo@<}9Xbay5Wx(egNa8lZ=HilL@*=gV3HBc z=sB2F1T$ty(ATL;EKLS9oL}_IJZ2an>%&a^>(qo zx~4l-inmFLI$l-h%l-VloWvBfM0ftl5?pp_`1&@kVt{z0FRNmpAUw+HhhQ{=yg|!S z4Nam=rK_$SjbndQYtjaqLK{*iQg92Up%(})QLJ0y=sTU0fu~PP)iTZlJMGLQ;UR7e z2W)MMcWW+e;pgk6T{jnF;Wg~;H3+B222E@A?gxc++ge>+&&h!^(_L+EIWG43&brQS z89Sht;qE;w`9{I3VB?WPs8I`r^n62TXGGLA%3gJgd-JtAOZCo9!HyT;@pi zF#qfDRBL9jJ-pa@&ukBy&3nJ7<4PN^hHR30Ou+4KxobU5JuO>R-1fO1StFtX-_W!XQ3t0vk)W^yl zAbazT#N3=Q2U9tjDK0>nvh8{gAPKmLk(&^%rFiA|1n=HK=(7jfadze~JM-+5qpWeH z-e@T|UcF2=6>f-yXH&s?O&1VVzYqohKdwQZLc@9`5~6W!du|0+_1%(BG4b^ct!&kh#z>pn}`qBFs{u+sP3M;Or1m(iV hU+wy@_W5VJ{+T}ieAhqU=f5q9i9cjnx-E&&{|h_42eJSF diff --git a/tests/integration/__pycache__/test_integration.cpython-314-pytest-8.4.2.pyc b/tests/integration/__pycache__/test_integration.cpython-314-pytest-8.4.2.pyc deleted file mode 100644 index dcaceeaf3f573d19ded0f77c5ea7655a21c41946..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14153 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 0, f"Expected recipes, got {recipe_count}" - - # Validate each recipe - for recipe_file in RECIPE_DIR.glob('*.cfg'): - content = recipe_file.read_text() - assert len(content) > 0, f"Recipe {recipe_file.name} is empty" - assert '%include' in content, f"Recipe {recipe_file.name} missing %include" - - -def test_make_targets(): - """Test Makefile targets work.""" - # Test generate-recipes - result = subprocess.run( - ['make', 'generate-recipes'], - cwd=RECIPE_GENERATOR_DIR, - capture_output=True, - text=True - ) - assert result.returncode == 0, f"make generate-recipes failed: {result.stderr}" - - -def test_container_build(): - """Test container builds successfully.""" - result = subprocess.run( - ['podman', 'build', '-t', 'phyllo/test-runner', '.'], - cwd=CONTAINER_DIR, - capture_output=True, - text=True - ) - - # Should build even if podman fails (might not be installed) - # The important thing is the Containerfile syntax is valid - if result.returncode != 0 and 'command not found' not in result.stderr: - pytest.fail(f"Container build failed: {result.stderr}") - - -def test_ingredients_structure(): - """Verify ingredient directory structure.""" - # Check core directories - assert (INGREDIENTS_DIR / 'core' / 'security').exists() - - # Check desktop directories - assert (INGREDIENTS_DIR / 'desktop' / 'gnome').exists() - assert (INGREDIENTS_DIR / 'desktop' / 'labwc').exists() - # vmm is now in virtual-machine-manager directory (under packages) - # assert (INGREDIENTS_DIR / 'desktop' / 'vmm').exists() - - # Check hypervisor directories - assert (INGREDIENTS_DIR / 'hypervisor' / 'base').exists() - assert (INGREDIENTS_DIR / 'hypervisor' / 'base').exists() - - # Check live directories - assert (INGREDIENTS_DIR / 'live' / 'core' / 'bootloader').exists() - assert (INGREDIENTS_DIR / 'live' / 'post').exists() - - # Count ingredients - ingredient_count = len(list(INGREDIENTS_DIR.glob('**/*.ks'))) - assert ingredient_count >= 45, f"Expected at least 45 ingredients, found {ingredient_count}" diff --git a/tests/test_recipe_generator.py b/tests/test_recipe_generator.py deleted file mode 100644 index b3adff5..0000000 --- a/tests/test_recipe_generator.py +++ /dev/null @@ -1,269 +0,0 @@ -"""Tests for the Recipe Generator.""" - -import pytest -from pathlib import Path -import sys - -RECIPE_GENERATOR_DIR = Path(__file__).parent.parent / 'recipe-generator' -sys.path.insert(0, str(RECIPE_GENERATOR_DIR)) - -from recipe_generator import RecipeGenerator - - -class TestRecipeGenerator: - """Test RecipeGenerator class.""" - - def setup_method(self): - """Set up test fixtures.""" - project_root = Path(__file__).parent.parent - self.generator = RecipeGenerator( - project_root / 'recipe-generator' / 'recipe_templates.yaml' - ) - - def test_template_loading(self): - """Test that templates are loaded correctly.""" - assert isinstance(self.generator.templates, dict) - assert len(self.generator.templates) == 2 - assert 'install' in self.generator.templates - assert 'live' in self.generator.templates - - def test_validate_template_success(self): - """Test validation of valid templates.""" - template = self.generator.templates['install'] - errors = self.generator.validate_template(template) - assert errors == [] - - def test_validate_template_missing_key(self): - """Test validation detects missing keys.""" - template = {'base': 'core'} - errors = self.generator.validate_template(template) - assert any('Missing required key' in error for error in errors) - - def test_validate_template_missing_ingredient(self): - """Test validation detects missing ingredients.""" - template = { - 'description': 'Test', - 'base': 'core', - 'required': [ - {'storage': 'core-storage'}, - {'nonexistent': 'nonexistent-ingredient'} - ] - } - errors = self.generator.validate_template(template) - assert any('not found' in error for error in errors) - - def test_generate_virtual_desktop_basic(self): - """Test generating basic desktop recipe.""" - content = self.generator.generate_recipe('install', '43', - desktop='gnome', - storage='standard', - security='secure') - assert '# An install recipe for desktop, server, or hypervisor' in content - assert '%include ingredients/core/base.ks' in content - assert '%include ingredients/desktop/gnome/packages.ks' in content - assert '%include ingredients/repo/fedora-43-mirrors.ks' in content - assert '%include ingredients/core/security/enabled.ks' in content - - def test_generate_virtual_desktop_encrypted(self): - """Test generating encrypted desktop recipe.""" - content = self.generator.generate_recipe('install', 'rawhide', - desktop='gnome', - storage='encrypted', - security='secure') - assert '%include ingredients/storage/encrypted.ks' in content - - def test_generate_virtual_desktop_labwc(self): - """Test generating LabWC desktop recipe.""" - content = self.generator.generate_recipe('install', '43', - desktop='labwc', - storage='standard', - security='secure') - assert '%include ingredients/desktop/labwc/config.ks' in content - assert '%include ingredients/desktop/gnome/packages.ks' not in content - - def test_generate_virtual_desktop_devel(self): - """Test generating development mode desktop recipe.""" - content = self.generator.generate_recipe('install', '43', - desktop='gnome', - storage='standard', - security='off') - assert '%include ingredients/core/security/disabled.ks' in content - - def test_generate_virtual_server(self): - """Test generating server recipe.""" - content = self.generator.generate_recipe('install', 'rawhide', - initial_setup='server', - security='secure') - assert '# An install recipe for desktop, server, or hypervisor' in content - assert '%include ingredients/initial-setup/server/config.ks' in content - - def test_generate_desktop_hypervisor_amd(self): - """Test generating AMD CPU hypervisor recipe.""" - content = self.generator.generate_recipe('install', 'rawhide', - variant_type='hypervisor', - hypervisor='base', - hypervisor_type='amdcpu', - security='secure') - assert '%include ingredients/hypervisor/amdcpu.ks' in content - assert '%include ingredients/hypervisor/intelcpu.ks' not in content - - def test_generate_desktop_hypervisor_intel_gpu(self): - """Test generating Intel CPU+GPU hypervisor recipe.""" - content = self.generator.generate_recipe('install', 'rawhide', - variant_type='hypervisor', - hypervisor='base', - hypervisor_type='intelcpu', - security='secure') - assert '%include ingredients/hypervisor/intelcpu.ks' in content - - def test_generate_live_desktop(self): - """Test generating live desktop recipe.""" - content = self.generator.generate_recipe('live', 'rawhide', - desktop='gnome', - security='secure') - assert '# A live recipe for live-desktop or live-server' in content - assert '%include ingredients/live/core/base.ks' in content - assert '%include ingredients/live/core/storage.ks' in content - - def test_generate_live_server(self): - """Test generating live server recipe.""" - content = self.generator.generate_recipe('live', 'rawhide', - security='secure') - assert '# A live recipe for live-desktop or live-server' in content - assert '%include ingredients/live/core/base.ks' in content - - def test_no_duplicate_includes(self): - """Test that duplicate includes are prevented.""" - content = self.generator.generate_recipe('install', 'rawhide', - variant_type='hypervisor', - hypervisor='base', - hypervisor_type='intelcpu', - security='secure') - includes = [line for line in content.split('\n') if line.startswith('%include')] - paths = [line.split()[1] for line in includes] - assert len(paths) == len(set(paths)), "Found duplicate %include entries" - - def test_missing_ingredient_detection(self): - """Test that missing ingredients are detected.""" - content = self.generator.generate_recipe('install', '43', - desktop='gnome', - storage='standard', - security='secure') - issues = self.generator.validate_recipe(content) - assert issues == [], f"Found missing ingredients: {issues}" - - def test_filename_generation_standard(self): - """Test filename generation for standard configuration.""" - filename = self.generator.generate_filename('install', '43', - desktop='gnome', - storage='standard', - security='secure') - assert filename == 'install_43.cfg' - - def test_filename_generation_encrypted(self): - """Test filename generation for encrypted storage.""" - filename = self.generator.generate_filename('install', 'rawhide', - desktop='gnome', - storage='encrypted', - security='secure') - assert filename == 'install_encrypted_rawhide.cfg' - - def test_filename_generation_devel(self): - """Test filename generation for development mode.""" - filename = self.generator.generate_filename('install', '43', - desktop='gnome', - storage='standard', - security='off') - assert filename == 'install_devel_43.cfg' - - def test_filename_generation_hypervisor(self): - """Test filename generation for hypervisor.""" - filename = self.generator.generate_filename('install', 'rawhide', - variant_type='hypervisor', - hypervisor='base', - hypervisor_type='intelcpu', - security='secure') - assert filename == 'install_hypervisor_intelcpu_rawhide.cfg' - - def test_invalid_recipe_type(self): - """Test error on invalid recipe type.""" - with pytest.raises(SystemExit) as exc_info: - self.generator.generate_recipe('nonexistent-type', '43') - assert exc_info.value.code == 1 - - def test_manifest_validation_missing_recipes_key(self): - """Test manifest validation detects missing recipes key.""" - manifest = {} - errors = self.generator.validate_manifest(manifest) - assert any('recipes' in error for error in errors) - - def test_manifest_validation_missing_name(self): - """Test manifest validation detects missing recipe name.""" - manifest = {'recipes': [{}]} - errors = self.generator.validate_manifest(manifest) - assert any('name' in error for error in errors) - - def test_manifest_validation_missing_version(self): - """Test manifest validation detects missing version.""" - manifest = {'recipes': [{'name': 'test', 'variants': [{}]}]} - errors = self.generator.validate_manifest(manifest) - assert any('version' in error for error in errors) - - def test_get_ksversion_43(self): - """Test version mapping for Fedora 43.""" - version = self.generator.get_ksversion('43') - assert version == 'F42' - - def test_get_ksversion_rawhide(self): - """Test version mapping for rawhide.""" - version = self.generator.get_ksversion('rawhide') - assert version is None - - def test_validate_recipe_semantic_f42(self): - """Test semantic validation with valid recipe for F42.""" - content = """text -poweroff -zerombr -clearpart --all --initlabel -part /boot/efi --fstype="efi" --size=512 -part / --fstype="ext4" --grow -%packages - -%end -""" - issues = self.generator.validate_recipe_semantic(content, '43') - assert issues == [] - - def test_validate_recipe_semantic_invalid_syntax(self): - """Test semantic validation detects invalid syntax.""" - content = "validcommand\ninvalidcommand without proper format\n%packages\n%end" - issues = self.generator.validate_recipe_semantic(content, '43') - assert len(issues) > 0 - - def test_validate_recipe_semantic_empty(self): - """Test semantic validation with empty content.""" - content = "" - issues = self.generator.validate_recipe_semantic(content, '43') - assert issues == [] - - def test_filename_generation_with_guest_agents(self): - """Test filename includes 'virtual' when guest_agents=True.""" - filename = self.generator.generate_filename('install', '43', - variant_type='desktop', - guest_agents=True) - assert filename == 'install_virtual_desktop_43.cfg' - - def test_filename_generation_with_hardware_support(self): - """Test filename includes 'hardware-support' when enabled.""" - filename = self.generator.generate_filename('install', '43', - variant_type='desktop', - hardware_support=True) - assert filename == 'install_desktop_hardware-support_43.cfg' - - def test_filename_generation_with_both_modifiers(self): - """Test filename includes both modifiers when enabled.""" - filename = self.generator.generate_filename('install', '43', - variant_type='desktop', - guest_agents=True, - hardware_support=True) - assert filename == 'install_virtual_desktop_hardware-support_43.cfg'