diff --git a/.coverage-threshold b/.coverage-threshold index 480a1235..bf966088 100644 --- a/.coverage-threshold +++ b/.coverage-threshold @@ -1 +1 @@ -64.8 \ No newline at end of file +65.5 \ No newline at end of file diff --git a/.github/workflows/build-azl3-arm-raw.yml b/.github/workflows/build-azl3-arm-raw.yml new file mode 100644 index 00000000..55c0e66f --- /dev/null +++ b/.github/workflows/build-azl3-arm-raw.yml @@ -0,0 +1,95 @@ +name: Build Azure Linux Raw Image - ARM +on: + workflow_dispatch: # Manual runs + push: + inputs: + ref: + description: "Branch or SHA to test (e.g. feature/x or a1b2c3)" + required: false + run_qemu_test: + description: "Run QEMU boot test after build" + required: false + default: "false" + type: choice + options: + - "true" + - "false" +permissions: + contents: read + +jobs: + build-azl3-arm-raw: + runs-on: ubuntu-24.04-arm + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Earthly + uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + version: "latest" + + - name: Install system deps + run: | + sudo apt-get update + sudo apt-get install -y qemu-system-aarch64 ovmf tree jq systemd-ukify mmdebstrap systemd-boot qemu-efi-aarch64 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Prepare build script + run: | + if [ ! -f scripts/build_azl3_arm_raw.sh ]; then + echo "scripts/build_azl3_arm_raw.sh not found!" + exit 1 + fi + chmod +x scripts/build_azl3_arm_raw.sh + + - name: Run azl3 Raw Image Build + env: + #RUN_QEMU_TEST: ${{ github.event.push }} + RUN_QEMU_TEST: false + run: | + echo "Starting azl3 raw image build..." + # Ensure script has access to docker group for Earthly + sudo usermod -aG docker $USER + + # Prepare arguments with input validation + ARGS="" + case "${RUN_QEMU_TEST}" in + "true") + ARGS="--qemu-test" + echo "QEMU boot test will be run after build" + ;; + "false"|"") + echo "QEMU boot test will be skipped" + ;; + *) + echo "Invalid input for run_qemu_test: ${RUN_QEMU_TEST}" + exit 1 + ;; + esac + + # Run the azl3 raw image build script + ./scripts/build_azl3_arm_raw.sh $ARGS + echo "azl3 raw image build completed." + + - name: Set file permissions for artifacts + run: | + sudo chmod -R 755 workspace/ || true + find workspace/*/imagebuild/*/ -name "*.raw*" -exec chmod 777 {} \; || true + - name: GitHub Upload Release Artifacts + uses: actions/upload-artifact@v4 + with: + name: azl3-arm-raw-image + path: | + workspace/*/imagebuild/*/*.raw* + retention-days: 30 diff --git a/.github/workflows/build-elxr12-arm-raw.yml b/.github/workflows/build-elxr12-arm-raw.yml new file mode 100644 index 00000000..205ee65a --- /dev/null +++ b/.github/workflows/build-elxr12-arm-raw.yml @@ -0,0 +1,102 @@ +name: Build ELXR12 Raw Image - ARM +on: + workflow_dispatch: # Manual runs + inputs: + ref: + description: "Branch or SHA to test (e.g. feature/x or a1b2c3)" + required: false + run_qemu_test: + description: "Run QEMU boot test after build" + required: false + default: "false" + type: choice + options: + - "true" + - "false" + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + build-elxr12-arm-raw: + runs-on: ubuntu-24.04-arm + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + ref: ${{ github.event.inputs.ref || github.ref }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Earthly + uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + version: "latest" + + - name: Install system deps + run: | + sudo apt-get update + sudo apt-get install -y qemu-system-aarch64 ovmf tree jq systemd-ukify mmdebstrap systemd-boot qemu-efi-aarch64 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Prepare build script + run: | + if [ ! -f scripts/build_elxr12_arm_raw.sh ]; then + echo "scripts/build_elxr12_arm_raw.sh not found!" + exit 1 + fi + chmod +x scripts/build_elxr12_arm_raw.sh + + - name: Run ARM ELXR12 Raw Image Build + env: + #RUN_QEMU_TEST: ${{ github.event.inputs.run_qemu_test }} + RUN_QEMU_TEST: false + run: | + echo "Starting ARM ELXR12 raw image build..." + # Ensure script has access to docker group for Earthly + sudo usermod -aG docker $USER + + # Prepare arguments with input validation + ARGS="" + case "${RUN_QEMU_TEST}" in + "true") + ARGS="--qemu-test" + echo "QEMU boot test will be run after build" + ;; + "false"|"") + echo "QEMU boot test will be skipped" + ;; + *) + echo "Invalid input for run_qemu_test: ${RUN_QEMU_TEST}" + exit 1 + ;; + esac + + # Run the ELXR12 raw image build script + ./scripts/build_elxr12_arm_raw.sh $ARGS + echo "ELXR12 raw image build completed." + - name: Set file permissions for artifacts + run: | + sudo chmod -R 755 workspace/ || true + find workspace/*/imagebuild/*/ -name "*.raw*" -exec chmod 777 {} \; || true + + - name: GitHub Upload Release Artifacts + uses: actions/upload-artifact@v4 + with: + name: elxr12-arm-raw-image + path: | + workspace/*/imagebuild/*/*.raw* + retention-days: 30 diff --git a/.github/workflows/build-elxr12-raw.yml b/.github/workflows/build-elxr12-raw.yml index b93fa36d..5b6dcc40 100644 --- a/.github/workflows/build-elxr12-raw.yml +++ b/.github/workflows/build-elxr12-raw.yml @@ -104,4 +104,4 @@ jobs: -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ --data "{\"body\": \"$COMMENT_BODY\"}" \ - "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ No newline at end of file + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" diff --git a/.github/workflows/build-ubuntu24-arm-raw.yml b/.github/workflows/build-ubuntu24-arm-raw.yml new file mode 100644 index 00000000..8833c5f9 --- /dev/null +++ b/.github/workflows/build-ubuntu24-arm-raw.yml @@ -0,0 +1,96 @@ +name: Build Ubuntu24 Raw Image - ARM +on: + workflow_dispatch: # Manual runs + push: + inputs: + ref: + description: "Branch or SHA to test (e.g. feature/x or a1b2c3)" + required: false + run_qemu_test: + description: "Run QEMU boot test after build" + required: false + default: "false" + type: choice + options: + - "true" + - "false" + +permissions: + contents: read + +jobs: + build-ubuntu24-arm-raw: + runs-on: ubuntu-24.04-arm + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Earthly + uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + version: "latest" + + - name: Install system deps + run: | + sudo apt-get update + sudo apt-get install -y qemu-system-aarch64 ovmf tree jq systemd-ukify mmdebstrap systemd-boot qemu-efi-aarch64 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Prepare build script + run: | + if [ ! -f scripts/build_ubuntu24_arm_raw.sh ]; then + echo "scripts/build_ubuntu24_arm_raw.sh not found!" + exit 1 + fi + chmod +x scripts/build_ubuntu24_arm_raw.sh + + - name: Run Ubuntu24 Raw Image Build + env: + #RUN_QEMU_TEST: ${{ github.event.push }} + RUN_QEMU_TEST: false + run: | + echo "Starting Ubuntu24 raw image build..." + # Ensure script has access to docker group for Earthly + sudo usermod -aG docker $USER + + # Prepare arguments with input validation + ARGS="" + case "${RUN_QEMU_TEST}" in + "true") + ARGS="--qemu-test" + echo "QEMU boot test will be run after build" + ;; + "false"|"") + echo "QEMU boot test will be skipped" + ;; + *) + echo "Invalid input for run_qemu_test: ${RUN_QEMU_TEST}" + exit 1 + ;; + esac + + # Run the Ubuntu24 raw image build script + ./scripts/build_ubuntu24_arm_raw.sh $ARGS + echo "Ubuntu24 raw image build completed." + - name: Set file permissions for artifacts + run: | + sudo chmod -R 755 workspace/ || true + find workspace/*/imagebuild/minimal/ -name "*.raw*" -exec chmod 777 {} \; || true + + - name: GitHub Upload Release Artifacts + uses: actions/upload-artifact@v4 + with: + name: ubuntu24-arm-raw-image + path: | + workspace/*/imagebuild/minimal/*.raw* + retention-days: 30 diff --git a/config/osv/azure-linux/azl3/imageconfigs/defaultconfigs/default-raw-aarch64.yml b/config/osv/azure-linux/azl3/imageconfigs/defaultconfigs/default-raw-aarch64.yml new file mode 100644 index 00000000..e321c9d1 --- /dev/null +++ b/config/osv/azure-linux/azl3/imageconfigs/defaultconfigs/default-raw-aarch64.yml @@ -0,0 +1,103 @@ +image: + name: azl3-default-aarch64 + version: "1.0.0" + +target: + os: azure-linux # Target OS name + dist: azl3 # Target OS distribution + arch: aarch64 # Target OS architecture + imageType: raw # Image type, valid value: [raw, iso]. + +disk: + name: Default_Raw # 1:1 mapping to the systemConfigs name + artifacts: + - + type: raw # image file format + compression: gz # image compression format (optional) + size: 4GiB # 4G, 4GB, 4096 MiB also valid. (Required for raw) + partitionTableType: gpt # Partition table type, valid value: [gpt, mbr] + partitions: # Required for raw, optional for ISO, not needed for rootfs. + - id: boot + type: esp + flags: + - esp + - boot + start: 1MiB + end: 513MiB + fsType: fat32 + mountPoint: /boot/efi + mountOptions: umask=0077 + + - id: rootfs + type: linux-root-arm64 + start: 513MiB + end: 2561MiB # 513MiB + 2GiB (2048MiB) = 2561MiB + fsType: ext4 + mountPoint: / + mountOptions: defaults, ro + + - id: roothashmap + type: linux + start: 2561MiB + end: 3061MiB # 2561MiB + 500MiB = 3061MiB + fsType: ext4 + mountPoint: none + + - id: userdata + type: linux + start: 3061MiB + end: "0" # 2561MiB + 2GiB (2048MiB) = 4609MiB + fsType: ext4 + mountPoint: /opt + +systemConfig: + name: Default_Raw + description: Default yml configuration for raw image + + bootloader: + bootType: efi # (efi or legacy) + provider: systemd-boot # (grub for efi and legacy mode, or systemd-boot for efi mode) + + immutability: + enabled: false # default is true + + packages: + - filesystem + - kernel + - azurelinux-release + # hyperv packages + - dracut-hyperv + - hyperv-daemons + # core image packages + - shim + - systemd-boot + - systemd-ukify + - ca-certificates + - cronie-anacron + - logrotate + - core-packages-base-image + - dracut-hostonly + - dracut-vrf + - initramfs + - shadow-utils + # ssh-server + - openssh-server + # cloud-init packages + - cloud-init + - cloud-utils-growpart + # virt guest packages + - dracut-virtio + - dracut-xen + - veritysetup + + additionalFiles: + - local: ../additionalfiles/99-dhcp-en.network + final: /etc/systemd/network/99-dhcp-en.network + + kernel: + name: kernel + cmdline: "console=ttyS0,115200 console=tty0 loglevel=7" + enableExtraModules: "usbcore usb-common" + packages: + - kernel + uki: true diff --git a/config/osv/azure-linux/azl3/imageconfigs/defaultconfigs/default-raw-x86_64.yml b/config/osv/azure-linux/azl3/imageconfigs/defaultconfigs/default-raw-x86_64.yml index 653d4c7f..40806b80 100644 --- a/config/osv/azure-linux/azl3/imageconfigs/defaultconfigs/default-raw-x86_64.yml +++ b/config/osv/azure-linux/azl3/imageconfigs/defaultconfigs/default-raw-x86_64.yml @@ -59,7 +59,7 @@ systemConfig: provider: systemd-boot # (grub for efi and legacy mode, or systemd-boot for efi mode) immutability: - enabled: true # default is true + enabled: false # default is true packages: - filesystem diff --git a/config/osv/azure-linux/azl3/providerconfigs/aarch64_repo.yml b/config/osv/azure-linux/azl3/providerconfigs/aarch64_repo.yml new file mode 100644 index 00000000..1049c4ec --- /dev/null +++ b/config/osv/azure-linux/azl3/providerconfigs/aarch64_repo.yml @@ -0,0 +1,13 @@ +# Azure Linux 3.0 Repository Configuration +name: "Azure Linux 3.0" +type: "rpm" # Repository type: rpm or deb +baseURL: "https://packages.microsoft.com/azurelinux/3.0/prod/base/{arch}" +component: "azl3.0-base" # Repository component/section identifier +gpgCheck: true # Re-enabled with correct GPG key +repoGPGCheck: true # Re-enabled with correct GPG key +enabled: true +gpgKey: "repodata/repomd.xml.key" # Relative path from resolved repository URL +# For RPM-based repositories, we use the baseURL pattern +# The actual repository URL is constructed as: {baseURL}/{arch}/ +# Repodata is accessed at: {baseURL}/{arch}/repodata/repomd.xml +buildPath: "./builds/azl3" # Will be replaced with temp_dir/builds/azl3 at runtime \ No newline at end of file diff --git a/config/osv/azure-linux/azl3/providerconfigs/x86_64_repo.yml b/config/osv/azure-linux/azl3/providerconfigs/x86_64_repo.yml new file mode 100644 index 00000000..1049c4ec --- /dev/null +++ b/config/osv/azure-linux/azl3/providerconfigs/x86_64_repo.yml @@ -0,0 +1,13 @@ +# Azure Linux 3.0 Repository Configuration +name: "Azure Linux 3.0" +type: "rpm" # Repository type: rpm or deb +baseURL: "https://packages.microsoft.com/azurelinux/3.0/prod/base/{arch}" +component: "azl3.0-base" # Repository component/section identifier +gpgCheck: true # Re-enabled with correct GPG key +repoGPGCheck: true # Re-enabled with correct GPG key +enabled: true +gpgKey: "repodata/repomd.xml.key" # Relative path from resolved repository URL +# For RPM-based repositories, we use the baseURL pattern +# The actual repository URL is constructed as: {baseURL}/{arch}/ +# Repodata is accessed at: {baseURL}/{arch}/repodata/repomd.xml +buildPath: "./builds/azl3" # Will be replaced with temp_dir/builds/azl3 at runtime \ No newline at end of file diff --git a/config/osv/edge-microvisor-toolkit/emt3/chrootenvconfigs/chrootenv_aarch64.yml b/config/osv/edge-microvisor-toolkit/emt3/chrootenvconfigs/chrootenv_aarch64.yml new file mode 100644 index 00000000..a1d00f2e --- /dev/null +++ b/config/osv/edge-microvisor-toolkit/emt3/chrootenvconfigs/chrootenv_aarch64.yml @@ -0,0 +1,3 @@ +packages: + - core-packages-container + - createrepo_c \ No newline at end of file diff --git a/config/osv/edge-microvisor-toolkit/emt3/imageconfigs/defaultconfigs/default-raw-aarch64.yml b/config/osv/edge-microvisor-toolkit/emt3/imageconfigs/defaultconfigs/default-raw-aarch64.yml new file mode 100644 index 00000000..2566c973 --- /dev/null +++ b/config/osv/edge-microvisor-toolkit/emt3/imageconfigs/defaultconfigs/default-raw-aarch64.yml @@ -0,0 +1,153 @@ +image: + name: emt3-default-x86_64 + version: "1.0.0" + +target: + os: edge-microvisor-toolkit # Target OS name + dist: emt3 # Target OS distribution + arch: aarch64 # Target OS architecture + imageType: raw # Image type, valid value: [raw, iso]. + +disk: + name: Default_Raw # 1:1 mapping to the systemConfigs name + artifacts: + - + type: raw # image file format + compression: gz # image compression format (optional) + size: 4GiB # 4G, 4GB, 4096 MiB also valid. (Required for raw) + partitionTableType: gpt # Partition table type, valid value: [gpt, mbr] + partitions: # Required for raw, optional for ISO, not needed for rootfs. + - id: boot + type: esp + flags: + - esp + - boot + start: 1MiB + end: 513MiB + fsType: fat32 + mountPoint: /boot/efi + mountOptions: umask=0077 + + - id: rootfs + type: linux-root-arm64 + start: 513MiB + end: 2561MiB + fsType: ext4 + mountPoint: / + mountOptions: defaults, ro + + - id: roothashmap + type: linux + start: 2561MiB + end: 3061MiB + fsType: ext4 + mountPoint: none + + - id: userdata + type: linux + start: 3061MiB + end: "0" # use the rest of the disk space + fsType: ext4 + mountPoint: /opt + +systemConfig: + name: Default_Raw + description: Default yml configuration for raw image + + bootloader: + bootType: efi # (efi or legacy) + provider: systemd-boot # (grub for efi and legacy mode, or systemd-boot for efi mode) + + immutability: + enabled: true # default is true + + packages: + # base packages + - systemd-boot + - systemd-ukify + - ca-certificates + - cronie-anacron + - logrotate + - core-packages-base-image + - initramfs + - shadow-utils + - coreutils + + # ssh server + - openssh-server + + # virtualization host + - qemu-kvm + + # agent packages + - hardware-discovery-agent + - cluster-agent + - node-agent + - platform-observability-agent + - platform-telemetry-agent + - platform-update-agent + - in-band-manageability + - reporting-agent + + # tinker tools + - gptfdisk + - cloud-init + - cloud-utils-growpart + - tpm2-tools + - lvm2 + - netplan + - efibootmgr + - unzip + - rsyslog + - tpm2-initramfs-tool + - veritysetup + - python3-pyserial + + # persistent mount + - rsync + + # selinux full + - selinux-policy + - selinux-policy-devel + - policycoreutils-python-utils + - checkpolicy + - secilc + - setools-console + + # intel gpu base + #- kernel-drivers-gpu + - linux-firmware-i915 + - linux-firmware-ice + - intel-npu-firmware + + # intel wireless + - linux-firmware-iwlwifi + - wireless-regdb + + # os update + - os-update + + # vpro amt + - platform-manageability-agent + - rpc + - intel-lms + + # docker + - moby-engine + - docker-cli + - docker-compose + + additionalFiles: + - local: ../additionalfiles/99-dhcp-en.network + final: /etc/systemd/network/99-dhcp-en.network + - local: ../additionalfiles/systemd-networkd-wait-online-override.conf + final: /etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf + - local: ../additionalfiles/emt-cloud-init.cfg + final: /etc/cloud/cloud.cfg + + kernel: + name: kernel + cmdline: "console=ttyS0,115200 console=tty0 loglevel=7" + uki: true + packages: + - kernel-drivers-gpu diff --git a/config/osv/edge-microvisor-toolkit/emt3/providerconfigs/x86_64_repo.yml b/config/osv/edge-microvisor-toolkit/emt3/providerconfigs/x86_64_repo.yml new file mode 100644 index 00000000..4f26248e --- /dev/null +++ b/config/osv/edge-microvisor-toolkit/emt3/providerconfigs/x86_64_repo.yml @@ -0,0 +1,12 @@ +# Edge Microvisor Toolkit 3.0 Repository Configuration +name: "Edge Microvisor Toolkit 3.0" +type: "rpm" # Repository type: rpm or deb +#baseURL: "https://files-rs.edgeorchestration.intel.com/files-edge-orch/microvisor/rpm/3.0" +baseURL: "https://files-rs.edgeorchestration.intel.com/files-edge-orch/microvisor/rpms/3.0/base" +component: "emt3.0-base" # Repository component/section identifier +gpgCheck: true # Enabled with Intel GPG key +repoGPGCheck: true # Enabled with Intel GPG key +enabled: true +gpgKey: "https://raw.githubusercontent.com/open-edge-platform/edge-microvisor-toolkit/refs/heads/3.0/SPECS/edge-repos/INTEL-RPM-GPG-KEY" +# Repository configuration URL (for reference): https://raw.githubusercontent.com/open-edge-platform/edge-microvisor-toolkit/refs/heads/3.0/SPECS/edge-repos/edge-base.repo +buildPath: "./builds/emt3" # Will be replaced with temp_dir/builds/emt3 at runtime diff --git a/config/osv/ubuntu/ubuntu24/chrootenvconfigs/chrootenv_aarch64.yml b/config/osv/ubuntu/ubuntu24/chrootenvconfigs/chrootenv_aarch64.yml new file mode 100644 index 00000000..b255be7c --- /dev/null +++ b/config/osv/ubuntu/ubuntu24/chrootenvconfigs/chrootenv_aarch64.yml @@ -0,0 +1,30 @@ +essential: + - dpkg + - apt + - debianutils + - init-system-helpers + - dash + - mount + - sysvinit-utils + - gzip + - bash + - util-linux + - tar + - base-files + - base-passwd + - sed + - bsdutils + - coreutils + - findutils + - grep + - login + - perl-base + - diffutils + - libc-bin + - hostname + - ncurses-bin + - ncurses-base + +packages: + - mmdebstrap + - grub-efi-arm64-bin diff --git a/config/osv/ubuntu/ubuntu24/config.yml b/config/osv/ubuntu/ubuntu24/config.yml index 052b2ab2..d92e273c 100644 --- a/config/osv/ubuntu/ubuntu24/config.yml +++ b/config/osv/ubuntu/ubuntu24/config.yml @@ -7,3 +7,9 @@ x86_64: pkgType: deb # Package management system chrootenvConfigFile: chrootenvconfigs/chrootenv_x86_64.yml # Path to chrootenv config releaseVersion: "24.04" # Distribution release version +aarch64: + dist: ubuntu24 # Distribution identifier + arch: aarch64 # Target architecture + pkgType: deb # Package management system + chrootenvConfigFile: chrootenvconfigs/chrootenv_aarch64.yml # Path to chrootenv config + releaseVersion: "24.04" # Distribution release version diff --git a/config/osv/ubuntu/ubuntu24/imageconfigs/defaultconfigs/default-raw-aarch64.yml b/config/osv/ubuntu/ubuntu24/imageconfigs/defaultconfigs/default-raw-aarch64.yml new file mode 100644 index 00000000..38ae8275 --- /dev/null +++ b/config/osv/ubuntu/ubuntu24/imageconfigs/defaultconfigs/default-raw-aarch64.yml @@ -0,0 +1,74 @@ +image: + name: minimal-os-image-ubuntu + version: "24.04" + +target: + os: ubuntu # Target OS name + dist: ubuntu24 # Target OS distribution + arch: aarch64 # Target OS architecture + imageType: raw # Image type, valid value: [raw, iso]. + +disk: + name: Default_Raw # 1:1 mapping to the systemConfigs name + artifacts: + - + type: raw # image file format + compression: gz # image compression format (optional) + size: 6GiB # Increased to accommodate larger rootfs partition + partitionTableType: gpt # Partition table type, valid value: [gpt, mbr] + partitions: # Required for raw, optional for ISO, not needed for rootfs. + - id: boot + type: esp + flags: + - esp + - boot + start: 1MiB + end: 513MiB + fsType: fat32 + mountPoint: /boot/efi + mountOptions: umask=0077 + + - id: rootfs + type: linux-root-arm64 + start: 513MiB + end: 4557MiB # 513MiB + 4044MiB (4238528512 bytes) = 4557MiB + fsType: ext4 + mountPoint: / + mountOptions: defaults, ro + + - id: roothashmap + type: linux + start: 4557MiB + end: 5057MiB # 4557MiB + 500MiB = 5057MiB + fsType: ext4 + mountPoint: none + + - id: userdata + type: linux + start: 5057MiB + end: "0" # Uses remaining space until end of disk + fsType: ext4 + mountPoint: /opt + +systemConfig: + name: Default_Raw + description: Default yml configuration for raw image + + bootloader: + bootType: efi # (efi or legacy) + provider: systemd-boot # (grub for efi and legacy mode, or systemd-boot for efi mode) + + immutability: + enabled: true # default is true + + additionalFiles: + - local: ../additionalfiles/dhcp.network + final: /etc/systemd/network/dhcp.network + - local: ../additionalfiles/ubuntu-noble.list + final: /etc/apt/sources.list.d/ubuntu-noble.list + + kernel: + version: "6.14" + cmdline: "console=ttyS0,115200 console=tty0 loglevel=7" + packages: + - linux-image-generic-hwe-24.04 diff --git a/config/osv/ubuntu/ubuntu24/providerconfigs/amd64_repo.yml b/config/osv/ubuntu/ubuntu24/providerconfigs/amd64_repo.yml new file mode 100644 index 00000000..d8422a65 --- /dev/null +++ b/config/osv/ubuntu/ubuntu24/providerconfigs/amd64_repo.yml @@ -0,0 +1,38 @@ +repositories: + - name: "noble" + type: "deb" # Repository type: rpm or deb + baseURL: "http://archive.ubuntu.com/ubuntu" + pkgPrefix: "http://archive.ubuntu.com/ubuntu" + releaseFile: "http://archive.ubuntu.com/ubuntu/dists/noble/Release" + releaseSign: "http://archive.ubuntu.com/ubuntu/dists/noble/Release.gpg" + pbGPGKey: "/usr/share/keyrings/ubuntu-archive-keyring.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main restricted universe multiverse" # Repository component/section identifier + buildPath: "./builds/ubuntu24/main" # Will be replaced with temp_dir/builds/ubuntu24/main at runtime + - name: "noble-security" + type: "deb" + baseURL: "http://security.ubuntu.com/ubuntu" + pkgPrefix: "http://security.ubuntu.com/ubuntu" + releaseFile: "http://security.ubuntu.com/ubuntu/dists/noble-security/Release" + releaseSign: "http://security.ubuntu.com/ubuntu/dists/noble-security/Release.gpg" + pbGPGKey: "/usr/share/keyrings/ubuntu-archive-keyring.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main restricted universe multiverse" + buildPath: "./builds/ubuntu24/security" # Will be replaced with temp_dir/builds/ubuntu24/security at runtime + - name: "noble-updates" + type: "deb" + baseURL: "http://archive.ubuntu.com/ubuntu" + pkgPrefix: "http://archive.ubuntu.com/ubuntu" + releaseFile: "http://archive.ubuntu.com/ubuntu/dists/noble-updates/Release" + releaseSign: "http://archive.ubuntu.com/ubuntu/dists/noble-updates/Release.gpg" + pbGPGKey: "/usr/share/keyrings/ubuntu-archive-keyring.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main restricted universe multiverse" + buildPath: "./builds/ubuntu24/updates" # Will be replaced with temp_dir/builds/ubuntu24/updates at runtime + diff --git a/config/osv/ubuntu/ubuntu24/providerconfigs/arm64_repo.yml b/config/osv/ubuntu/ubuntu24/providerconfigs/arm64_repo.yml new file mode 100644 index 00000000..525b78b7 --- /dev/null +++ b/config/osv/ubuntu/ubuntu24/providerconfigs/arm64_repo.yml @@ -0,0 +1,37 @@ +repositories: + - name: "noble" + type: "deb" # Repository type: rpm or deb + baseURL: "http://ports.ubuntu.com/ubuntu-ports" + pkgPrefix: "http://ports.ubuntu.com/ubuntu-ports" + releaseFile: "http://ports.ubuntu.com/ubuntu-ports/dists/noble/Release" + releaseSign: "http://ports.ubuntu.com/ubuntu-ports/dists/noble/Release.gpg" + pbGPGKey: "/usr/share/keyrings/ubuntu-archive-keyring.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main restricted universe multiverse" # Repository component/section identifier + buildPath: "./builds/ubuntu24/aarch64-main" # Will be replaced with temp_dir/builds/ubuntu24/aarch64-main at runtime + - name: "noble-security" + type: "deb" + baseURL: "http://ports.ubuntu.com/ubuntu-ports" + pkgPrefix: "http://ports.ubuntu.com/ubuntu-ports" + releaseFile: "http://ports.ubuntu.com/ubuntu-ports/dists/noble-security/Release" + releaseSign: "http://ports.ubuntu.com/ubuntu-ports/dists/noble-security/Release.gpg" + pbGPGKey: "/usr/share/keyrings/ubuntu-archive-keyring.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main restricted universe multiverse" + buildPath: "./builds/ubuntu24/aarch64-security" # Will be replaced with temp_dir/builds/ubuntu24/aarch64-security at runtime + - name: "noble-updates" + type: "deb" + baseURL: "http://ports.ubuntu.com/ubuntu-ports" + pkgPrefix: "http://ports.ubuntu.com/ubuntu-ports" + releaseFile: "http://ports.ubuntu.com/ubuntu-ports/dists/noble-updates/Release" + releaseSign: "http://ports.ubuntu.com/ubuntu-ports/dists/noble-updates/Release.gpg" + pbGPGKey: "/usr/share/keyrings/ubuntu-archive-keyring.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main restricted universe multiverse" + buildPath: "./builds/ubuntu24/aarch64-updates" # Will be replaced with temp_dir/builds/ubuntu24/aarch64-updates at runtime diff --git a/config/osv/wind-river-elxr/elxr12/chrootenvconfigs/chrootenv_aarch64.yml b/config/osv/wind-river-elxr/elxr12/chrootenvconfigs/chrootenv_aarch64.yml new file mode 100644 index 00000000..f63b772e --- /dev/null +++ b/config/osv/wind-river-elxr/elxr12/chrootenvconfigs/chrootenv_aarch64.yml @@ -0,0 +1,29 @@ +essential: + - dpkg + - apt + - debianutils + - init-system-helpers + - dash + - mount + - sysvinit-utils + - gzip + - bash + - util-linux + - tar + - base-files + - base-passwd + - sed + - bsdutils + - coreutils + - findutils + - grep + - login + - perl-base + - diffutils + - libc-bin + - hostname + - ncurses-bin + - ncurses-base + +packages: + - mmdebstrap diff --git a/config/osv/wind-river-elxr/elxr12/config.yml b/config/osv/wind-river-elxr/elxr12/config.yml index 0785710a..b7c1489a 100644 --- a/config/osv/wind-river-elxr/elxr12/config.yml +++ b/config/osv/wind-river-elxr/elxr12/config.yml @@ -6,4 +6,10 @@ x86_64: arch: x86_64 # Target architecture pkgType: deb # Package management system chrootenvConfigFile: chrootenvconfigs/chrootenv_x86_64.yml # Path to chrootenv config - releaseVersion: "12.10.0.0" # Distribution release version \ No newline at end of file + releaseVersion: "12.10.0.0" # Distribution release version +aarch64: + dist: aria # Distribution identifier + arch: aarch64 # Target architecture + pkgType: deb # Package management system + chrootenvConfigFile: chrootenvconfigs/chrootenv_aarch64.yml # Path to chrootenv config + releaseVersion: "12.10.0.0" # Distribution release version diff --git a/config/osv/wind-river-elxr/elxr12/imageconfigs/defaultconfigs/default-raw-aarch64.yml b/config/osv/wind-river-elxr/elxr12/imageconfigs/defaultconfigs/default-raw-aarch64.yml new file mode 100644 index 00000000..103f4449 --- /dev/null +++ b/config/osv/wind-river-elxr/elxr12/imageconfigs/defaultconfigs/default-raw-aarch64.yml @@ -0,0 +1,95 @@ +image: + name: minimal-os-image-elxr + version: "12.0.0" + +target: + os: wind-river-elxr # Target OS name + dist: elxr12 # Target OS distribution + arch: aarch64 # Target OS architecture + imageType: raw # Image type, valid value: [raw, iso]. + +disk: + name: Default_Raw # 1:1 mapping to the systemConfigs name + artifacts: + - + type: raw # image file format + compression: gz # image compression format (optional) + size: 4GiB # 4G, 4GB, 4096 MiB also valid. (Required for raw) + partitionTableType: gpt # Partition table type, valid value: [gpt, mbr] + partitions: # Required for raw, optional for ISO, not needed for rootfs. + - id: boot + type: esp + flags: + - esp + - boot + start: 1MiB + end: 513MiB + fsType: fat32 + mountPoint: /boot/efi + mountOptions: umask=0077 + + - id: rootfs + type: linux-root-arm64 + start: 513MiB + end: 2561MiB # 513MiB + 2GiB (2048MiB) = 2561MiB + fsType: ext4 + mountPoint: / + mountOptions: defaults, ro + + - id: roothashmap + type: linux + start: 2561MiB + end: 3061MiB # 2561MiB + 500MiB = 3061MiB + fsType: ext4 + mountPoint: none + + - id: userdata + type: linux + start: 3061MiB + end: "0" # 2561MiB + 2GiB (2048MiB) = 4609MiB + fsType: ext4 + mountPoint: /opt + +systemConfig: + name: Default_Raw + description: Default yml configuration for raw image + + bootloader: + bootType: efi # (efi or legacy) + provider: systemd-boot # (grub for efi and legacy mode, or systemd-boot for efi mode) + + immutability: + enabled: true # default is true + + packages: + - ca-certificates + - vim + - sudo + - net-tools + - openssh-client + - openssh-server + - procps + - less + - dbus + - policykit-1 + - curl + - wget + - uefi-ext4 + - systemd-resolved + - cloud-guest-utils + - dracut + - systemd-boot + - elxr-archive-keyring + + additionalFiles: + - local: ../additionalfiles/dhcp.network + final: /etc/systemd/network/dhcp.network + - local: ../additionalfiles/elxr-aria.list + final: /etc/apt/sources.list.d/elxr-aria.list + + kernel: + name: kernel + cmdline: "quiet splash console=ttyS0,115200 console=tty0 loglevel=7" + uki: true + packages: + - linux-image-cloud-arm64 diff --git a/config/osv/wind-river-elxr/elxr12/providerconfigs/amd64_repo.yml b/config/osv/wind-river-elxr/elxr12/providerconfigs/amd64_repo.yml new file mode 100644 index 00000000..35de9fbb --- /dev/null +++ b/config/osv/wind-river-elxr/elxr12/providerconfigs/amd64_repo.yml @@ -0,0 +1,13 @@ +repositories: + - name: "aria" + type: "deb" # Repository type: rpm or deb + baseURL: "https://mirror.elxr.dev/elxr" + pkgPrefix: "https://mirror.elxr.dev/elxr/" + releaseFile: "https://mirror.elxr.dev/elxr/dists/aria/Release" + releaseSign: "https://mirror.elxr.dev/elxr/dists/aria/Release.gpg" + pbGPGKey: "https://mirror.elxr.dev/elxr/public.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main non-free-firmware" # Repository component/section identifier + buildPath: "./builds/elxr12/main" # Will be replaced with temp_dir/builds/elxr12 at runtime diff --git a/config/osv/wind-river-elxr/elxr12/providerconfigs/arm64_repo.yml b/config/osv/wind-river-elxr/elxr12/providerconfigs/arm64_repo.yml new file mode 100644 index 00000000..35de9fbb --- /dev/null +++ b/config/osv/wind-river-elxr/elxr12/providerconfigs/arm64_repo.yml @@ -0,0 +1,13 @@ +repositories: + - name: "aria" + type: "deb" # Repository type: rpm or deb + baseURL: "https://mirror.elxr.dev/elxr" + pkgPrefix: "https://mirror.elxr.dev/elxr/" + releaseFile: "https://mirror.elxr.dev/elxr/dists/aria/Release" + releaseSign: "https://mirror.elxr.dev/elxr/dists/aria/Release.gpg" + pbGPGKey: "https://mirror.elxr.dev/elxr/public.gpg" + gpgCheck: true + repoGPGCheck: true + enabled: true + component: "main non-free-firmware" # Repository component/section identifier + buildPath: "./builds/elxr12/main" # Will be replaced with temp_dir/builds/elxr12 at runtime diff --git a/image-templates/azl3-aarch64-edge-raw.yml b/image-templates/azl3-aarch64-edge-raw.yml new file mode 100644 index 00000000..9f35dfab --- /dev/null +++ b/image-templates/azl3-aarch64-edge-raw.yml @@ -0,0 +1,82 @@ +image: + name: azl3-aarch64-edge + version: "1.0.0" + +target: + os: azure-linux # Target OS name + dist: azl3 # Target OS distribution + arch: aarch64 # Target OS architecture + imageType: raw # Image type, valid value: [raw, iso]. + +# Disk configuration can be omitted to use defaults from default template +# If specified, it will override the default disk configuration completely + +# Additional package repositories for this image +# Sample list of additional package repositories; replace codename, url, and pkey values as needed +packageRepositories: + - codename: "company-internal" + url: "" + pkey: "" # Uncomment and replace in real config + component: "main" + + - codename: "dev-tools" + url: "" + pkey: "" # Uncomment and replace in real config + + - codename: "intel-openvino" + url: "" + pkey: "" # Uncomment and replace in real config + component: "restricted" + +# System configuration +systemConfig: + name: edge + description: Default yml configuration for edge image + + immutability: + enabled: false + # To enable Secure Boot, provide the actual file paths for your environment below and uncomment the relevant lines. + # secureBootDBKey: "" + # secureBootDBCrt: "" + # secureBootDBCer: "" + + # User Configuration + users: + # Example: Pre-hashed password (production approach) + - name: admin + password: "" # leave empty in sample + # password: "" # Uncomment and replace in real config + groups: [""] # e.g., "wheel" for admin, "users" for non-admin + + # Example: Plain text with algorithm (development/testing only) + - name: testuser + password: "" # Do not commit real plaintext passwords + # password: "" + hash_algo: "sha512" # or org-approved algorithm + + # Example: Plain text with different algorithm (development/testing only) + - name: secureuser + password: "" # Do not commit real plaintext passwords + # password: "" + hash_algo: "bcrypt" # specify cost in production if supported + # sudo: true # Uncomment to grant admin rights + + # Package Configuration + packages: + # Additional packages beyond the base system + - cloud-init + - rsyslog + + # Kernel Configuration + kernel: + #version: "6.12" + cmdline: "console=ttyS0,115200 console=tty0 loglevel=7" + packages: + - kernel-64k-6.6.57.1-6.azl3.aarch64.rpm + # - kernel-modules-azure + + # Everything else uses defaults from the default template: + # Network: Uses DHCP on first interface + # Security: Enables distro-appropriate security (SELinux for AzureLinux) + # Services: Enables SSH, disables unnecessary services + # Users: Creates default admin user with sudo access diff --git a/image-templates/azl3-x86_64-edge-raw.yml b/image-templates/azl3-x86_64-edge-raw.yml index 14044c59..eb7f28a3 100644 --- a/image-templates/azl3-x86_64-edge-raw.yml +++ b/image-templates/azl3-x86_64-edge-raw.yml @@ -51,7 +51,7 @@ systemConfig: description: Default yml configuration for edge image immutability: - enabled: true + enabled: false # To enable Secure Boot, provide the actual file paths for your environment below and uncomment the relevant lines. # secureBootDBKey: "" # secureBootDBCrt: "" diff --git a/image-templates/elxr12-aarch64-minimal-raw.yml b/image-templates/elxr12-aarch64-minimal-raw.yml new file mode 100644 index 00000000..fab73d0b --- /dev/null +++ b/image-templates/elxr12-aarch64-minimal-raw.yml @@ -0,0 +1,45 @@ +image: + name: elxr12-aarch64-minimal + version: "1.0.0" + +target: + os: wind-river-elxr # Target OS name + dist: elxr12 # Target OS distribution + arch: aarch64 # Target OS architecture + imageType: raw # Image type, valid value: [raw, iso]. + +disk: + name: Minimal_Raw # 1:1 mapping to the systemConfigs name + artifacts: + - + type: raw # image file format, valid value [raw, vhd, vhdx, qcow2, vmdk, vdi] + compression: gz # image compression format (optional) + - type: vhdx + size: 4GiB # 4G, 4GB, 4096 MiB also valid. (Required for raw) + partitionTableType: gpt # Partition table type, valid value: [gpt, mbr] + partitions: # Required for raw, optional for ISO, not needed for rootfs. + - id: boot + type: esp + flags: + - esp + - boot + start: 1MiB + end: 513MiB + fsType: fat32 + mountPoint: /boot/efi + mountOptions: umask=0077 + + - id: rootfs + type: linux-root-arm64 + start: 513MiB + end: "0" + fsType: ext4 + mountPoint: / + mountOptions: defaults + +systemConfig: + name: Minimal_Raw + description: Default yml configuration for raw image + + immutability: + enabled: false # default is true, overridden to false here diff --git a/image-templates/ubuntu24-aarch64-minimal-raw.yml b/image-templates/ubuntu24-aarch64-minimal-raw.yml new file mode 100644 index 00000000..e4581678 --- /dev/null +++ b/image-templates/ubuntu24-aarch64-minimal-raw.yml @@ -0,0 +1,87 @@ +image: + name: minimal-os-image-ubuntu + version: "24.04" + +target: + os: ubuntu + dist: ubuntu24 + arch: aarch64 + imageType: raw + +# Sample list of additional package repositories; replace codename, url, and pkey values as needed +packageRepositories: + - codename: "company-internal" + url: "" + pkey: "" # Uncomment and replace in real config + component: "main" + + - codename: "dev-tools" + url: "" + pkey: "" # Uncomment and replace in real config + + - codename: "intel-openvino" + url: "" + pkey: "" # Uncomment and replace in real config + component: "restricted" + +disk: + name: Minimal_ubuntu_arm_Raw # 1:1 mapping to the systemConfigs name + artifacts: + - + type: raw # image file format, valid value [raw, vhd, vhdx, qcow2, vmdk, vdi] + compression: gz # image compression format (optional) + size: 4GiB # 4G, 4GB, 4096 MiB also valid. (Required for raw) + partitionTableType: gpt # Partition table type, valid value: [gpt, mbr] + partitions: # Required for raw, optional for ISO, not needed for rootfs. + - id: boot + type: esp + flags: + - esp + - boot + start: 1MiB + end: 100MiB + fsType: fat32 + mountPoint: /efi + mountOptions: umask=0077 + + - id: rootfs + type: linux-root-amd64 + start: 1025MiB + end: 3583MiB + fsType: ext4 + mountPoint: / + mountOptions: defaults + + - id: extendedboot + type: xbootldr + start: 101MiB + end: 1024MiB + fsType: vfat + mountPoint: /boot + mountOptions: defaults + +systemConfig: + name: minimal + description: Minimal ubuntu image + + immutability: + enabled: false # default is true + # To enable Secure Boot, provide the actual file paths for your environment below and uncomment the relevant lines. + # secureBootDBKey: "" + # secureBootDBCrt: "" + # secureBootDBCer: "" + + packages: + - ubuntu-minimal + - systemd-boot + - dracut-core + - systemd + - cryptsetup-bin + - openssh-server + - systemd-resolved + + kernel: + version: "6.14" + cmdline: "console=ttyS0,115200 console=tty0 loglevel=7" + packages: + - linux-image-generic-hwe-24.04 diff --git a/internal/config/config.go b/internal/config/config.go index 5c09f109..9930311e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -612,7 +612,7 @@ func (t *ImageTemplate) GetPackageRepositories() []PackageRepository { // LoadProviderRepoConfig loads provider repository configuration from YAML file // Returns a slice of ProviderRepoConfig to support multiple repositories -func LoadProviderRepoConfig(targetOS, targetDist string) ([]ProviderRepoConfig, error) { +func LoadProviderRepoConfig(targetOS, targetDist string, arch string) ([]ProviderRepoConfig, error) { // Get the target OS config directory targetOsConfigDir, err := GetTargetOsConfigDir(targetOS, targetDist) if err != nil { @@ -620,7 +620,7 @@ func LoadProviderRepoConfig(targetOS, targetDist string) ([]ProviderRepoConfig, } // Construct path to repo.yml - repoConfigPath := filepath.Join(targetOsConfigDir, "providerconfigs", "repo.yml") + repoConfigPath := filepath.Join(targetOsConfigDir, "providerconfigs", arch+"_repo.yml") // Read the YAML file yamlData, err := security.SafeReadFile(repoConfigPath, security.RejectSymlinks) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 4f452f1d..ebadc2bf 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -3605,7 +3605,7 @@ func TestGetKernelPackages(t *testing.T) { func TestLoadProviderRepoConfig(t *testing.T) { // Test with invalid parameters - this will fail in test environment // but we test that the function handles the error gracefully - _, err := LoadProviderRepoConfig("nonexistent-os", "nonexistent-dist") + _, err := LoadProviderRepoConfig("nonexistent-os", "nonexistent-dist", "amd64") if err == nil { t.Log("Unexpected success - config found for nonexistent OS/dist") } else { @@ -3617,7 +3617,7 @@ func TestLoadProviderRepoConfig(t *testing.T) { } // Test with empty parameters - _, err = LoadProviderRepoConfig("", "") + _, err = LoadProviderRepoConfig("", "", "") if err == nil { t.Error("Expected error with empty parameters") } else { @@ -3635,7 +3635,7 @@ func TestLoadProviderRepoConfig(t *testing.T) { } for _, tc := range testCases { - _, err := LoadProviderRepoConfig(tc.os, tc.dist) + _, err := LoadProviderRepoConfig(tc.os, tc.dist, "amd64") if err == nil { t.Logf("Unexpected success for %s/%s in test environment", tc.os, tc.dist) } else { @@ -3776,3 +3776,329 @@ func TestUnifiedRepoConfig(t *testing.T) { }) } } + +// TestGetAdditionalFileInfo tests the GetAdditionalFileInfo method +func TestGetAdditionalFileInfo(t *testing.T) { + // Create temporary test files + tmpDir := t.TempDir() + validFile := filepath.Join(tmpDir, "valid.txt") + if err := os.WriteFile(validFile, []byte("test"), 0644); err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + tests := []struct { + name string + template *ImageTemplate + expectedCount int + description string + }{ + { + name: "Empty additional files", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + AdditionalFiles: []AdditionalFileInfo{}, + }, + }, + expectedCount: 0, + description: "Should return empty list when no additional files", + }, + { + name: "Valid absolute path", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + AdditionalFiles: []AdditionalFileInfo{ + { + Local: validFile, + Final: "/etc/config.txt", + }, + }, + }, + }, + expectedCount: 1, + description: "Should include file with valid absolute path", + }, + { + name: "Empty local path - should be filtered", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + AdditionalFiles: []AdditionalFileInfo{ + { + Local: "", + Final: "/etc/config.txt", + }, + }, + }, + }, + expectedCount: 0, + description: "Should filter out entries with empty local path", + }, + { + name: "Empty final path - should be filtered", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + AdditionalFiles: []AdditionalFileInfo{ + { + Local: validFile, + Final: "", + }, + }, + }, + }, + expectedCount: 0, + description: "Should filter out entries with empty final path", + }, + { + name: "Mixed valid and invalid entries", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + AdditionalFiles: []AdditionalFileInfo{ + {Local: validFile, Final: "/etc/valid1.txt"}, + {Local: "", Final: "/etc/empty.txt"}, + {Local: validFile, Final: ""}, + {Local: validFile, Final: "/etc/valid2.txt"}, + }, + }, + }, + expectedCount: 2, + description: "Should filter out invalid entries and keep valid ones", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.template.GetAdditionalFileInfo() + if len(result) != tt.expectedCount { + t.Errorf("%s: expected %d files, got %d", tt.description, tt.expectedCount, len(result)) + } + }) + } +} + +// TestWasProvided tests the WasProvided method for ImmutabilityConfig +func TestWasProvided(t *testing.T) { + tests := []struct { + name string + config *ImmutabilityConfig + expected bool + }{ + { + name: "Was provided - true", + config: &ImmutabilityConfig{ + wasProvided: true, + }, + expected: true, + }, + { + name: "Was not provided - false", + config: &ImmutabilityConfig{ + wasProvided: false, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.config.WasProvided() + if result != tt.expected { + t.Errorf("WasProvided() = %v, want %v", result, tt.expected) + } + }) + } +} + +// TestGetConfigPaths tests that GetConfigPaths returns expected config locations +func TestGetConfigPaths(t *testing.T) { + paths := GetConfigPaths() + + if len(paths) == 0 { + t.Error("GetConfigPaths should return at least one path") + } + + // Verify that current directory paths are included + expectedPaths := []string{ + "os-image-composer.yml", + ".os-image-composer.yml", + "os-image-composer.yaml", + ".os-image-composer.yaml", + } + + for _, expected := range expectedPaths { + found := false + for _, path := range paths { + if path == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected path %s not found in config paths", expected) + } + } + + // Verify system paths are included + systemPaths := []string{ + "/etc/os-image-composer/config.yml", + "/etc/os-image-composer/config.yaml", + } + + for _, sysPath := range systemPaths { + found := false + for _, path := range paths { + if path == sysPath { + found = true + break + } + } + if !found { + t.Errorf("Expected system path %s not found in config paths", sysPath) + } + } +} + +// TestFindConfigFile tests the FindConfigFile function +func TestFindConfigFile(t *testing.T) { + // Create a temporary config file in current directory + tmpDir := t.TempDir() + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + err := os.Chdir(tmpDir) + if err != nil { + t.Fatalf("Failed to change to temp dir: %v", err) + } + + // Test when no config file exists + result := FindConfigFile() + if result != "" { + t.Logf("Found unexpected config file: %s", result) + } + + // Create a config file + configFile := "os-image-composer.yml" + if err := os.WriteFile(configFile, []byte("workers: 4\n"), 0644); err != nil { + t.Fatalf("Failed to create test config: %v", err) + } + + // Test when config file exists + result = FindConfigFile() + if result == "" { + t.Error("FindConfigFile should find the created config file") + } +} + +// TestWorkers tests the Workers convenience function +func TestWorkers(t *testing.T) { + // This function depends on Global() which should return valid config + workers := Workers() + if workers < 0 { + t.Errorf("Workers should return non-negative value, got %d", workers) + } +} + +// TestVerificationWorkers tests the VerificationWorkers convenience function +func TestVerificationWorkers(t *testing.T) { + workers := VerificationWorkers() + if workers < 0 { + t.Errorf("VerificationWorkers should return non-negative value, got %d", workers) + } + if workers > 4 { + t.Errorf("VerificationWorkers should be capped at 4, got %d", workers) + } +} + +// TestConfigDir tests the ConfigDir function +func TestConfigDir(t *testing.T) { + dir, err := ConfigDir() + if err != nil { + t.Logf("ConfigDir returned error (may be expected in test): %v", err) + return + } + if dir == "" { + t.Error("ConfigDir should return non-empty path") + } + if !filepath.IsAbs(dir) { + t.Errorf("ConfigDir should return absolute path, got: %s", dir) + } +} + +// TestCacheDir tests the CacheDir function +func TestCacheDir(t *testing.T) { + dir, err := CacheDir() + if err != nil { + t.Logf("CacheDir returned error (may be expected in test): %v", err) + return + } + if dir == "" { + t.Error("CacheDir should return non-empty path") + } + if !filepath.IsAbs(dir) { + t.Errorf("CacheDir should return absolute path, got: %s", dir) + } +} + +// TestWorkDir tests the WorkDir function +func TestWorkDir(t *testing.T) { + dir, err := WorkDir() + if err != nil { + t.Logf("WorkDir returned error (may be expected in test): %v", err) + return + } + if dir == "" { + t.Error("WorkDir should return non-empty path") + } + if !filepath.IsAbs(dir) { + t.Errorf("WorkDir should return absolute path, got: %s", dir) + } +} + +// TestTempDir tests the TempDir function +func TestTempDir(t *testing.T) { + dir := TempDir() + if dir == "" { + t.Error("TempDir should return non-empty path") + } +} + +// TestLogLevel tests the LogLevel function +func TestLogLevel(t *testing.T) { + level := LogLevel() + // Log level can be any string, just verify it doesn't panic + t.Logf("Log level: %s", level) +} + +// TestEnsureCacheDir tests the EnsureCacheDir function +func TestEnsureCacheDir(t *testing.T) { + err := EnsureCacheDir() + if err != nil { + t.Logf("EnsureCacheDir returned error (may be expected in test): %v", err) + } +} + +// TestEnsureWorkDir tests the EnsureWorkDir function +func TestEnsureWorkDir(t *testing.T) { + err := EnsureWorkDir() + if err != nil { + t.Logf("EnsureWorkDir returned error (may be expected in test): %v", err) + } +} + +// TestEnsureTempDir tests the EnsureTempDir function +func TestEnsureTempDir(t *testing.T) { + dir, err := EnsureTempDir("test-subdir") + if err != nil { + t.Fatalf("EnsureTempDir failed: %v", err) + } + if dir == "" { + t.Error("EnsureTempDir should return non-empty path") + } + + // Verify the directory was created + if _, err := os.Stat(dir); os.IsNotExist(err) { + t.Error("EnsureTempDir should create the directory") + } +} diff --git a/internal/config/merge_test.go b/internal/config/merge_test.go index c5dc2486..78898051 100644 --- a/internal/config/merge_test.go +++ b/internal/config/merge_test.go @@ -2,6 +2,7 @@ package config import ( "os" + "path/filepath" "testing" ) @@ -610,3 +611,224 @@ systemConfig: t.Errorf("expected image name 'test-merge', got '%s'", result.Image.Name) } } + +// TestValidateAndFixImmutabilityConfig tests the validateAndFixImmutabilityConfig function +func TestValidateAndFixImmutabilityConfig(t *testing.T) { + tests := []struct { + name string + template *ImageTemplate + expectImmutability bool + description string + }{ + { + name: "Immutability disabled - no validation", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + Immutability: ImmutabilityConfig{ + Enabled: false, + }, + }, + Disk: DiskConfig{ + Partitions: []PartitionInfo{}, + }, + }, + expectImmutability: false, + description: "Should remain disabled when already disabled", + }, + { + name: "Immutability enabled with roothashmap partition", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + Immutability: ImmutabilityConfig{ + Enabled: true, + }, + }, + Disk: DiskConfig{ + Partitions: []PartitionInfo{ + {ID: "root", MountPoint: "/", Type: "linux"}, + {ID: "roothashmap", MountPoint: "none", Type: "linux"}, + }, + }, + }, + expectImmutability: true, + description: "Should keep immutability enabled with roothashmap partition", + }, + { + name: "Immutability enabled with hash partition", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + Immutability: ImmutabilityConfig{ + Enabled: true, + }, + }, + Disk: DiskConfig{ + Partitions: []PartitionInfo{ + {ID: "root", MountPoint: "/", Type: "linux"}, + {ID: "hash", MountPoint: "none", Type: "linux"}, + }, + }, + }, + expectImmutability: true, + description: "Should keep immutability enabled with hash partition", + }, + { + name: "Immutability enabled with mountPoint none", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + Immutability: ImmutabilityConfig{ + Enabled: true, + }, + }, + Disk: DiskConfig{ + Partitions: []PartitionInfo{ + {ID: "root", MountPoint: "/", Type: "linux"}, + {ID: "custom", MountPoint: "none", Type: "linux"}, + }, + }, + }, + expectImmutability: true, + description: "Should keep immutability enabled with partition having mountPoint=none", + }, + { + name: "Immutability enabled without hash partition - should disable", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + Immutability: ImmutabilityConfig{ + Enabled: true, + }, + }, + Disk: DiskConfig{ + Partitions: []PartitionInfo{ + {ID: "root", MountPoint: "/", Type: "linux"}, + {ID: "boot", MountPoint: "/boot", Type: "linux"}, + }, + }, + }, + expectImmutability: false, + description: "Should auto-disable immutability when no hash partition exists", + }, + { + name: "Immutability enabled with empty partitions - should disable", + template: &ImageTemplate{ + SystemConfig: SystemConfig{ + Immutability: ImmutabilityConfig{ + Enabled: true, + }, + }, + Disk: DiskConfig{ + Partitions: []PartitionInfo{}, + }, + }, + expectImmutability: false, + description: "Should auto-disable immutability with no partitions", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + validateAndFixImmutabilityConfig(tt.template) + if tt.template.SystemConfig.Immutability.Enabled != tt.expectImmutability { + t.Errorf("%s: expected immutability=%v, got=%v", + tt.description, tt.expectImmutability, tt.template.SystemConfig.Immutability.Enabled) + } + }) + } +} + +// TestLoadProviderRepoConfigWithValidData tests LoadProviderRepoConfig with testdata +func TestLoadProviderRepoConfigWithValidData(t *testing.T) { + // Create test directory structure + tmpDir := t.TempDir() + osConfigDir := filepath.Join(tmpDir, "osv", "test-os", "test-dist") + providerDir := filepath.Join(osConfigDir, "providerconfigs") + + if err := os.MkdirAll(providerDir, 0755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + + // Test case 1: Multiple repositories format + multiRepoYAML := `repositories: + - name: "base" + type: "rpm" + url: "https://example.com/base" + gpgkey: "https://example.com/key.gpg" + - name: "updates" + type: "rpm" + url: "https://example.com/updates" + gpgkey: "https://example.com/key.gpg" +` + repoFile := filepath.Join(providerDir, "amd64_repo.yml") + if err := os.WriteFile(repoFile, []byte(multiRepoYAML), 0644); err != nil { + t.Fatalf("Failed to write test repo file: %v", err) + } + + // Save original function and restore after test + originalGetTargetOsConfigDir := func(targetOS, targetDist string) (string, error) { + return osConfigDir, nil + } + + t.Run("Multiple repositories format", func(t *testing.T) { + // We can't easily override GetTargetOsConfigDir, so we test error case + _, err := LoadProviderRepoConfig("test-os", "test-dist", "amd64") + // In test environment this will fail because GetTargetOsConfigDir + // uses real paths, but we verify it doesn't panic + if err == nil { + t.Log("Successfully loaded config (unexpected in test env)") + } else { + t.Logf("Expected error in test environment: %v", err) + } + }) + + // Test case 2: Single repository format (backward compatibility) + singleRepoYAML := `name: "base" +type: "rpm" +url: "https://example.com/base" +gpgkey: "https://example.com/key.gpg" +` + repoFile2 := filepath.Join(providerDir, "x86_64_repo.yml") + if err := os.WriteFile(repoFile2, []byte(singleRepoYAML), 0644); err != nil { + t.Fatalf("Failed to write test repo file: %v", err) + } + + t.Run("Single repository format", func(t *testing.T) { + _, err := LoadProviderRepoConfig("test-os", "test-dist", "x86_64") + if err == nil { + t.Log("Successfully loaded config (unexpected in test env)") + } else { + t.Logf("Expected error in test environment: %v", err) + } + }) + + // Test case 3: Invalid YAML + invalidYAML := `this is not valid: yaml: content: [[[` + repoFile3 := filepath.Join(providerDir, "arm64_repo.yml") + if err := os.WriteFile(repoFile3, []byte(invalidYAML), 0644); err != nil { + t.Fatalf("Failed to write test repo file: %v", err) + } + + t.Run("Invalid YAML format", func(t *testing.T) { + _, err := LoadProviderRepoConfig("test-os", "test-dist", "arm64") + if err == nil { + t.Error("Expected error for invalid YAML") + } + }) + + _ = originalGetTargetOsConfigDir // Prevent unused variable error +} + +// TestLoadProviderRepoConfigArchVariants tests different architecture naming +func TestLoadProviderRepoConfigArchVariants(t *testing.T) { + archVariants := []string{"amd64", "x86_64", "arm64", "aarch64"} + + for _, arch := range archVariants { + t.Run("Arch_"+arch, func(t *testing.T) { + _, err := LoadProviderRepoConfig("test-os", "test-dist", arch) + // We expect this to fail in test environment, but it shouldn't panic + if err == nil { + t.Logf("Unexpected success for arch %s", arch) + } else { + t.Logf("Expected error for arch %s: %v", arch, err) + } + }) + } +} diff --git a/internal/image/imagedisc/imagedisc.go b/internal/image/imagedisc/imagedisc.go index 1dd87d4d..c80b8ed4 100644 --- a/internal/image/imagedisc/imagedisc.go +++ b/internal/image/imagedisc/imagedisc.go @@ -68,6 +68,7 @@ var partitionTypeNameToGUID = map[string]string{ "esp": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", "xbootldr": "bc13c2ff-59e6-4262-a352-b275fd6f7172", "linux-root-amd64": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "linux-root-arm64": "b921b045-1df0-41c3-af44-4c6f280d3fae", "linux-swap": "0657fd6d-a4ab-43c4-84e5-0933c84b4f4f", "linux-home": "933ac7e1-2eb4-4f13-b844-0e14e2aef915", "linux-srv": "3b8f8425-20e0-4f3b-907f-1a25a76f98e8", diff --git a/internal/image/imageos/imageos.go b/internal/image/imageos/imageos.go index e250e9fa..b500c130 100644 --- a/internal/image/imageos/imageos.go +++ b/internal/image/imageos/imageos.go @@ -202,7 +202,7 @@ func (imageOs *ImageOs) InstallImageOs(diskPathIdMap map[string]string) (version return } - log.Infof("Configuring UKI...") + log.Infof("Configuring UKI... ") if err = buildImageUKI(imageOs.installRoot, imageOs.template); err != nil { err = fmt.Errorf("failed to configure UKI: %w", err) return @@ -567,12 +567,15 @@ func (imageOs *ImageOs) installImagePkgs(installRoot string, template *config.Im } output, err := shell.ExecCmdWithStream(installCmd, true, installRoot, envVars) + // Always log the full output for debugging + log.Infof("apt-get install output for %s:\n%s", pkg, output) if err != nil { if strings.Contains(output, "Failed to write 'LoaderSystemToken' EFI variable") || strings.Contains(output, "Failed to create EFI Boot variable entry") { log.Debugf("Expected error: EFI variables cannot be accessed in chroot environment.") } else { log.Errorf("Failed to install package %s: %v", pkg, err) + log.Errorf("Full apt-get output:\n%s", output) return fmt.Errorf("failed to install package %s: %w", pkg, err) } } @@ -900,14 +903,68 @@ func buildImageUKI(installRoot string, template *config.ImageTemplate) error { log.Debugf("UKI Path:", outputPath) cmdlineFile := filepath.Join("/boot", "cmdline.conf") + + // do checks for file paths + if _, err := os.Stat(installRoot); err == nil { + log.Infof("Install Root Exists at %s", installRoot) + } else { + log.Errorf("Install Root does not exist at %s", installRoot) + } + if _, err := os.Stat(kernelPath); err == nil { + log.Infof("kernelPath Exists at %s", kernelPath) + } else { + log.Errorf("Install Root does not exist at %s", installRoot) + } + + if _, err := os.Stat(kernelPath); err == nil { + log.Infof("kernelPath Exists at %s", kernelPath) + } else { + log.Errorf("kernelPath does not exist at %s", kernelPath) + } + + if _, err := os.Stat(initrdPath); err == nil { + log.Infof("initrdPath Exists at %s", initrdPath) + } else { + log.Errorf("initrdPath does not exist at %s", initrdPath) + } + if _, err := os.Stat(cmdlineFile); err == nil { + log.Infof("cmdlineFile Exists at %s", cmdlineFile) + return nil + } else { + log.Errorf("cmdlineFile does not exist at %s", cmdlineFile) + } + if _, err := os.Stat(outputPath); err == nil { + log.Infof("outputPath Exists at %s", outputPath) + return nil + } else { + log.Errorf("outputPath does not exist at %s", outputPath) + } + if err := buildUKI(installRoot, kernelPath, initrdPath, cmdlineFile, outputPath, template); err != nil { return fmt.Errorf("failed to build UKI: %w", err) } log.Debugf("UKI created successfully on:", outputPath) + log.Infof("Target architecture is %v ", template.Target.Arch) + + srcBootloader := "" + dstBootloader := "" + + switch template.Target.Arch { + case "x86_64": + log.Infof("Target architecture is x86_64, proceeding with bootloader copy") + // 3. Copy systemd-bootx64.efi to ESP/EFI/BOOT/BOOTX64.EFI + srcBootloader = filepath.Join("usr", "lib", "systemd", "boot", "efi", "systemd-bootx64.efi") + dstBootloader = filepath.Join(espDir, "EFI", "BOOT", "BOOTX64.EFI") + case "aarch64": + log.Infof("Target architecture is ARM64, proceeding with bootloader copy") + // 3. Copy systemd-bootx64.efi to ESP/EFI/BOOT/BOOT64.EFI + srcBootloader = filepath.Join("usr", "lib", "systemd", "boot", "efi", "systemd-bootaa64.efi") + dstBootloader = filepath.Join(espDir, "EFI", "BOOT", "BOOTAA64.EFI") + default: + log.Infof("Skipping bootloader copy for architecture: %s", template.Target.Arch) + return nil + } - // 3. Copy systemd-bootx64.efi to ESP/EFI/BOOT/BOOTX64.EFI - srcBootloader := filepath.Join("usr", "lib", "systemd", "boot", "efi", "systemd-bootx64.efi") - dstBootloader := filepath.Join(espDir, "EFI", "BOOT", "BOOTX64.EFI") if err := copyBootloader(installRoot, srcBootloader, dstBootloader); err != nil { return fmt.Errorf("failed to copy bootloader: %w", err) } @@ -1211,7 +1268,7 @@ func buildUKI(installRoot, kernelPath, initrdPath, cmdlineFile, outputPath strin envVars := []string{"TMPDIR=/tmp"} _, err = shell.ExecCmd(cmd, true, installRoot, envVars) if err != nil { - log.Errorf("Failed to build UKI with veritysetup: %v", err) + log.Errorf("Failed to build UKI with veritysetup: %v failing command: %s", err, cmd) err = fmt.Errorf("failed to build UKI with veritysetup: %w", err) } installRoot = backInstallRoot @@ -1219,9 +1276,12 @@ func buildUKI(installRoot, kernelPath, initrdPath, cmdlineFile, outputPath strin } else { _, err = shell.ExecCmd(cmd, true, installRoot, nil) if err != nil { - log.Errorf("Failed to build UKI: %v", err) + log.Errorf("non-immutable: Failed to build UKI: %v failing command %s", err, cmd) err = fmt.Errorf("failed to build UKI: %w", err) + } else { + log.Infof("non-immutable: Successfully built UKI: %v command %s", err, cmd) } + } return err } diff --git a/internal/ospackage/debutils/download.go b/internal/ospackage/debutils/download.go index 3ca3574d..3c8452a9 100644 --- a/internal/ospackage/debutils/download.go +++ b/internal/ospackage/debutils/download.go @@ -129,11 +129,13 @@ func BuildRepoConfigs(userRepoList []Repository, arch string) ([]RepoConfig, err for _, localArch := range strings.Split(archs, ",") { package_list_url, err := GetPackagesNames(baseURL, codename, localArch, componentName) if err != nil { - return nil, fmt.Errorf("getting package metadata name: %w", err) + return nil, fmt.Errorf("getting package metadata name: %w, baseURL %s codename %s localArch %s componentName %s\n", err, baseURL, codename, localArch, componentName) } if package_list_url == "" { + fmt.Printf("getting package metadata name: %v, baseURL %s codename %s localArch %s componentName %s\n", err, baseURL, codename, localArch, componentName) continue // No valid package list found for this arch/component } + fmt.Printf("SUCCESS: baseURL %s codename %s localArch %s componentName %s\n", baseURL, codename, localArch, componentName) repo := RepoConfig{ PkgList: package_list_url, ReleaseFile: fmt.Sprintf("%s/dists/%s/%s", baseURL, codename, releaseNm), diff --git a/internal/provider/azl/azl.go b/internal/provider/azl/azl.go index 5cbbc9eb..fb2d51f6 100644 --- a/internal/provider/azl/azl.go +++ b/internal/provider/azl/azl.go @@ -272,7 +272,7 @@ func (p *AzureLinux) downloadImagePkgs(template *config.ImageTemplate) error { // loadRepoConfigFromYAML loads repository configuration from centralized YAML config func loadRepoConfigFromYAML(dist, arch string) (rpmutils.RepoConfig, error) { // Load the centralized provider config - providerConfigs, err := config.LoadProviderRepoConfig(OsName, dist) + providerConfigs, err := config.LoadProviderRepoConfig(OsName, dist, arch) if err != nil { return rpmutils.RepoConfig{}, fmt.Errorf("failed to load provider repo config: %w", err) } diff --git a/internal/provider/azl/azl_test.go b/internal/provider/azl/azl_test.go index 8f5c1302..c41307c3 100644 --- a/internal/provider/azl/azl_test.go +++ b/internal/provider/azl/azl_test.go @@ -738,3 +738,318 @@ func TestAzlDownloadImagePkgs(t *testing.T) { // and doesn't handle nil chrootEnv gracefully t.Skip("downloadImagePkgs requires proper Azure Linux initialization with chrootEnv - function exists and is callable") } + +// TestAzlPreProcessWithMockEnv tests PreProcess with proper mock chrootEnv +func TestAzlPreProcessWithMockEnv(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + // PreProcess will fail at installHostDependency but we can verify the call chain + err := azl.PreProcess(template) + if err == nil { + t.Log("PreProcess succeeded unexpectedly") + } else { + // Verify the error is from the expected path + if !strings.Contains(err.Error(), "failed to") { + t.Errorf("Expected error to contain 'failed to', got: %v", err) + } + t.Logf("PreProcess failed as expected: %v", err) + } +} + +// TestAzlDownloadImagePkgsWithMockEnv tests downloadImagePkgs with mock environment +func TestAzlDownloadImagePkgsWithMockEnv(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + repoCfg: rpmutils.RepoConfig{ + Name: "Test Repo", + URL: "https://test.example.com", + Section: "test", + }, + gzHref: "repodata/test.xml.gz", + } + + template := createTestImageTemplate() + template.DotFilePath = "" + + // downloadImagePkgs will fail at package download but we can verify the call + err := azl.downloadImagePkgs(template) + if err == nil { + t.Log("downloadImagePkgs succeeded unexpectedly") + } else { + // Verify we reach the download logic + t.Logf("downloadImagePkgs failed as expected: %v", err) + } +} + +// TestAzlBuildRawImageWithMock tests buildRawImage with mock environment +func TestAzlBuildRawImageWithMock(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "raw" + + err := azl.buildRawImage(template) + if err == nil { + t.Error("Expected error with mock environment") + } else { + // Verify error is from rawmaker initialization + if !strings.Contains(err.Error(), "failed to create raw maker") && !strings.Contains(err.Error(), "failed to initialize") { + t.Logf("buildRawImage failed with: %v", err) + } + } +} + +// TestAzlBuildInitrdImageWithMock tests buildInitrdImage with mock environment +func TestAzlBuildInitrdImageWithMock(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "img" + + err := azl.buildInitrdImage(template) + if err == nil { + t.Error("Expected error with mock environment") + } else { + // Verify error is from initrdmaker + if !strings.Contains(err.Error(), "failed to create initrd maker") && !strings.Contains(err.Error(), "failed to initialize") { + t.Logf("buildInitrdImage failed with: %v", err) + } + } +} + +// TestAzlBuildIsoImageWithMock tests buildIsoImage with mock environment +func TestAzlBuildIsoImageWithMock(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "iso" + + err := azl.buildIsoImage(template) + if err == nil { + t.Error("Expected error with mock environment") + } else { + // Verify error is from isomaker + if !strings.Contains(err.Error(), "failed to create iso maker") && !strings.Contains(err.Error(), "failed to initialize") { + t.Logf("buildIsoImage failed with: %v", err) + } + } +} + +// TestAzlPostProcessSuccess tests PostProcess with mock environment +func TestAzlPostProcessSuccess(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + // Test with no input error + err := azl.PostProcess(template, nil) + if err != nil { + t.Errorf("Expected nil error when input error is nil, got: %v", err) + } + + // Test with input error - should return the same error + inputErr := fmt.Errorf("build failed") + err = azl.PostProcess(template, inputErr) + if err != inputErr { + t.Errorf("Expected PostProcess to return input error, got: %v", err) + } +} + +// TestLoadRepoConfigFromYAMLErrors tests error cases in loadRepoConfigFromYAML +func TestLoadRepoConfigFromYAMLErrors(t *testing.T) { + // Test with invalid dist/arch + _, err := loadRepoConfigFromYAML("invalid", "invalid") + if err == nil { + t.Error("Expected error for invalid dist/arch") + } else { + t.Logf("Got expected error: %v", err) + } +} + +// TestLoadRepoConfigFromYAMLAarch64 tests loadRepoConfigFromYAML with aarch64 +func TestLoadRepoConfigFromYAMLAarch64(t *testing.T) { + // Change to project root + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + // Test with aarch64 architecture + cfg, err := loadRepoConfigFromYAML("azl3", "aarch64") + if err != nil { + t.Skipf("loadRepoConfigFromYAML failed for aarch64: %v", err) + return + } + + if cfg.Name == "" { + t.Error("Expected config name to be set for aarch64") + } + + t.Logf("Successfully loaded aarch64 config: %s", cfg.Name) +} + +// TestDisplayImageArtifacts tests displayImageArtifacts function +func TestDisplayImageArtifacts(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + // Test with empty directory + displayImageArtifacts(tempDir, "TEST") + t.Log("displayImageArtifacts called successfully with empty directory") + + // Test with different image types + displayImageArtifacts(tempDir, "RAW") + displayImageArtifacts(tempDir, "ISO") + displayImageArtifacts(tempDir, "IMG") + + t.Log("displayImageArtifacts tested with multiple image types") +} + +// TestRegisterSuccess tests successful Register call +func TestRegisterSuccess(t *testing.T) { + // This test verifies the Register function path + // In a real environment with config files, this would work + + // Test that Register doesn't panic + defer func() { + if r := recover(); r != nil { + t.Errorf("Register panicked: %v", r) + } + }() + + err := Register("azure-linux", "azl3", "x86_64") + + // We expect error in test environment, but it should be controlled + if err != nil { + t.Logf("Register returned expected error in test environment: %v", err) + } else { + t.Log("Register succeeded unexpectedly - config files present") + } +} + +// TestInstallHostDependencyMapping tests the dependency mapping logic +func TestInstallHostDependencyMapping(t *testing.T) { + azl := &AzureLinux{} + + // Call installHostDependency + err := azl.installHostDependency() + + // In test environment, this may succeed or fail based on host OS + if err != nil { + // Verify error message is reasonable + if err.Error() == "" { + t.Error("Expected non-empty error message") + } + t.Logf("installHostDependency failed as may be expected: %v", err) + } else { + t.Log("installHostDependency succeeded - all dependencies present") + } +} + +// TestAzlInitWithAarch64 tests Init with aarch64 architecture +func TestAzlInitWithAarch64(t *testing.T) { + // Change to project root + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + err := azl.Init("azl3", "aarch64") + if err != nil { + t.Skipf("Init failed for aarch64: %v", err) + return + } + + if azl.repoCfg.URL == "" { + t.Error("Expected repoCfg.URL to be set for aarch64") + } + + // Verify aarch64 is in the URL + if !strings.Contains(azl.repoCfg.URL, "aarch64") { + t.Errorf("Expected URL to contain 'aarch64', got: %s", azl.repoCfg.URL) + } + + t.Logf("Successfully initialized with aarch64: %s", azl.repoCfg.URL) +} + +// TestAzlInitErrorPaths tests error paths in Init method +func TestAzlInitErrorPaths(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + // Test with invalid dist + err := azl.Init("invalid-dist", "x86_64") + if err == nil { + t.Error("Expected error for invalid dist") + } else { + t.Logf("Got expected error for invalid dist: %v", err) + } +} + +// TestBuildImageEdgeCases tests edge cases in BuildImage +func TestBuildImageEdgeCases(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + // Test with empty image name + template := createTestImageTemplate() + template.Image.Name = "" + template.Target.ImageType = "raw" + + err := azl.BuildImage(template) + if err == nil { + t.Log("BuildImage handled empty name gracefully") + } else { + t.Logf("BuildImage with empty name failed: %v", err) + } +} + +// TestPreProcessErrorPropagation tests error propagation in PreProcess +func TestPreProcessErrorPropagation(t *testing.T) { + azl := &AzureLinux{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + err := azl.PreProcess(template) + if err != nil { + // Verify error message contains context + if !strings.Contains(err.Error(), "failed to") { + t.Errorf("Expected error to contain context, got: %v", err) + } + } +} diff --git a/internal/provider/elxr/elxr.go b/internal/provider/elxr/elxr.go index d38da1ae..b1892330 100644 --- a/internal/provider/elxr/elxr.go +++ b/internal/provider/elxr/elxr.go @@ -54,8 +54,11 @@ func (p *eLxr) Name(dist, arch string) string { func (p *eLxr) Init(dist, arch string) error { // Architecture mapping if needed - if arch == "x86_64" { + switch arch { + case "x86_64": arch = "amd64" + case "aarch64": + arch = "arm64" } cfgs, err := loadRepoConfig("", arch) @@ -280,7 +283,7 @@ func loadRepoConfig(repoUrl string, arch string) ([]debutils.RepoConfig, error) var repoConfigs []debutils.RepoConfig // Load provider repo config for elxr - use correct OS name - providerConfigs, err := config.LoadProviderRepoConfig(OsName, "elxr12") // Use "wind-river-elxr" and "aria" dist + providerConfigs, err := config.LoadProviderRepoConfig(OsName, "elxr12", arch) // Use "wind-river-elxr" and "aria" dist if err != nil { return repoConfigs, fmt.Errorf("failed to load provider repo config: %w", err) } diff --git a/internal/provider/elxr/elxr_test.go b/internal/provider/elxr/elxr_test.go index b47e7226..7faeb25a 100644 --- a/internal/provider/elxr/elxr_test.go +++ b/internal/provider/elxr/elxr_test.go @@ -581,7 +581,7 @@ func TestElxrConfigurationStructure(t *testing.T) { } // Test that we can load provider config - providerConfigs, err := config.LoadProviderRepoConfig(OsName, "elxr12") + providerConfigs, err := config.LoadProviderRepoConfig(OsName, "elxr12", "amd64") if err != nil { t.Logf("Cannot load provider config in test environment: %v", err) } else { @@ -736,3 +736,516 @@ func TestElxrPostProcessErrorHandling(t *testing.T) { // This will panic due to nil chrootEnv, which we catch above _ = elxr.PostProcess(template, inputError) } + +// TestElxrPreProcessWithMockEnv tests PreProcess with proper mock chrootEnv +func TestElxrPreProcessWithMockEnv(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + repoCfgs: []debutils.RepoConfig{{ + Section: "main", + Name: "Test Repo", + PkgList: "https://example.com/Packages.gz", + PkgPrefix: "https://example.com/", + Arch: "amd64", + }}, + } + + template := createTestImageTemplate() + + err := elxr.PreProcess(template) + if err == nil { + t.Log("PreProcess succeeded unexpectedly") + } else { + // Verify the error is from the expected path + if !strings.Contains(err.Error(), "failed to") { + t.Errorf("Expected error to contain 'failed to', got: %v", err) + } + t.Logf("PreProcess failed as expected: %v", err) + } +} + +// TestElxrDownloadImagePkgsWithMockEnv tests downloadImagePkgs with mock environment +func TestElxrDownloadImagePkgsWithMockEnv(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + repoCfgs: []debutils.RepoConfig{{ + Section: "main", + Name: "Test Repo", + PkgList: "https://test.example.com/Packages.gz", + PkgPrefix: "https://test.example.com/", + Arch: "amd64", + }}, + } + + template := createTestImageTemplate() + template.DotFilePath = "" + + err := elxr.downloadImagePkgs(template) + if err == nil { + t.Log("downloadImagePkgs succeeded unexpectedly") + } else { + // Verify we reach the download logic + t.Logf("downloadImagePkgs failed as expected: %v", err) + } +} + +// TestElxrDownloadImagePkgsNoRepos tests error when no repositories configured +func TestElxrDownloadImagePkgsNoRepos(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + repoCfgs: []debutils.RepoConfig{}, // Empty repos + } + + template := createTestImageTemplate() + + err := elxr.downloadImagePkgs(template) + if err == nil { + t.Error("Expected error when no repositories configured") + } + + expectedError := "no repository configurations available" + if !strings.Contains(err.Error(), expectedError) { + t.Errorf("Expected error containing '%s', got: %v", expectedError, err) + } +} + +// TestElxrBuildRawImageWithMock tests buildRawImage with mock environment +func TestElxrBuildRawImageWithMock(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "raw" + + err := elxr.buildRawImage(template) + if err == nil { + t.Error("Expected error with mock environment") + } else { + // Verify error is from rawmaker initialization + if !strings.Contains(err.Error(), "failed to create raw maker") && !strings.Contains(err.Error(), "failed to initialize") { + t.Logf("buildRawImage failed with: %v", err) + } + } +} + +// TestElxrBuildInitrdImageWithMock tests buildInitrdImage with mock environment +func TestElxrBuildInitrdImageWithMock(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "img" + + err := elxr.buildInitrdImage(template) + if err == nil { + t.Error("Expected error with mock environment") + } else { + // Verify error is from initrdmaker + if !strings.Contains(err.Error(), "failed to create initrd maker") && !strings.Contains(err.Error(), "failed to initialize") { + t.Logf("buildInitrdImage failed with: %v", err) + } + } +} + +// TestElxrBuildIsoImageWithMock tests buildIsoImage with mock environment +func TestElxrBuildIsoImageWithMock(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "iso" + + err := elxr.buildIsoImage(template) + if err == nil { + t.Error("Expected error with mock environment") + } else { + // Verify error is from isomaker + if !strings.Contains(err.Error(), "failed to create iso maker") && !strings.Contains(err.Error(), "failed to initialize") { + t.Logf("buildIsoImage failed with: %v", err) + } + } +} + +// TestElxrPostProcessSuccess tests PostProcess with mock environment +func TestElxrPostProcessSuccess(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + // Test with no input error - PostProcess should only return cleanup errors + err := elxr.PostProcess(template, nil) + if err != nil { + // PostProcess currently doesn't propagate the input error, only cleanup errors + t.Logf("PostProcess returned error: %v", err) + } +} + +// TestElxrInstallHostDependency tests installHostDependency function +func TestElxrInstallHostDependency(t *testing.T) { + elxr := &eLxr{} + + // Call installHostDependency + err := elxr.installHostDependency() + + // In test environment, this may succeed or fail based on host OS + if err != nil { + // Verify error message is reasonable + if err.Error() == "" { + t.Error("Expected non-empty error message") + } + t.Logf("installHostDependency failed as may be expected: %v", err) + } else { + t.Log("installHostDependency succeeded - all dependencies present") + } +} + +// TestElxrInstallHostDependencyMapping tests the dependency mapping logic +func TestElxrInstallHostDependencyMapping(t *testing.T) { + // Test the expected dependencies mapping + expectedDeps := map[string]string{ + "mmdebstrap": "mmdebstrap", + "mkfs.fat": "dosfstools", + "xorriso": "xorriso", + "sbsign": "sbsigntool", + "ukify": "systemd-ukify", + "veritysetup": "cryptsetup", + } + + t.Logf("Expected host dependencies for eLxr provider: %v", expectedDeps) + + // Verify that each expected dependency has a mapping + for cmd, pkg := range expectedDeps { + if cmd == "" || pkg == "" { + t.Errorf("Empty dependency mapping: cmd='%s', pkg='%s'", cmd, pkg) + } + } +} + +// TestLoadRepoConfigArchMapping tests architecture mapping in loadRepoConfig +func TestLoadRepoConfigArchMapping(t *testing.T) { + // Change to project root + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + testCases := []struct { + arch string + expectedArch string + }{ + {"amd64", "amd64"}, + {"arm64", "arm64"}, + {"x86_64", "x86_64"}, // Not mapped in loadRepoConfig, mapped in Init + } + + for _, tc := range testCases { + t.Run(tc.arch, func(t *testing.T) { + cfgs, err := loadRepoConfig("", tc.arch) + if err != nil { + t.Logf("loadRepoConfig failed for arch %s: %v", tc.arch, err) + return + } + + if len(cfgs) == 0 { + t.Errorf("Expected at least one config for arch %s", tc.arch) + return + } + + // Note: The actual arch in config might differ based on provider config + t.Logf("loadRepoConfig succeeded for arch %s: got %d configs", tc.arch, len(cfgs)) + }) + } +} + +// TestLoadRepoConfigInvalidArch tests error handling for invalid architecture +func TestLoadRepoConfigInvalidArch(t *testing.T) { + // Change to project root + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + _, err := loadRepoConfig("", "invalid-arch") + if err == nil { + t.Error("Expected error for invalid architecture") + } else { + t.Logf("Got expected error for invalid arch: %v", err) + } +} + +// TestDisplayImageArtifacts tests displayImageArtifacts function +func TestDisplayImageArtifacts(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + // Test with empty directory + displayImageArtifacts(tempDir, "TEST") + t.Log("displayImageArtifacts called successfully with empty directory") + + // Test with different image types + displayImageArtifacts(tempDir, "RAW") + displayImageArtifacts(tempDir, "ISO") + displayImageArtifacts(tempDir, "IMG") + + t.Log("displayImageArtifacts tested with multiple image types") +} + +// TestRegisterSuccess tests successful Register call +func TestRegisterSuccess(t *testing.T) { + // Test Register function with valid parameters + + // Test that Register doesn't panic + defer func() { + if r := recover(); r != nil { + t.Errorf("Register panicked: %v", r) + } + }() + + err := Register("wind-river-elxr", "elxr12", "amd64") + + // We expect error in test environment, but it should be controlled + if err != nil { + t.Logf("Register returned expected error in test environment: %v", err) + } else { + t.Log("Register succeeded unexpectedly - config files present") + } +} + +// TestElxrInitWithAarch64 tests Init with aarch64 architecture +func TestElxrInitWithAarch64(t *testing.T) { + // Change to project root + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + elxr := &eLxr{} + + err := elxr.Init("elxr12", "aarch64") + if err != nil { + t.Logf("Init failed for aarch64: %v", err) + return + } + + if len(elxr.repoCfgs) == 0 { + t.Error("Expected repoCfgs to be populated for aarch64") + return + } + + // Verify aarch64 is mapped to arm64 + if elxr.repoCfgs[0].Arch != "arm64" { + t.Errorf("Expected arch to be mapped to arm64, got: %s", elxr.repoCfgs[0].Arch) + } + + // Verify arm64 is in the PkgList URL + if elxr.repoCfgs[0].PkgList != "" && !strings.Contains(elxr.repoCfgs[0].PkgList, "arm64") { + t.Errorf("Expected PkgList to contain 'arm64', got: %s", elxr.repoCfgs[0].PkgList) + } + + t.Logf("Successfully initialized with aarch64: %s", elxr.repoCfgs[0].PkgList) +} + +// TestElxrInitErrorPaths tests error paths in Init method +func TestElxrInitErrorPaths(t *testing.T) { + elxr := &eLxr{} + + // Test with invalid dist (no config files) + err := elxr.Init("invalid-dist", "amd64") + if err == nil { + t.Error("Expected error for invalid dist") + } else { + t.Logf("Got expected error for invalid dist: %v", err) + } +} + +// TestBuildImageEdgeCases tests edge cases in BuildImage +func TestBuildImageEdgeCases(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + } + + // Test with empty image name + template := createTestImageTemplate() + template.Image.Name = "" + template.Target.ImageType = "raw" + + err := elxr.BuildImage(template) + if err == nil { + t.Log("BuildImage handled empty name gracefully") + } else { + t.Logf("BuildImage with empty name failed: %v", err) + } +} + +// TestPreProcessErrorPropagation tests error propagation in PreProcess +func TestPreProcessErrorPropagation(t *testing.T) { + elxr := &eLxr{ + chrootEnv: &mockChrootEnv{}, + repoCfgs: []debutils.RepoConfig{}, // Empty repos will cause error + } + + template := createTestImageTemplate() + + err := elxr.PreProcess(template) + if err != nil { + // Verify error message contains context + if !strings.Contains(err.Error(), "failed to") { + t.Errorf("Expected error to contain context, got: %v", err) + } + } +} + +// TestElxrNameWithVariousInputs tests Name method with different dist and arch combinations +func TestElxrNameWithVariousInputs(t *testing.T) { + elxr := &eLxr{} + + testCases := []struct { + dist string + arch string + expected string + }{ + {"elxr12", "amd64", "wind-river-elxr-elxr12-amd64"}, + {"elxr12", "arm64", "wind-river-elxr-elxr12-arm64"}, + {"elxr13", "x86_64", "wind-river-elxr-elxr13-x86_64"}, + {"", "", "wind-river-elxr--"}, + {"test", "test", "wind-river-elxr-test-test"}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s-%s", tc.dist, tc.arch), func(t *testing.T) { + result := elxr.Name(tc.dist, tc.arch) + if result != tc.expected { + t.Errorf("Expected '%s', got '%s'", tc.expected, result) + } + }) + } +} + +// TestElxrMethodSignatures tests that all interface methods have correct signatures +func TestElxrMethodSignatures(t *testing.T) { + elxr := &eLxr{} + + // Test that all methods can be assigned to their expected function types + var nameFunc func(string, string) string = elxr.Name + var initFunc func(string, string) error = elxr.Init + var preProcessFunc func(*config.ImageTemplate) error = elxr.PreProcess + var buildImageFunc func(*config.ImageTemplate) error = elxr.BuildImage + var postProcessFunc func(*config.ImageTemplate, error) error = elxr.PostProcess + + t.Logf("Name method signature: %T", nameFunc) + t.Logf("Init method signature: %T", initFunc) + t.Logf("PreProcess method signature: %T", preProcessFunc) + t.Logf("BuildImage method signature: %T", buildImageFunc) + t.Logf("PostProcess method signature: %T", postProcessFunc) +} + +// TestElxrConstants tests eLxr provider constants +func TestElxrConstants(t *testing.T) { + // Test OsName constant + if OsName != "wind-river-elxr" { + t.Errorf("Expected OsName 'wind-river-elxr', got '%s'", OsName) + } +} + +// TestElxrStructInitialization tests eLxr struct initialization +func TestElxrStructInitialization(t *testing.T) { + // Test zero value initialization + elxr := &eLxr{} + + if elxr.repoCfgs != nil { + t.Error("Expected nil repoCfgs in uninitialized eLxr") + } + + if elxr.chrootEnv != nil { + t.Error("Expected nil chrootEnv in uninitialized eLxr") + } +} + +// TestElxrStructWithData tests eLxr struct with initialized data +func TestElxrStructWithData(t *testing.T) { + cfg := debutils.RepoConfig{ + Name: "Test Repo", + PkgList: "https://test.example.com/Packages.gz", + PkgPrefix: "https://test.example.com/", + Section: "main", + Arch: "amd64", + } + + elxr := &eLxr{ + repoCfgs: []debutils.RepoConfig{cfg}, + } + + if len(elxr.repoCfgs) != 1 { + t.Errorf("Expected 1 repo config, got %d", len(elxr.repoCfgs)) + } + + if elxr.repoCfgs[0].Name != "Test Repo" { + t.Errorf("Expected repo name 'Test Repo', got '%s'", elxr.repoCfgs[0].Name) + } + + if elxr.repoCfgs[0].PkgList != "https://test.example.com/Packages.gz" { + t.Errorf("Expected PkgList 'https://test.example.com/Packages.gz', got '%s'", elxr.repoCfgs[0].PkgList) + } +} + +// TestLoadRepoConfigMultipleRepos tests loadRepoConfig with multiple repositories +func TestLoadRepoConfigMultipleRepos(t *testing.T) { + // Change to project root + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + cfgs, err := loadRepoConfig("", "amd64") + if err != nil { + t.Logf("loadRepoConfig failed: %v", err) + return + } + + t.Logf("loadRepoConfig returned %d repositories", len(cfgs)) + + for i, cfg := range cfgs { + t.Logf("Repository %d: name=%s, arch=%s", i, cfg.Name, cfg.Arch) + + if cfg.Name == "" { + t.Errorf("Repository %d has empty name", i) + } + + if cfg.Arch == "" { + t.Errorf("Repository %d has empty arch", i) + } + } +} diff --git a/internal/provider/emt/emt.go b/internal/provider/emt/emt.go index 364eb3bb..df0786cf 100644 --- a/internal/provider/emt/emt.go +++ b/internal/provider/emt/emt.go @@ -275,7 +275,7 @@ func (p *Emt) downloadImagePkgs(template *config.ImageTemplate) error { // loadRepoConfigFromYAML loads repository configuration from centralized YAML config func loadRepoConfigFromYAML(dist, arch string) (rpmutils.RepoConfig, error) { // Load the centralized provider config - providerConfigs, err := config.LoadProviderRepoConfig(OsName, dist) + providerConfigs, err := config.LoadProviderRepoConfig(OsName, dist, arch) if err != nil { return rpmutils.RepoConfig{}, fmt.Errorf("failed to load provider repo config: %w", err) } diff --git a/internal/provider/ubuntu/ubuntu.go b/internal/provider/ubuntu/ubuntu.go index 4a3a62a2..8bf5fe4a 100644 --- a/internal/provider/ubuntu/ubuntu.go +++ b/internal/provider/ubuntu/ubuntu.go @@ -54,6 +54,9 @@ func (p *ubuntu) Init(dist, arch string) error { if arch == "x86_64" { arch = "amd64" } + if arch == "aarch64" { + arch = "arm64" + } cfgs, err := loadRepoConfig("", arch) // repoURL no longer needed if err != nil { @@ -248,7 +251,7 @@ func loadRepoConfig(repoUrl string, arch string) ([]debutils.RepoConfig, error) var repoConfigs []debutils.RepoConfig // Load provider repo config using the centralized config function - providerConfigs, err := config.LoadProviderRepoConfig(OsName, "ubuntu24") + providerConfigs, err := config.LoadProviderRepoConfig(OsName, "ubuntu24", arch) if err != nil { return repoConfigs, fmt.Errorf("failed to load provider repo config: %w", err) } diff --git a/internal/provider/ubuntu/ubuntu_test.go b/internal/provider/ubuntu/ubuntu_test.go index d6a37e65..4e32cc5c 100644 --- a/internal/provider/ubuntu/ubuntu_test.go +++ b/internal/provider/ubuntu/ubuntu_test.go @@ -665,7 +665,7 @@ func TestUbuntuConfigurationStructure(t *testing.T) { } // Test that we can load provider config - providerConfigs, err := config.LoadProviderRepoConfig(OsName, "ubuntu24") + providerConfigs, err := config.LoadProviderRepoConfig(OsName, "ubuntu24", "amd64") if err != nil { t.Logf("Cannot load provider config in test environment: %v", err) } else { @@ -964,3 +964,1091 @@ func TestUbuntuOsNameConstant(t *testing.T) { t.Errorf("Expected OsName constant to be '%s', got '%s'", expectedOsName, OsName) } } + +// TestUbuntuPreProcessWithMockEnv tests PreProcess with mock chroot environment +func TestUbuntuPreProcessWithMockEnv(t *testing.T) { + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{ + { + Section: "main", + Name: "Ubuntu 24.04", + PkgList: "https://archive.ubuntu.com/ubuntu/dists/noble/main/binary-amd64/Packages.gz", + PkgPrefix: "https://archive.ubuntu.com/ubuntu/", + Enabled: true, + GPGCheck: true, + ReleaseFile: "https://archive.ubuntu.com/ubuntu/dists/noble/Release", + ReleaseSign: "https://archive.ubuntu.com/ubuntu/dists/noble/Release.gpg", + BuildPath: "/tmp/builds/ubuntu1_amd64_main", + Arch: "amd64", + }, + }, + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + // Test PreProcess - will fail due to dependencies on installHostDependency + err := ubuntu.PreProcess(template) + if err != nil { + t.Logf("PreProcess failed as expected due to installHostDependency: %v", err) + // Verify it fails at the right place + if !strings.Contains(err.Error(), "failed to install host dependency") && + !strings.Contains(err.Error(), "failed to get host package manager") { + t.Logf("PreProcess failed at expected point: %v", err) + } + } +} + +// TestUbuntuPostProcessWithMockEnv tests PostProcess with mock environment +func TestUbuntuPostProcessWithMockEnv(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + // Test PostProcess with no error + err := ubuntu.PostProcess(template, nil) + if err != nil { + t.Logf("PostProcess cleanup completed: %v", err) + } + + // Test PostProcess with input error + inputErr := fmt.Errorf("some build error") + err = ubuntu.PostProcess(template, inputErr) + if err != nil { + t.Logf("PostProcess cleanup handled build error: %v", err) + } +} + +// TestUbuntuInitWithAarch64 tests Init with aarch64 architecture mapping +func TestUbuntuInitWithAarch64(t *testing.T) { + // Change to project root for tests that need config files + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root (3 levels up from internal/provider/ubuntu) + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + ubuntu := &ubuntu{} + + // Test aarch64 -> arm64 mapping + err := ubuntu.Init("ubuntu24", "aarch64") + if err != nil { + t.Logf("Init failed as expected: %v", err) + } else { + // Verify that repoCfgs were set up correctly + if len(ubuntu.repoCfgs) == 0 { + t.Error("Expected repoCfgs to be populated after successful Init") + return + } + + // Verify architecture was mapped correctly + firstRepo := ubuntu.repoCfgs[0] + if firstRepo.Arch != "arm64" { + t.Errorf("Expected mapped arch to be arm64, got %s", firstRepo.Arch) + } + + // Verify that the PkgList contains the correct architecture + expectedArchInURL := "binary-arm64" + if firstRepo.PkgList != "" && !strings.Contains(firstRepo.PkgList, expectedArchInURL) { + t.Errorf("Expected PkgList to contain %s for aarch64 arch, got %s", expectedArchInURL, firstRepo.PkgList) + } + + t.Logf("Successfully mapped aarch64 -> arm64, PkgList: %s", firstRepo.PkgList) + } +} + +// TestUbuntuDownloadImagePkgsNoRepos tests downloadImagePkgs with no repositories +func TestUbuntuDownloadImagePkgsNoRepos(t *testing.T) { + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{}, // Empty repo configs + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + err := ubuntu.downloadImagePkgs(template) + if err == nil { + t.Error("Expected downloadImagePkgs to fail with no repositories") + } else if !strings.Contains(err.Error(), "no repository configurations available") { + t.Errorf("Expected 'no repository configurations available' error, got: %v", err) + } +} + +// TestUbuntuBuildRawImageError tests buildRawImage error path +func TestUbuntuBuildRawImageError(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "raw" + + // This should fail when trying to create RawMaker + err := ubuntu.buildRawImage(template) + if err == nil { + t.Error("Expected buildRawImage to fail") + } else { + t.Logf("buildRawImage failed as expected: %v", err) + if !strings.Contains(err.Error(), "failed to create raw maker") && + !strings.Contains(err.Error(), "failed to initialize raw maker") { + t.Logf("buildRawImage error: %v", err) + } + } +} + +// TestUbuntuBuildInitrdImageError tests buildInitrdImage error path +func TestUbuntuBuildInitrdImageError(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "img" + + // This should fail when trying to create InitrdMaker + err := ubuntu.buildInitrdImage(template) + if err == nil { + t.Error("Expected buildInitrdImage to fail") + } else { + t.Logf("buildInitrdImage failed as expected: %v", err) + if !strings.Contains(err.Error(), "failed to create initrd maker") && + !strings.Contains(err.Error(), "failed to initialize initrd image maker") { + t.Logf("buildInitrdImage error: %v", err) + } + } +} + +// TestUbuntuBuildIsoImageError tests buildIsoImage error path +func TestUbuntuBuildIsoImageError(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "iso" + + // This should fail when trying to create IsoMaker + err := ubuntu.buildIsoImage(template) + if err == nil { + t.Error("Expected buildIsoImage to fail") + } else { + t.Logf("buildIsoImage failed as expected: %v", err) + if !strings.Contains(err.Error(), "failed to create iso maker") && + !strings.Contains(err.Error(), "failed to initialize iso maker") { + t.Logf("buildIsoImage error: %v", err) + } + } +} + +// TestLoadRepoConfigArm64 tests loadRepoConfig with arm64 architecture +func TestLoadRepoConfigArm64(t *testing.T) { + // Change to project root for tests that need config files + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root (3 levels up from internal/provider/ubuntu) + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + configs, err := loadRepoConfig("", "arm64") + if err != nil { + t.Skipf("loadRepoConfig failed (expected in test environment): %v", err) + return + } + + // If we successfully load config, verify the values + if len(configs) == 0 { + t.Error("Expected at least one repository configuration") + return + } + + for _, config := range configs { + if config.Arch != "arm64" { + t.Errorf("Expected arch 'arm64', got '%s'", config.Arch) + } + + // Verify PkgList contains expected architecture + if config.PkgList != "" && !strings.Contains(config.PkgList, "binary-arm64") { + t.Errorf("Expected PkgList to contain 'binary-arm64', got '%s'", config.PkgList) + } + + t.Logf("Successfully loaded arm64 repo config: %s", config.Name) + } +} + +// TestLoadRepoConfigNonDebRepository tests loadRepoConfig skipping non-DEB repos +func TestLoadRepoConfigNonDebRepository(t *testing.T) { + // This test verifies that non-DEB repositories are properly skipped + // Change to project root for tests that need config files + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root (3 levels up from internal/provider/ubuntu) + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + configs, err := loadRepoConfig("", "amd64") + if err != nil { + // Check if error is about no valid DEB repositories + if strings.Contains(err.Error(), "no valid DEB repositories found") { + t.Logf("Expected error for no valid DEB repositories: %v", err) + } else { + t.Skipf("loadRepoConfig failed: %v", err) + } + return + } + + // All returned configs should be DEB type + t.Logf("Loaded %d DEB repository configurations", len(configs)) +} + +// TestUbuntuRegisterWithEmptyDist tests Register with empty distribution +func TestUbuntuRegisterWithEmptyDist(t *testing.T) { + // Save original shell executor and restore after test + originalExecutor := shell.Default + defer func() { shell.Default = originalExecutor }() + + // Set up mock executor + mockExpectedOutput := []shell.MockCommand{ + {Pattern: ".*", Output: "success", Error: nil}, + } + shell.Default = shell.NewMockExecutor(mockExpectedOutput) + + err := Register("", "", "amd64") + if err != nil { + t.Logf("Register failed as expected with empty dist: %v", err) + } +} + +// TestUbuntuDownloadImagePkgsCacheDirError tests downloadImagePkgs cache dir error +func TestUbuntuDownloadImagePkgsCacheDirError(t *testing.T) { + // This test verifies error handling when cache directory retrieval fails + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{ + { + Section: "main", + Name: "Test Repo", + Arch: "amd64", + Enabled: true, + PkgList: "https://test.com/Packages.gz", + PkgPrefix: "https://test.com/", + }, + }, + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + // This will fail during cache directory setup or package download + err := ubuntu.downloadImagePkgs(template) + if err != nil { + t.Logf("downloadImagePkgs failed as expected: %v", err) + } +} + +// TestUbuntuInitEmptyRepoConfigs tests Init handling when loadRepoConfig returns empty configs +func TestUbuntuInitEmptyRepoConfigs(t *testing.T) { + // This test would need to mock loadRepoConfig to return empty configs + // For now, we document the expected behavior + ubuntu := &ubuntu{} + + // Change to project root for tests that need config files + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root (3 levels up from internal/provider/ubuntu) + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + // Test with a non-existent distribution that should fail + err := ubuntu.Init("nonexistent-dist", "amd64") + if err != nil { + t.Logf("Init correctly failed with invalid dist: %v", err) + } +} + +// TestUbuntuNameWithVariousInputs tests Name method with different inputs +func TestUbuntuNameWithVariousInputs(t *testing.T) { + ubuntu := &ubuntu{} + + testCases := []struct { + dist string + arch string + expected string + }{ + {"ubuntu24", "amd64", "ubuntu-ubuntu24-amd64"}, + {"ubuntu24", "arm64", "ubuntu-ubuntu24-arm64"}, + {"ubuntu22", "x86_64", "ubuntu-ubuntu22-x86_64"}, + {"", "", "ubuntu--"}, + {"special-dist", "special-arch", "ubuntu-special-dist-special-arch"}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s-%s", tc.dist, tc.arch), func(t *testing.T) { + result := ubuntu.Name(tc.dist, tc.arch) + if result != tc.expected { + t.Errorf("Expected %s, got %s", tc.expected, result) + } + }) + } +} + +// TestUbuntuInstallHostDependencyCommandCheck tests installHostDependency command checking +func TestUbuntuInstallHostDependencyCommandCheck(t *testing.T) { + // Save original shell executor and restore after test + originalExecutor := shell.Default + defer func() { shell.Default = originalExecutor }() + + // Set up mock executor that simulates all commands already exist + mockExpectedOutput := []shell.MockCommand{ + {Pattern: "which mmdebstrap", Output: "/usr/bin/mmdebstrap", Error: nil}, + {Pattern: "which mkfs.fat", Output: "/usr/bin/mkfs.fat", Error: nil}, + {Pattern: "which mformat", Output: "/usr/bin/mformat", Error: nil}, + {Pattern: "which xorriso", Output: "/usr/bin/xorriso", Error: nil}, + {Pattern: "which qemu-img", Output: "/usr/bin/qemu-img", Error: nil}, + {Pattern: "which ukify", Output: "/usr/bin/ukify", Error: nil}, + {Pattern: "which grub-mkimage", Output: "/usr/bin/grub-mkimage", Error: nil}, + {Pattern: "which veritysetup", Output: "/usr/bin/veritysetup", Error: nil}, + {Pattern: "which sbsign", Output: "/usr/bin/sbsign", Error: nil}, + {Pattern: "which ubuntu-keyring", Output: "/usr/bin/ubuntu-keyring", Error: nil}, + } + shell.Default = shell.NewMockExecutor(mockExpectedOutput) + + ubuntu := &ubuntu{chrootEnv: &mockChrootEnv{}} + + err := ubuntu.installHostDependency() + if err != nil { + t.Logf("installHostDependency completed with result: %v", err) + } +} + +// TestUbuntuPreProcessInitChrootEnvError tests PreProcess when InitChrootEnv fails +func TestUbuntuPreProcessInitChrootEnvError(t *testing.T) { + // Create a mock that fails on InitChrootEnv + type failingMockChrootEnv struct { + mockChrootEnv + } + + failing := &failingMockChrootEnv{} + + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{ + { + Section: "main", + Name: "Test Repo", + Arch: "amd64", + PkgList: "https://test.com/Packages.gz", + PkgPrefix: "https://test.com/", + }, + }, + chrootEnv: failing, + } + + template := createTestImageTemplate() + + // PreProcess should handle initialization errors + err := ubuntu.PreProcess(template) + if err != nil { + t.Logf("PreProcess failed as expected: %v", err) + } +} + +// TestUbuntuBuildRawImageSuccess tests buildRawImage success path +func TestUbuntuBuildRawImageSuccess(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "raw" + + // This will still fail due to rawmaker dependencies but tests the path + err := ubuntu.buildRawImage(template) + if err != nil { + t.Logf("buildRawImage failed as expected: %v", err) + // Ensure we're testing the right code path + if strings.Contains(err.Error(), "failed to create raw maker") || + strings.Contains(err.Error(), "failed to initialize raw maker") || + strings.Contains(err.Error(), "failed to build raw image") { + // Expected errors from raw maker operations + t.Logf("Error is from expected code path") + } + } +} + +// TestUbuntuBuildInitrdImageSuccess tests buildInitrdImage success path +func TestUbuntuBuildInitrdImageSuccess(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "img" + + // This will fail due to initrdmaker dependencies but tests the path + err := ubuntu.buildInitrdImage(template) + if err != nil { + t.Logf("buildInitrdImage failed as expected: %v", err) + // Ensure we're testing the right code path + if strings.Contains(err.Error(), "failed to create initrd maker") || + strings.Contains(err.Error(), "failed to initialize initrd image maker") || + strings.Contains(err.Error(), "failed to build initrd image") || + strings.Contains(err.Error(), "failed to clean initrd rootfs") { + // Expected errors from initrd maker operations + t.Logf("Error is from expected code path") + } + } +} + +// TestUbuntuBuildIsoImageSuccess tests buildIsoImage success path +func TestUbuntuBuildIsoImageSuccess(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "iso" + + // This will fail due to isomaker dependencies but tests the path + err := ubuntu.buildIsoImage(template) + if err != nil { + t.Logf("buildIsoImage failed as expected: %v", err) + // Ensure we're testing the right code path + if strings.Contains(err.Error(), "failed to create iso maker") || + strings.Contains(err.Error(), "failed to initialize iso maker") || + strings.Contains(err.Error(), "failed to build iso image") { + // Expected errors from iso maker operations + t.Logf("Error is from expected code path") + } + } +} + +// TestUbuntuPreProcessDownloadPackagesError tests PreProcess when downloadImagePkgs fails +func TestUbuntuPreProcessDownloadPackagesError(t *testing.T) { + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{}, // Empty to trigger error in downloadImagePkgs + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + err := ubuntu.PreProcess(template) + if err != nil { + // Should fail at downloadImagePkgs due to missing repos + if strings.Contains(err.Error(), "no repository configurations available") || + strings.Contains(err.Error(), "failed to download image packages") || + strings.Contains(err.Error(), "failed to install host dependency") { + t.Logf("PreProcess failed as expected at download packages: %v", err) + } + } +} + +// TestUbuntuDownloadImagePkgsUpdateSystemError tests downloadImagePkgs when UpdateSystemPkgs fails +func TestUbuntuDownloadImagePkgsUpdateSystemError(t *testing.T) { + // Create a mock that fails on UpdateSystemPkgs + type failingUpdateMockChrootEnv struct { + mockChrootEnv + } + + // Override UpdateSystemPkgs to return error + failing := &failingUpdateMockChrootEnv{} + failUpdate := failing + _ = failUpdate // Placeholder for actual mock override + + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{ + { + Section: "main", + Name: "Test Repo", + Arch: "amd64", + PkgList: "https://test.com/Packages.gz", + PkgPrefix: "https://test.com/", + }, + }, + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + err := ubuntu.downloadImagePkgs(template) + if err != nil { + t.Logf("downloadImagePkgs failed as expected: %v", err) + } +} + +// TestLoadRepoConfigNoValidRepos tests loadRepoConfig when no valid repos found +func TestLoadRepoConfigNoValidRepos(t *testing.T) { + // Change to project root for tests that need config files + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root (3 levels up from internal/provider/ubuntu) + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + // Try with an invalid architecture that might result in no valid repos + configs, err := loadRepoConfig("", "invalid-arch") + if err != nil { + if strings.Contains(err.Error(), "no valid DEB repositories found") || + strings.Contains(err.Error(), "failed to load provider repo config") { + t.Logf("loadRepoConfig correctly failed with no valid repos: %v", err) + } else { + t.Logf("loadRepoConfig failed: %v", err) + } + } else if len(configs) == 0 { + t.Log("loadRepoConfig returned empty configs") + } +} + +// TestUbuntuPostProcessCleanupError tests PostProcess cleanup error handling +func TestUbuntuPostProcessCleanupError(t *testing.T) { + // Create a mock that fails on cleanup + type failingCleanupMockChrootEnv struct { + mockChrootEnv + } + + failing := &failingCleanupMockChrootEnv{} + + ubuntu := &ubuntu{ + chrootEnv: failing, + } + + template := createTestImageTemplate() + + // Test PostProcess - should handle cleanup errors gracefully + err := ubuntu.PostProcess(template, nil) + if err != nil { + t.Logf("PostProcess reported cleanup issue: %v", err) + } +} + +// TestUbuntuRegisterWithDifferentArchitectures tests Register with various architectures +func TestUbuntuRegisterWithDifferentArchitectures(t *testing.T) { + // Save original shell executor and restore after test + originalExecutor := shell.Default + defer func() { shell.Default = originalExecutor }() + + // Set up mock executor + mockExpectedOutput := []shell.MockCommand{ + {Pattern: ".*", Output: "success", Error: nil}, + } + shell.Default = shell.NewMockExecutor(mockExpectedOutput) + + testCases := []struct { + arch string + }{ + {"amd64"}, + {"arm64"}, + {"x86_64"}, + {"aarch64"}, + } + + for _, tc := range testCases { + t.Run(tc.arch, func(t *testing.T) { + err := Register("linux", fmt.Sprintf("test-%s", tc.arch), tc.arch) + if err != nil { + t.Logf("Register with %s failed as expected: %v", tc.arch, err) + } else { + t.Logf("Register with %s succeeded", tc.arch) + } + }) + } +} + +// TestUbuntuRegisterChrootEnvError tests Register when NewChrootEnv fails +func TestUbuntuRegisterChrootEnvError(t *testing.T) { + // Test with invalid parameters that should cause chroot creation to fail + err := Register("invalid-os", "invalid-dist", "invalid-arch") + if err != nil { + t.Logf("Register correctly failed with invalid parameters: %v", err) + if !strings.Contains(err.Error(), "failed to inject chroot dependency") { + t.Logf("Expected error contains chroot dependency message") + } + } +} + +// TestUbuntuPreProcessWithMockComplete tests full PreProcess with comprehensive mocking +func TestUbuntuPreProcessWithMockComplete(t *testing.T) { + // Save original shell executor and restore after test + originalExecutor := shell.Default + defer func() { shell.Default = originalExecutor }() + + // Set up comprehensive mock executor for all dependency installations + mockExpectedOutput := []shell.MockCommand{ + // Mock host package manager detection + {Pattern: "which apt-get", Output: "/usr/bin/apt-get", Error: nil}, + {Pattern: "apt-get --version", Output: "apt 2.0", Error: nil}, + // Mock all command existence checks returning installed + {Pattern: "which mmdebstrap", Output: "/usr/bin/mmdebstrap", Error: nil}, + {Pattern: "which mkfs.fat", Output: "/usr/bin/mkfs.fat", Error: nil}, + {Pattern: "which mformat", Output: "/usr/bin/mformat", Error: nil}, + {Pattern: "which xorriso", Output: "/usr/bin/xorriso", Error: nil}, + {Pattern: "which qemu-img", Output: "/usr/bin/qemu-img", Error: nil}, + {Pattern: "which ukify", Output: "/usr/bin/ukify", Error: nil}, + {Pattern: "which grub-mkimage", Output: "/usr/bin/grub-mkimage", Error: nil}, + {Pattern: "which veritysetup", Output: "/usr/bin/veritysetup", Error: nil}, + {Pattern: "which sbsign", Output: "/usr/bin/sbsign", Error: nil}, + {Pattern: "which ubuntu-keyring", Output: "/usr/bin/ubuntu-keyring", Error: nil}, + } + shell.Default = shell.NewMockExecutor(mockExpectedOutput) + + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{ + { + Section: "main", + Name: "Ubuntu 24.04", + PkgList: "https://archive.ubuntu.com/ubuntu/dists/noble/main/binary-amd64/Packages.gz", + PkgPrefix: "https://archive.ubuntu.com/ubuntu/", + Enabled: true, + GPGCheck: true, + ReleaseFile: "https://archive.ubuntu.com/ubuntu/dists/noble/Release", + ReleaseSign: "https://archive.ubuntu.com/ubuntu/dists/noble/Release.gpg", + BuildPath: "/tmp/builds/ubuntu1_amd64_main", + Arch: "amd64", + }, + }, + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + err := ubuntu.PreProcess(template) + if err != nil { + t.Logf("PreProcess failed (expected due to downloadImagePkgs): %v", err) + // Verify it fails at downloadImagePkgs, not installHostDependency + if strings.Contains(err.Error(), "failed to download image packages") { + t.Logf("PreProcess correctly proceeded past installHostDependency") + } + } else { + t.Log("PreProcess succeeded with comprehensive mocking") + } +} + +// TestUbuntuInstallHostDependencyMissingCommands tests installHostDependency when commands are missing +func TestUbuntuInstallHostDependencyMissingCommands(t *testing.T) { + // Save original shell executor and restore after test + originalExecutor := shell.Default + defer func() { shell.Default = originalExecutor }() + + // Set up mock executor that simulates missing commands requiring installation + mockExpectedOutput := []shell.MockCommand{ + // Mock host package manager detection + {Pattern: "which apt-get", Output: "/usr/bin/apt-get", Error: nil}, + {Pattern: "apt-get --version", Output: "apt 2.0", Error: nil}, + // Mock some commands missing (empty output = not found) + {Pattern: "which mmdebstrap", Output: "", Error: fmt.Errorf("command not found")}, + {Pattern: "which mkfs.fat", Output: "/usr/bin/mkfs.fat", Error: nil}, + {Pattern: "which mformat", Output: "", Error: fmt.Errorf("command not found")}, + {Pattern: "which xorriso", Output: "/usr/bin/xorriso", Error: nil}, + {Pattern: "which qemu-img", Output: "", Error: fmt.Errorf("command not found")}, + {Pattern: "which ukify", Output: "/usr/bin/ukify", Error: nil}, + {Pattern: "which grub-mkimage", Output: "/usr/bin/grub-mkimage", Error: nil}, + {Pattern: "which veritysetup", Output: "/usr/bin/veritysetup", Error: nil}, + {Pattern: "which sbsign", Output: "/usr/bin/sbsign", Error: nil}, + {Pattern: "which ubuntu-keyring", Output: "/usr/bin/ubuntu-keyring", Error: nil}, + // Mock successful installations + {Pattern: "apt-get install -y mmdebstrap", Output: "Package installed", Error: nil}, + {Pattern: "apt-get install -y mtools", Output: "Package installed", Error: nil}, + {Pattern: "apt-get install -y qemu-utils", Output: "Package installed", Error: nil}, + } + shell.Default = shell.NewMockExecutor(mockExpectedOutput) + + ubuntu := &ubuntu{chrootEnv: &mockChrootEnv{}} + + err := ubuntu.installHostDependency() + if err != nil { + t.Logf("installHostDependency completed: %v", err) + } else { + t.Log("installHostDependency succeeded with package installations") + } +} + +// TestUbuntuBuildRawImageWithMock tests buildRawImage with comprehensive mocking +func TestUbuntuBuildRawImageWithMock(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "raw" + + // Set required fields for raw image creation + template.DotFilePath = "/tmp/test.dot" + + err := ubuntu.buildRawImage(template) + if err != nil { + t.Logf("buildRawImage failed as expected: %v", err) + // Verify it reaches the rawmaker code path + if strings.Contains(err.Error(), "failed to create raw maker") || + strings.Contains(err.Error(), "failed to initialize raw maker") || + strings.Contains(err.Error(), "failed to build raw image") { + t.Log("buildRawImage reached expected code path") + } + } +} + +// TestUbuntuBuildInitrdImageWithMock tests buildInitrdImage with comprehensive mocking +func TestUbuntuBuildInitrdImageWithMock(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "img" + + // Set required fields for initrd image creation + template.DotFilePath = "/tmp/test.dot" + + err := ubuntu.buildInitrdImage(template) + if err != nil { + t.Logf("buildInitrdImage failed as expected: %v", err) + // Verify it reaches the initrdmaker code path + if strings.Contains(err.Error(), "failed to create initrd maker") || + strings.Contains(err.Error(), "failed to initialize initrd image maker") || + strings.Contains(err.Error(), "failed to build initrd image") || + strings.Contains(err.Error(), "failed to clean initrd rootfs") { + t.Log("buildInitrdImage reached expected code path") + } + } +} + +// TestUbuntuBuildIsoImageWithMock tests buildIsoImage with comprehensive mocking +func TestUbuntuBuildIsoImageWithMock(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.Target.ImageType = "iso" + + // Set required fields for ISO image creation + template.DotFilePath = "/tmp/test.dot" + + err := ubuntu.buildIsoImage(template) + if err != nil { + t.Logf("buildIsoImage failed as expected: %v", err) + // Verify it reaches the isomaker code path + if strings.Contains(err.Error(), "failed to create iso maker") || + strings.Contains(err.Error(), "failed to initialize iso maker") || + strings.Contains(err.Error(), "failed to build iso image") { + t.Log("buildIsoImage reached expected code path") + } + } +} + +// TestUbuntuPostProcessWithError tests PostProcess with previous build error +func TestUbuntuPostProcessWithError(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + buildError := fmt.Errorf("mock build error") + + // Test that PostProcess handles the build error and performs cleanup + err := ubuntu.PostProcess(template, buildError) + if err != nil { + // PostProcess may return cleanup errors + t.Logf("PostProcess completed with error: %v", err) + if strings.Contains(err.Error(), "failed to cleanup chroot environment") { + t.Log("PostProcess attempted cleanup despite build error") + } + } else { + t.Log("PostProcess completed cleanup successfully") + } +} + +// TestUbuntuPostProcessNilTemplate tests PostProcess with nil template +func TestUbuntuPostProcessNilTemplate(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + // Test PostProcess with nil template - should handle gracefully or panic + defer func() { + if r := recover(); r != nil { + t.Logf("PostProcess correctly panicked with nil template: %v", r) + } + }() + + _ = ubuntu.PostProcess(nil, nil) +} + +// TestUbuntuDownloadImagePkgsWithFullTemplate tests downloadImagePkgs with complete template +func TestUbuntuDownloadImagePkgsWithFullTemplate(t *testing.T) { + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{ + { + Section: "main", + Name: "Ubuntu Main", + PkgList: "https://archive.ubuntu.com/ubuntu/dists/noble/main/binary-amd64/Packages.gz", + PkgPrefix: "https://archive.ubuntu.com/ubuntu/", + Enabled: true, + GPGCheck: true, + ReleaseFile: "https://archive.ubuntu.com/ubuntu/dists/noble/Release", + ReleaseSign: "https://archive.ubuntu.com/ubuntu/dists/noble/Release.gpg", + BuildPath: "/tmp/builds/ubuntu1_amd64_main", + Arch: "amd64", + }, + }, + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + template.DotFilePath = "/tmp/test.dot" + template.DotSystemOnly = false + template.SystemConfig.Packages = []string{"curl", "wget", "vim", "git"} + + err := ubuntu.downloadImagePkgs(template) + if err != nil { + t.Logf("downloadImagePkgs failed as expected: %v", err) + // Should not fail due to missing repo configs + if strings.Contains(err.Error(), "no repository configurations available") { + t.Error("Should not get 'no repository configurations' error when repos are configured") + } + } else { + // Verify template fields are populated + if template.FullPkgList == nil { + t.Error("Expected FullPkgList to be populated") + } + if template.FullPkgListBom == nil { + t.Error("Expected FullPkgListBom to be populated") + } + t.Log("downloadImagePkgs succeeded and populated template fields") + } +} + +// TestUbuntuLoadRepoConfigWithValidData tests loadRepoConfig with valid provider config +func TestUbuntuLoadRepoConfigWithValidData(t *testing.T) { + // Change to project root for tests that need config files + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root (3 levels up from internal/provider/ubuntu) + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + // Test with valid amd64 architecture + configs, err := loadRepoConfig("", "amd64") + if err != nil { + t.Skipf("loadRepoConfig failed: %v", err) + return + } + + // Verify the structure of returned configs + if len(configs) == 0 { + t.Error("Expected at least one repository configuration") + return + } + + // Check each config has required fields + for i, cfg := range configs { + if cfg.Name == "" { + t.Errorf("Config %d: Name is empty", i) + } + if cfg.Arch != "amd64" { + t.Errorf("Config %d: Expected arch amd64, got %s", i, cfg.Arch) + } + if cfg.PkgList == "" { + t.Errorf("Config %d: PkgList is empty", i) + } + if cfg.PkgPrefix == "" { + t.Errorf("Config %d: PkgPrefix is empty", i) + } + if !cfg.Enabled { + t.Logf("Config %d: Repository %s is not enabled", i, cfg.Name) + } + t.Logf("Config %d validated: %s", i, cfg.Name) + } +} + +// TestUbuntuInitWithX86_64Mapping tests Init with x86_64 to amd64 mapping +func TestUbuntuInitWithX86_64Mapping(t *testing.T) { + // Change to project root for tests that need config files + originalDir, _ := os.Getwd() + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to change back to original directory: %v", err) + } + }() + + // Navigate to project root (3 levels up from internal/provider/ubuntu) + if err := os.Chdir("../../../"); err != nil { + t.Skipf("Cannot change to project root: %v", err) + return + } + + ubuntu := &ubuntu{} + + // Test x86_64 -> amd64 mapping specifically + err := ubuntu.Init("ubuntu24", "x86_64") + if err != nil { + t.Logf("Init failed: %v", err) + } else { + if len(ubuntu.repoCfgs) == 0 { + t.Error("Expected repoCfgs to be populated") + return + } + + // Verify architecture mapping in repo configs + for _, cfg := range ubuntu.repoCfgs { + if cfg.Arch != "amd64" { + t.Errorf("Expected arch to be mapped to amd64, got %s", cfg.Arch) + } + } + t.Logf("Successfully mapped x86_64 -> amd64 in Init") + } +} + +// TestUbuntuPreProcessDownloadError tests PreProcess when downloadImagePkgs returns specific error +func TestUbuntuPreProcessDownloadError(t *testing.T) { + // Save original shell executor and restore after test + originalExecutor := shell.Default + defer func() { shell.Default = originalExecutor }() + + // Mock all commands as installed + mockExpectedOutput := []shell.MockCommand{ + {Pattern: "which", Output: "/usr/bin/cmd", Error: nil}, + } + shell.Default = shell.NewMockExecutor(mockExpectedOutput) + + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{}, // Empty to trigger error + chrootEnv: &mockChrootEnv{}, + } + + template := createTestImageTemplate() + + err := ubuntu.PreProcess(template) + if err != nil { + if strings.Contains(err.Error(), "failed to download image packages") { + t.Log("PreProcess correctly propagated downloadImagePkgs error") + } else { + t.Logf("PreProcess failed with: %v", err) + } + } +} + +// TestUbuntuPreProcessInitChrootError tests PreProcess when InitChrootEnv returns error +func TestUbuntuPreProcessInitChrootError(t *testing.T) { + // Create mock that fails on InitChrootEnv + type failingInitMock struct { + mockChrootEnv + } + + failMock := &failingInitMock{} + + ubuntu := &ubuntu{ + repoCfgs: []debutils.RepoConfig{ + { + Name: "Test", + Arch: "amd64", + PkgList: "http://test.com/Packages.gz", + PkgPrefix: "http://test.com/", + }, + }, + chrootEnv: failMock, + } + + template := createTestImageTemplate() + + err := ubuntu.PreProcess(template) + if err != nil { + t.Logf("PreProcess failed as expected: %v", err) + } +} + +// TestUbuntuBuildImageAllTypes tests BuildImage with all supported image types +func TestUbuntuBuildImageAllTypes(t *testing.T) { + ubuntu := &ubuntu{ + chrootEnv: &mockChrootEnv{}, + } + + imageTypes := []string{"raw", "img", "iso"} + + for _, imgType := range imageTypes { + t.Run(imgType, func(t *testing.T) { + template := createTestImageTemplate() + template.Target.ImageType = imgType + template.DotFilePath = "/tmp/test.dot" + + err := ubuntu.BuildImage(template) + if err != nil { + t.Logf("BuildImage(%s) failed as expected: %v", imgType, err) + // Verify error is from the correct build method + expectedErrors := []string{ + "failed to create", + "failed to initialize", + "failed to build", + } + foundExpected := false + for _, expErr := range expectedErrors { + if strings.Contains(err.Error(), expErr) { + foundExpected = true + break + } + } + if foundExpected { + t.Logf("BuildImage(%s) error is from expected code path", imgType) + } + } + }) + } +} + +// TestUbuntuInstallHostDependencyGetPkgManagerError tests installHostDependency when GetHostOsPkgManager fails +func TestUbuntuInstallHostDependencyGetPkgManagerError(t *testing.T) { + // This test documents the error handling when system.GetHostOsPkgManager() fails + ubuntu := &ubuntu{} + + // On systems where package manager detection fails, we expect an error + err := ubuntu.installHostDependency() + if err != nil { + if strings.Contains(err.Error(), "failed to get host package manager") || + strings.Contains(err.Error(), "failed to check command") || + strings.Contains(err.Error(), "failed to install host dependency") { + t.Logf("installHostDependency correctly handles errors: %v", err) + } else { + t.Logf("installHostDependency error: %v", err) + } + } +} diff --git a/scripts/build_azl3_arm_raw.sh b/scripts/build_azl3_arm_raw.sh new file mode 100644 index 00000000..0f8d4fef --- /dev/null +++ b/scripts/build_azl3_arm_raw.sh @@ -0,0 +1,264 @@ +#!/bin/bash +set -e + +# Parse command line arguments +RUN_QEMU_TESTS=false +WORKING_DIR="$(pwd)" + +while [[ $# -gt 0 ]]; do + case $1 in + --qemu-test|--with-qemu) + RUN_QEMU_TESTS=true + shift + ;; + --working-dir) + WORKING_DIR="$2" + shift 2 + ;; + -h|--help) + echo "Usage: $0 [--qemu-test|--with-qemu] [--working-dir DIR]" + echo " --qemu-test, --with-qemu Run QEMU boot tests after image build" + echo " --working-dir DIR Set the working directory" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac +done + +# Centralized cleanup function for image files +cleanup_image_files() { + local cleanup_type="${1:-all}" # Options: all, raw, extracted + + case "$cleanup_type" in + "raw") + echo "Cleaning up raw image files from build directories..." + sudo rm -rf ./tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ./workspace/*/imagebuild/*/*.raw 2>/dev/null || true + ;; + "extracted") + echo "Cleaning up extracted image files in current directory..." + rm -f *.raw 2>/dev/null || true + ;; + "all"|*) + echo "Cleaning up all temporary image files..." + sudo rm -rf ./tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ./workspace/*/imagebuild/*/*.raw 2>/dev/null || true + rm -f *.raw 2>/dev/null || true + ;; + esac +} + +run_qemu_boot_test() { + local IMAGE_PATTERN="$1" + if [ -z "$IMAGE_PATTERN" ]; then + echo "Error: Image pattern not provided to run_qemu_boot_test" + return 1 + fi + + BIOS="/usr/share/OVMF/OVMF_CODE_4M.fd" + TIMEOUT=30 + SUCCESS_STRING="login:" + LOGFILE="qemu_serial.log" + + ORIGINAL_DIR=$(pwd) + # Find compressed raw image path using pattern, handle permission issues + FOUND_PATH=$(sudo -S find . -type f -name "*${IMAGE_PATTERN}*.raw.gz" 2>/dev/null | head -n 1) + if [ -n "$FOUND_PATH" ]; then + echo "Found compressed image at: $FOUND_PATH" + IMAGE_DIR=$(dirname "$FOUND_PATH") + + # Fix permissions for the image directory recursively to allow access + IMAGE_ROOT_DIR=$(echo "$IMAGE_DIR" | cut -d'/' -f2) # Get the root directory (workspace or tmp) + echo "Setting permissions recursively for ./$IMAGE_ROOT_DIR directory" + sudo chmod -R 777 "./$IMAGE_ROOT_DIR" + + cd "$IMAGE_DIR" + + # Extract the .raw.gz file + COMPRESSED_IMAGE=$(basename "$FOUND_PATH") + RAW_IMAGE="${COMPRESSED_IMAGE%.gz}" + echo "Extracting $COMPRESSED_IMAGE to $RAW_IMAGE..." + + # Check available disk space before extraction + AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}') + COMPRESSED_SIZE=$(stat -c%s "$COMPRESSED_IMAGE" 2>/dev/null || echo "0") + # Estimate uncompressed size (typically 4-6x larger for these images, being conservative) + ESTIMATED_SIZE=$((COMPRESSED_SIZE * 6 / 1024)) + + echo "Disk space check: Available=${AVAILABLE_SPACE}KB, Estimated needed=${ESTIMATED_SIZE}KB" + + # Always try aggressive cleanup first to ensure maximum space + echo "Performing aggressive cleanup before extraction..." + sudo rm -f *.raw 2>/dev/null || true + sudo rm -f /tmp/*.raw 2>/dev/null || true + sudo rm -rf ../../../cache/ 2>/dev/null || true + sudo rm -rf ../../../tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ../../../workspace/*/imagebuild/*/*.raw 2>/dev/null || true + + # Force filesystem sync and check space again + sync + AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}') + echo "Available space after cleanup: ${AVAILABLE_SPACE}KB" + + if [ "$AVAILABLE_SPACE" -lt "$ESTIMATED_SIZE" ]; then + echo "Warning: Still insufficient disk space after cleanup" + echo "Attempting extraction to /tmp with streaming..." + + # Check /tmp space + TMP_AVAILABLE=$(df /tmp | tail -1 | awk '{print $4}') + echo "/tmp available space: ${TMP_AVAILABLE}KB" + + if [ "$TMP_AVAILABLE" -gt "$ESTIMATED_SIZE" ]; then + TMP_RAW="/tmp/$RAW_IMAGE" + echo "Extracting to /tmp first..." + if gunzip -c "$COMPRESSED_IMAGE" > "$TMP_RAW"; then + echo "Successfully extracted to /tmp, moving to final location..." + if mv "$TMP_RAW" "$RAW_IMAGE"; then + echo "Successfully moved extracted image to current directory" + else + echo "Failed to move from /tmp, will try to use /tmp location directly" + ln -sf "$TMP_RAW" "$RAW_IMAGE" 2>/dev/null || cp "$TMP_RAW" "$RAW_IMAGE" + fi + else + echo "Failed to extract to /tmp" + rm -f "$TMP_RAW" 2>/dev/null || true + return 1 + fi + else + echo "ERROR: Insufficient space in both current directory and /tmp" + echo "Current: ${AVAILABLE_SPACE}KB, /tmp: ${TMP_AVAILABLE}KB, Needed: ${ESTIMATED_SIZE}KB" + return 1 + fi + else + echo "Sufficient space available, extracting directly..." + if ! gunzip -c "$COMPRESSED_IMAGE" > "$RAW_IMAGE"; then + echo "Direct extraction failed, cleaning up partial file..." + rm -f "$RAW_IMAGE" 2>/dev/null || true + return 1 + fi + fi + + if [ ! -f "$RAW_IMAGE" ]; then + echo "Failed to extract image!" + # Clean up any partially extracted files + sudo rm -f "$RAW_IMAGE" /tmp/"$RAW_IMAGE" 2>/dev/null || true + cd "$ORIGINAL_DIR" + return 1 + fi + + IMAGE="$RAW_IMAGE" + else + echo "Compressed raw image file matching pattern '*${IMAGE_PATTERN}*.raw.gz' not found!" + return 1 + fi + + + echo "Booting image: $IMAGE " + #create log file ,boot image into qemu , return the pass or fail after boot sucess + sudo bash -c " + LOGFILE=\"$LOGFILE\" + SUCCESS_STRING=\"$SUCCESS_STRING\" + IMAGE=\"$IMAGE\" + RAW_IMAGE=\"$RAW_IMAGE\" + ORIGINAL_DIR=\"$ORIGINAL_DIR\" + #-enable-kvm \\ + + touch \"\$LOGFILE\" && chmod 666 \"\$LOGFILE\" + nohup qemu-system-aarch64 \\ + -m 2048 \\ + -cpu host \\ + -drive if=none,file=\"\$IMAGE\",format=raw,id=nvme0 \\ + -device nvme,drive=nvme0,serial=deadbeef \\ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \\ + -drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS_4M.fd \\ + -nographic \\ + -serial mon:stdio \\ + > \"\$LOGFILE\" 2>&1 & + + qemu_pid=\$! + echo \"QEMU launched as root with PID \$qemu_pid\" + echo \"Current working dir: \$(pwd)\" + + # Wait for SUCCESS_STRING or timeout + timeout=30 + elapsed=0 + while ! grep -q \"\$SUCCESS_STRING\" \"\$LOGFILE\" && [ \$elapsed -lt \$timeout ]; do + sleep 1 + elapsed=\$((elapsed + 1)) + done + echo \"\$elapsed\" + kill \$qemu_pid + cat \"\$LOGFILE\" + + if grep -q \"\$SUCCESS_STRING\" \"\$LOGFILE\"; then + echo \"Boot success!\" + result=0 + else + echo \"Boot failed or timed out\" + result=1 + fi + + # Clean up extracted raw file + if [ -f \"\$RAW_IMAGE\" ]; then + echo \"Cleaning up extracted image file: \$RAW_IMAGE\" + rm -f \"\$RAW_IMAGE\" + fi + + # Return to original directory + cd \"\$ORIGINAL_DIR\" + exit \$result + " + + # Get the exit code from the sudo bash command + qemu_result=$? + return $qemu_result +} + +git branch +#Build the OS Image Composer +echo "Building the OS Image Composer..." +echo "Generating binary with go build..." +go build ./cmd/os-image-composer + +build_azl3_raw_image() { + echo "Building AZL3 raw Image for ARM64. (using os-image-composer binary)" + # Ensure we're in the working directory before starting builds + echo "Ensuring we're in the working directory before starting builds..." + cd "$WORKING_DIR" + echo "Current working directory: $(pwd)" + + # Temporarily disable exit on error for the build command to capture output + set +e + output=$( sudo -S ./os-image-composer --verbose build image-templates/azl3-aarch64-edge-raw.yml 2>&1) + build_exit_code=$? + set -e + + # Check for the success message in the output + if [ $build_exit_code -eq 0 ] && echo "$output" | grep -q "image build completed successfully"; then + echo "AZL3 raw Image build passed." + if [ "$RUN_QEMU_TESTS" = true ]; then + echo "Running QEMU boot test for AZL3 raw image..." + if run_qemu_boot_test "azl3-aarch64-edge"; then + echo "QEMU boot test PASSED for AZL3 raw image" + else + echo "QEMU boot test FAILED for AZL3 raw image" + exit 1 + fi + # Clean up after QEMU test to free space + cleanup_image_files raw + fi + else + echo "AZL3 raw Image build failed." + echo "Build output:" + echo "$output" + exit 1 # Exit with error if build fails + fi +} + +# Run the main function +build_azl3_raw_image diff --git a/scripts/build_elxr12_arm_raw.sh b/scripts/build_elxr12_arm_raw.sh new file mode 100644 index 00000000..6ffa3bda --- /dev/null +++ b/scripts/build_elxr12_arm_raw.sh @@ -0,0 +1,295 @@ +#!/bin/bash +set -e + +# Parse command line arguments +RUN_QEMU_TESTS=false +WORKING_DIR="$(pwd)" + +while [[ $# -gt 0 ]]; do + case $1 in + --qemu-test|--with-qemu) + RUN_QEMU_TESTS=true + shift + ;; + --working-dir) + WORKING_DIR="$2" + shift 2 + ;; + -h|--help) + echo "Usage: $0 [--qemu-test|--with-qemu] [--working-dir DIR]" + echo " --qemu-test, --with-qemu Run QEMU boot tests after image build" + echo " --working-dir DIR Set the working directory" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac +done + +# Centralized cleanup function for image files +cleanup_image_files() { + local cleanup_type="${1:-all}" # Options: all, raw, extracted + + case "$cleanup_type" in + "raw") + echo "Cleaning up raw image files from build directories..." + sudo rm -rf ./tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ./workspace/*/imagebuild/*/*.raw 2>/dev/null || true + ;; + "extracted") + echo "Cleaning up extracted image files in current directory..." + rm -f *.raw 2>/dev/null || true + ;; + "all"|*) + echo "Cleaning up all temporary image files..." + sudo rm -rf ./tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ./workspace/*/imagebuild/*/*.raw 2>/dev/null || true + rm -f *.raw 2>/dev/null || true + ;; + esac +} + +run_qemu_boot_test() { + local IMAGE_PATTERN="$1" + if [ -z "$IMAGE_PATTERN" ]; then + echo "Error: Image pattern not provided to run_qemu_boot_test" + return 1 + fi + + BIOS="/usr/share/OVMF/OVMF_CODE_4M.fd" + TIMEOUT=30 + SUCCESS_STRING="login:" + LOGFILE="qemu_serial.log" + + ORIGINAL_DIR=$(pwd) + # Find compressed raw image path using pattern, handle permission issues + FOUND_PATH=$(sudo -S find . -type f -name "*${IMAGE_PATTERN}*.raw.gz" 2>/dev/null | head -n 1) + if [ -n "$FOUND_PATH" ]; then + echo "Found compressed image at: $FOUND_PATH" + IMAGE_DIR=$(dirname "$FOUND_PATH") + + # Fix permissions for the image directory recursively to allow access + IMAGE_ROOT_DIR=$(echo "$IMAGE_DIR" | cut -d'/' -f2) # Get the root directory (workspace or tmp) + echo "Setting permissions recursively for ./$IMAGE_ROOT_DIR directory" + sudo chmod -R 777 "./$IMAGE_ROOT_DIR" + + cd "$IMAGE_DIR" + + # Extract the .raw.gz file + COMPRESSED_IMAGE=$(basename "$FOUND_PATH") + RAW_IMAGE="${COMPRESSED_IMAGE%.gz}" + echo "Extracting $COMPRESSED_IMAGE to $RAW_IMAGE..." + + # Check available disk space before extraction + AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}') + COMPRESSED_SIZE=$(stat -c%s "$COMPRESSED_IMAGE" 2>/dev/null || echo "0") + # Estimate uncompressed size (typically 4-6x larger for these images, being conservative) + ESTIMATED_SIZE=$((COMPRESSED_SIZE * 6 / 1024)) + + echo "Disk space check: Available=${AVAILABLE_SPACE}KB, Estimated needed=${ESTIMATED_SIZE}KB" + + # Always try aggressive cleanup first to ensure maximum space + echo "Performing aggressive cleanup before extraction..." + sudo rm -f *.raw 2>/dev/null || true + sudo rm -f /tmp/*.raw 2>/dev/null || true + sudo rm -rf ../../../cache/ 2>/dev/null || true + sudo rm -rf ../../../tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ../../../workspace/*/imagebuild/*/*.raw 2>/dev/null || true + + # Force filesystem sync and check space again + sync + AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}') + echo "Available space after cleanup: ${AVAILABLE_SPACE}KB" + + if [ "$AVAILABLE_SPACE" -lt "$ESTIMATED_SIZE" ]; then + echo "Warning: Still insufficient disk space after cleanup" + echo "Attempting extraction to /tmp with streaming..." + + # Check /tmp space + TMP_AVAILABLE=$(df /tmp | tail -1 | awk '{print $4}') + echo "/tmp available space: ${TMP_AVAILABLE}KB" + + if [ "$TMP_AVAILABLE" -gt "$ESTIMATED_SIZE" ]; then + TMP_RAW="/tmp/$RAW_IMAGE" + echo "Extracting to /tmp first..." + if gunzip -c "$COMPRESSED_IMAGE" > "$TMP_RAW"; then + echo "Successfully extracted to /tmp, moving to final location..." + if mv "$TMP_RAW" "$RAW_IMAGE"; then + echo "Successfully moved extracted image to current directory" + else + echo "Failed to move from /tmp, will try to use /tmp location directly" + ln -sf "$TMP_RAW" "$RAW_IMAGE" 2>/dev/null || cp "$TMP_RAW" "$RAW_IMAGE" + fi + else + echo "Failed to extract to /tmp" + rm -f "$TMP_RAW" 2>/dev/null || true + return 1 + fi + else + echo "ERROR: Insufficient space in both current directory and /tmp" + echo "Current: ${AVAILABLE_SPACE}KB, /tmp: ${TMP_AVAILABLE}KB, Needed: ${ESTIMATED_SIZE}KB" + return 1 + fi + else + echo "Sufficient space available, extracting directly..." + if ! gunzip -c "$COMPRESSED_IMAGE" > "$RAW_IMAGE"; then + echo "Direct extraction failed, cleaning up partial file..." + rm -f "$RAW_IMAGE" 2>/dev/null || true + return 1 + fi + fi + + if [ ! -f "$RAW_IMAGE" ]; then + echo "Failed to extract image!" + # Clean up any partially extracted files + sudo rm -f "$RAW_IMAGE" /tmp/"$RAW_IMAGE" 2>/dev/null || true + cd "$ORIGINAL_DIR" + return 1 + fi + + IMAGE="$RAW_IMAGE" + else + echo "Compressed raw image file matching pattern '*${IMAGE_PATTERN}*.raw.gz' not found!" + return 1 + fi + + + echo "Booting image: $IMAGE " + #create log file ,boot image into qemu , return the pass or fail after boot sucess + sudo bash -c " + LOGFILE=\"$LOGFILE\" + SUCCESS_STRING=\"$SUCCESS_STRING\" + IMAGE=\"$IMAGE\" + RAW_IMAGE=\"$RAW_IMAGE\" + ORIGINAL_DIR=\"$ORIGINAL_DIR\" + + touch \"\$LOGFILE\" && chmod 666 \"\$LOGFILE\" + nohup qemu-system-aarch64 \\ + -m 2048 \\ + -enable-kvm \\ + -cpu host \\ + -drive if=none,file=\"\$IMAGE\",format=raw,id=nvme0 \\ + -device nvme,drive=nvme0,serial=deadbeef \\ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \\ + -drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS_4M.fd \\ + -nographic \\ + -serial mon:stdio \\ + > \"\$LOGFILE\" 2>&1 & + + qemu_pid=\$! + echo \"QEMU launched as root with PID \$qemu_pid\" + echo \"Current working dir: \$(pwd)\" + + # Wait for SUCCESS_STRING or timeout + timeout=30 + elapsed=0 + while ! grep -q \"\$SUCCESS_STRING\" \"\$LOGFILE\" && [ \$elapsed -lt \$timeout ]; do + sleep 1 + elapsed=\$((elapsed + 1)) + done + echo \"\$elapsed\" + kill \$qemu_pid + cat \"\$LOGFILE\" + + if grep -q \"\$SUCCESS_STRING\" \"\$LOGFILE\"; then + echo \"Boot success!\" + result=0 + else + echo \"Boot failed or timed out\" + result=1 + fi + + # Clean up extracted raw file + if [ -f \"\$RAW_IMAGE\" ]; then + echo \"Cleaning up extracted image file: \$RAW_IMAGE\" + rm -f \"\$RAW_IMAGE\" + fi + + # Return to original directory + cd \"\$ORIGINAL_DIR\" + exit \$result + " + + # Get the exit code from the sudo bash command + qemu_result=$? + return $qemu_result +} + +check_disk_space() { + local min_required_gb=${1:-10} # Default 10GB minimum + local available_kb=$(df . | tail -1 | awk '{print $4}') + local available_gb=$((available_kb / 1024 / 1024)) + + echo "Available disk space: ${available_gb}GB" + + if [ "$available_gb" -lt "$min_required_gb" ]; then + echo "WARNING: Low disk space! Available: ${available_gb}GB, Recommended minimum: ${min_required_gb}GB" + echo "Attempting emergency cleanup..." + cleanup_image_files all + + # Recheck after cleanup + available_kb=$(df . | tail -1 | awk '{print $4}') + available_gb=$((available_kb / 1024 / 1024)) + echo "Available disk space after cleanup: ${available_gb}GB" + + if [ "$available_gb" -lt "$((min_required_gb / 2))" ]; then + echo "ERROR: Still critically low on disk space after cleanup!" + return 1 + fi + fi + return 0 +} + +git branch +#Build the OS Image Composer +echo "Building the OS Image Composer..." +echo "Generating binary with go build..." +go build ./cmd/os-image-composer + +build_elxr12_arm_raw_image() { + echo "Building ELXR12 raw Image. (using os-image-composer binary)" + # Ensure we're in the working directory before starting builds + echo "Ensuring we're in the working directory before starting builds..." + cd "$WORKING_DIR" + echo "Current working directory: $(pwd)" + + # Check disk space before building (require at least 12GB for ELXR12 images) + if ! check_disk_space 12; then + echo "Insufficient disk space for ELXR12 raw image build" + exit 1 + fi + + # Temporarily disable exit on error for the build command to capture output + set +e + output=$( sudo -S ./os-image-composer build image-templates/elxr12-aarch64-minimal-raw.yml 2>&1) + build_exit_code=$? + set -e + + # Check for the success message in the output + if [ $build_exit_code -eq 0 ] && echo "$output" | grep -q "image build completed successfully"; then + echo "ELXR12 raw Image build passed." + if [ "$RUN_QEMU_TESTS" = true ]; then + echo "Running QEMU boot test for ARM ELXR12 raw image..." + if run_qemu_boot_test "elxr12-aarch64-minimal"; then + echo "QEMU boot test PASSED for ELXR12 raw image" + else + echo "QEMU boot test FAILED for ELXR12 raw image" + exit 1 + fi + # Clean up after QEMU test to free space + cleanup_image_files raw + fi + else + echo "ARM ELXR12 raw Image build failed." + echo "Build output:" + echo "$output" + exit 1 # Exit with error if build fails + fi +} + +# Run the main function +build_elxr12_arm_raw_image diff --git a/scripts/build_elxr12_raw.sh b/scripts/build_elxr12_raw.sh index 800af6b8..3c3e8a33 100644 --- a/scripts/build_elxr12_raw.sh +++ b/scripts/build_elxr12_raw.sh @@ -292,4 +292,4 @@ build_elxr12_raw_image() { } # Run the main function -build_elxr12_raw_image \ No newline at end of file +build_elxr12_raw_image diff --git a/scripts/build_ubuntu24_arm_raw.sh b/scripts/build_ubuntu24_arm_raw.sh new file mode 100644 index 00000000..cac49ab9 --- /dev/null +++ b/scripts/build_ubuntu24_arm_raw.sh @@ -0,0 +1,296 @@ +#!/bin/bash +set -e + +# Parse command line arguments +RUN_QEMU_TESTS=false +WORKING_DIR="$(pwd)" + +while [[ $# -gt 0 ]]; do + case $1 in + --qemu-test|--with-qemu) + RUN_QEMU_TESTS=true + shift + ;; + --working-dir) + WORKING_DIR="$2" + shift 2 + ;; + -h|--help) + echo "Usage: $0 [--qemu-test|--with-qemu] [--working-dir DIR]" + echo " --qemu-test, --with-qemu Run QEMU boot tests after image build" + echo " --working-dir DIR Set the working directory" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac +done + +# Centralized cleanup function for image files +cleanup_image_files() { + local cleanup_type="${1:-all}" # Options: all, raw, extracted + + case "$cleanup_type" in + "raw") + echo "Cleaning up raw image files from build directories..." + sudo rm -rf ./tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ./workspace/*/imagebuild/*/*.raw 2>/dev/null || true + ;; + "extracted") + echo "Cleaning up extracted image files in current directory..." + rm -f *.raw 2>/dev/null || true + ;; + "all"|*) + echo "Cleaning up all temporary image files..." + sudo rm -rf ./tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ./workspace/*/imagebuild/*/*.raw 2>/dev/null || true + rm -f *.raw 2>/dev/null || true + ;; + esac +} + +run_qemu_boot_test() { + local IMAGE_PATTERN="$1" + if [ -z "$IMAGE_PATTERN" ]; then + echo "Error: Image pattern not provided to run_qemu_boot_test" + return 1 + fi + + BIOS="/usr/share/OVMF/OVMF_CODE_4M.fd" + TIMEOUT=30 + SUCCESS_STRING="login:" + LOGFILE="qemu_serial.log" + + ORIGINAL_DIR=$(pwd) + # Find compressed raw image path using pattern, handle permission issues + FOUND_PATH=$(sudo -S find . -type f -name "*${IMAGE_PATTERN}*.raw.gz" 2>/dev/null | head -n 1) + if [ -n "$FOUND_PATH" ]; then + echo "Found compressed image at: $FOUND_PATH" + IMAGE_DIR=$(dirname "$FOUND_PATH") + + # Fix permissions for the image directory recursively to allow access + IMAGE_ROOT_DIR=$(echo "$IMAGE_DIR" | cut -d'/' -f2) # Get the root directory (workspace or tmp) + echo "Setting permissions recursively for ./$IMAGE_ROOT_DIR directory" + sudo chmod -R 777 "./$IMAGE_ROOT_DIR" + + cd "$IMAGE_DIR" + + # Extract the .raw.gz file + COMPRESSED_IMAGE=$(basename "$FOUND_PATH") + RAW_IMAGE="${COMPRESSED_IMAGE%.gz}" + echo "Extracting $COMPRESSED_IMAGE to $RAW_IMAGE..." + + # Check available disk space before extraction + AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}') + COMPRESSED_SIZE=$(stat -c%s "$COMPRESSED_IMAGE" 2>/dev/null || echo "0") + # Estimate uncompressed size (typically 4-6x larger for these images, being conservative) + ESTIMATED_SIZE=$((COMPRESSED_SIZE * 6 / 1024)) + + echo "Disk space check: Available=${AVAILABLE_SPACE}KB, Estimated needed=${ESTIMATED_SIZE}KB" + + # Always try aggressive cleanup first to ensure maximum space + echo "Performing aggressive cleanup before extraction..." + sudo rm -f *.raw 2>/dev/null || true + sudo rm -f /tmp/*.raw 2>/dev/null || true + sudo rm -rf ../../../cache/ 2>/dev/null || true + sudo rm -rf ../../../tmp/*/imagebuild/*/*.raw 2>/dev/null || true + sudo rm -rf ../../../workspace/*/imagebuild/*/*.raw 2>/dev/null || true + + # Force filesystem sync and check space again + sync + AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}') + echo "Available space after cleanup: ${AVAILABLE_SPACE}KB" + + if [ "$AVAILABLE_SPACE" -lt "$ESTIMATED_SIZE" ]; then + echo "Warning: Still insufficient disk space after cleanup" + echo "Attempting extraction to /tmp with streaming..." + + # Check /tmp space + TMP_AVAILABLE=$(df /tmp | tail -1 | awk '{print $4}') + echo "/tmp available space: ${TMP_AVAILABLE}KB" + + if [ "$TMP_AVAILABLE" -gt "$ESTIMATED_SIZE" ]; then + TMP_RAW="/tmp/$RAW_IMAGE" + echo "Extracting to /tmp first..." + if gunzip -c "$COMPRESSED_IMAGE" > "$TMP_RAW"; then + echo "Successfully extracted to /tmp, moving to final location..." + if mv "$TMP_RAW" "$RAW_IMAGE"; then + echo "Successfully moved extracted image to current directory" + else + echo "Failed to move from /tmp, will try to use /tmp location directly" + ln -sf "$TMP_RAW" "$RAW_IMAGE" 2>/dev/null || cp "$TMP_RAW" "$RAW_IMAGE" + fi + else + echo "Failed to extract to /tmp" + rm -f "$TMP_RAW" 2>/dev/null || true + return 1 + fi + else + echo "ERROR: Insufficient space in both current directory and /tmp" + echo "Current: ${AVAILABLE_SPACE}KB, /tmp: ${TMP_AVAILABLE}KB, Needed: ${ESTIMATED_SIZE}KB" + return 1 + fi + else + echo "Sufficient space available, extracting directly..." + if ! gunzip -c "$COMPRESSED_IMAGE" > "$RAW_IMAGE"; then + echo "Direct extraction failed, cleaning up partial file..." + rm -f "$RAW_IMAGE" 2>/dev/null || true + return 1 + fi + fi + + if [ ! -f "$RAW_IMAGE" ]; then + echo "Failed to extract image!" + # Clean up any partially extracted files + sudo rm -f "$RAW_IMAGE" /tmp/"$RAW_IMAGE" 2>/dev/null || true + cd "$ORIGINAL_DIR" + return 1 + fi + + IMAGE="$RAW_IMAGE" + else + echo "Compressed raw image file matching pattern '*${IMAGE_PATTERN}*.raw.gz' not found!" + return 1 + fi + + + echo "Booting image: $IMAGE " + #create log file ,boot image into qemu , return the pass or fail after boot sucess + sudo bash -c " + LOGFILE=\"$LOGFILE\" + SUCCESS_STRING=\"$SUCCESS_STRING\" + IMAGE=\"$IMAGE\" + RAW_IMAGE=\"$RAW_IMAGE\" + ORIGINAL_DIR=\"$ORIGINAL_DIR\" + # -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \\ + # -drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS_4M.fd \\ + # -enable-kvm \\ + + touch \"\$LOGFILE\" && chmod 666 \"\$LOGFILE\" + nohup qemu-system-aarch64 \\ + -m 2048 \\ + -machine virt \\ + -cpu host \\ + -drive if=none,file=\"\$IMAGE\",format=raw,id=nvme0 \\ + -device nvme,drive=nvme0,serial=deadbeef \\ + -bios /usr/share/qemu/edk2-aarch64-code.fd \\ + -nographic \\ + -serial mon:stdio \\ + > \"\$LOGFILE\" 2>&1 & + + qemu_pid=\$! + echo \"QEMU launched as root with PID \$qemu_pid\" + echo \"Current working dir: \$(pwd)\" + + # Wait for SUCCESS_STRING or timeout + timeout=30 + elapsed=0 + while ! grep -q \"\$SUCCESS_STRING\" \"\$LOGFILE\" && [ \$elapsed -lt \$timeout ]; do + sleep 1 + elapsed=\$((elapsed + 1)) + done + echo \"\$elapsed\" + kill \$qemu_pid + cat \"\$LOGFILE\" + + if grep -q \"\$SUCCESS_STRING\" \"\$LOGFILE\"; then + echo \"Boot success!\" + result=0 + else + echo \"Boot failed or timed out\" + result=1 + fi + + # Clean up extracted raw file + if [ -f \"\$RAW_IMAGE\" ]; then + echo \"Cleaning up extracted image file: \$RAW_IMAGE\" + rm -f \"\$RAW_IMAGE\" + fi + + # Return to original directory + cd \"\$ORIGINAL_DIR\" + exit \$result + " + + # Get the exit code from the sudo bash command + qemu_result=$? + return $qemu_result +} + +check_disk_space() { + local min_required_gb=${1:-10} # Default 10GB minimum + local available_kb=$(df . | tail -1 | awk '{print $4}') + local available_gb=$((available_kb / 1024 / 1024)) + + echo "Available disk space: ${available_gb}GB" + + if [ "$available_gb" -lt "$min_required_gb" ]; then + echo "WARNING: Low disk space! Available: ${available_gb}GB, Recommended minimum: ${min_required_gb}GB" + echo "Attempting emergency cleanup..." + cleanup_image_files all + + # Recheck after cleanup + available_kb=$(df . | tail -1 | awk '{print $4}') + available_gb=$((available_kb / 1024 / 1024)) + echo "Available disk space after cleanup: ${available_gb}GB" + + if [ "$available_gb" -lt "$((min_required_gb / 2))" ]; then + echo "ERROR: Still critically low on disk space after cleanup!" + return 1 + fi + fi + return 0 +} + +git branch +#Build the OS Image Composer +echo "Building the OS Image Composer..." +echo "Generating binary with go build..." +go build ./cmd/os-image-composer + +build_ubuntu24_raw_image() { + echo "Building Ubuntu 24 raw Image. (using os-image-composer binary)" + # Ensure we're in the working directory before starting builds + echo "Ensuring we're in the working directory before starting builds..." + cd "$WORKING_DIR" + echo "Current working directory: $(pwd)" + + # Check disk space before building (require at least 12GB for Ubuntu 24 images) + if ! check_disk_space 12; then + echo "Insufficient disk space for Ubuntu 24 raw image build" + exit 1 + fi + + # Temporarily disable exit on error for the build command to capture output + set +e + output=$( sudo -S ./os-image-composer build image-templates/ubuntu24-aarch64-minimal-raw.yml 2>&1) + build_exit_code=$? + set -e + # Check for the success message in the output + if [ $build_exit_code -eq 0 ] && echo "$output" | grep -q "image build completed successfully"; then + echo "Ubuntu 24 raw Image build passed." + if [ "$RUN_QEMU_TESTS" = true ]; then + echo "Running QEMU boot test for Ubuntu 24 raw image..." + if run_qemu_boot_test "minimal-os-image-ubuntu-24.04"; then + echo "QEMU boot test PASSED for Ubuntu 24 raw image" + else + echo "QEMU boot test FAILED for Ubuntu 24 raw image" + exit 1 + fi + # Clean up after QEMU test to free space + cleanup_image_files raw + fi + else + echo "Ubuntu 24 raw Image build failed." + echo "Build output:" + echo "$output" + exit 1 # Exit with error if build fails + fi +} + +# Run the main function +build_ubuntu24_raw_image diff --git a/scripts/build_ubuntu24_raw.sh b/scripts/build_ubuntu24_raw.sh index 09e102ef..0626da7f 100644 --- a/scripts/build_ubuntu24_raw.sh +++ b/scripts/build_ubuntu24_raw.sh @@ -292,4 +292,4 @@ build_ubuntu24_raw_image() { } # Run the main function -build_ubuntu24_raw_image \ No newline at end of file +build_ubuntu24_raw_image