diff --git a/.github/workflows/iso_builder_test.yaml b/.github/workflows/iso_builder_test.yaml new file mode 100644 index 00000000..96365d66 --- /dev/null +++ b/.github/workflows/iso_builder_test.yaml @@ -0,0 +1,99 @@ +name: Iso Builder Test Worfklow + +on: + pull_request: + branches: + - main + paths: + - './training/iso-builder/**' + - '!training/iso-builder/README.md' + - '.github/workflows/iso_builder_test.yaml' + push: + branches: + - main + paths: + - './training/iso-builder/**' + - '!training/iso-builder/README.md' + - '.github/workflows/iso_builder_test.yaml' + + workflow_dispatch: + +env: + REGISTRY: quay.io + REGISTRY_ORG: ai-lab + +jobs: + iso-builder-build-and-test: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + strategy: + matrix: + include: + - platforms: linux/amd64,linux/arm64 + parent_image_registry: quay.io + parent_image_org: centos-bootc + parent_image_name: centos-bootc + parent_image_tag: stream9 + iso_image_name: iso-builder-centos-bootc + iso_download_url: https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-boot.iso + iso_name: CentOS-Stream-9-latest-x86_64-boot.iso + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v4.1.4 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Generate an SSH key + id: ssh-keygen + run: | + ssh-keygen -t ed25519 -C "ai-lab-recipes@redhat.com" -N "" -f id_rsa + echo "sshpubkey=$(cat id_rsa.pub | xargs)" >> $GITHUB_OUTPUT + + - name: Download the ISO + working-directory: ./training/iso-builder/ + run: | + make centos-iso-download + + - name: Build Image + id: build_image + working-directory: ./training/iso-builder/ + run: | + make image FROM=${{ matrix.parent_image_registry }}/${{ matrix.parent_image_org }}/${{ matrix.parent_image_name }}:${{ matrix.parent_image_tag }} IMAGE_NAME=${{ matrix.iso_image_name }} IMAGE_TAG=${{ matrix.parent_image_tag }} + echo "embedded_image=$(echo '${{ matrix.parent_image_registry }}/${{ matrix.parent_image_org }}/${{ matrix.parent_image_name }}:${{ matrix.parent_image_tag }}' | xargs)" >> $GITHUB_OUTPUT + echo "produced_image=$(echo '${{env.REGISTRY}}/${{env.REGISTRY_ORG}}/${{ matrix.iso_image_name }}:${{ matrix.parent_image_tag }}' | xargs)" >> $GITHUB_OUTPUT + + - name: Run the Image + working-directory: ./training/iso-builder/ + run: | + make iso IMAGE=${{ steps.build_image.outputs.produced_image }} EMBED_IMAGE=${{ steps.build_image.outputs.embedded_image }} ORIGINAL_ISO="ISOs/${{ matrix.iso_name }}" SSHKEY="${{ steps.ssh-keygen.outputs.sshpubkey }}" CONTAINER_TOOL_EXTRA_ARGS=--pull=never + + # - name: Setup tmate session + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3.18 + # timeout-minutes: 10 + # with: + # detached: false + # limit-access-to-actor: true + + # - name: Publish Job Results to Slack + # id: slack + # if: always() + # uses: slackapi/slack-github-action@v1.26.0 + # with: + # payload: | + # { + # "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + # } + # env: + # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.gitignore b/.gitignore index ca818ba0..1b82f734 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ recipes/common/bin/* training/cloud/examples training/instructlab/instructlab vector_dbs/milvus/volumes/milvus/* +training/iso-builder/ISOs/* +!training/iso-builder/ISOs/.gitkeep diff --git a/training/iso-builder/Containerfile b/training/iso-builder/Containerfile new file mode 100644 index 00000000..00fff9d6 --- /dev/null +++ b/training/iso-builder/Containerfile @@ -0,0 +1,7 @@ +FROM quay.io/centos/centos:stream9 + +RUN dnf install -y skopeo buildah lorax +ADD iso-builder.sh /root/iso-builder.sh +ADD ks.template /root/ks.template + +ENTRYPOINT ["/bin/bash", "/root/iso-builder.sh"] diff --git a/training/iso-builder/ISOs/.gitkeep b/training/iso-builder/ISOs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/training/iso-builder/Makefile b/training/iso-builder/Makefile new file mode 100644 index 00000000..86ab9548 --- /dev/null +++ b/training/iso-builder/Makefile @@ -0,0 +1,57 @@ +FROM ?= + +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab +IMAGE_NAME ?= iso-builder +IMAGE_TAG ?= latest +IMAGE ?= ${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}:${IMAGE_TAG} + +CONTAINER_TOOL ?= podman +CONTAINER_TOOL_EXTRA_ARGS ?= + +EMBED_IMAGE ?= +ORIGINAL_ISO ?= +SSHKEY ?= +OUTPUT_DIR ?= ./iso + +.PHONY: image iso centos-iso-download +image: + "${CONTAINER_TOOL}" build \ + --file Containerfile \ + --tag "${IMAGE}" \ + $(FROM:%=--from=%) \ + $(MODEL_REPO:%=--build-arg MODEL_REPO=%) \ + $(MODEL_PATH:%=--build-arg MODEL_PATH=%) \ + ${CONTAINER_TOOL_EXTRA_ARGS} + +iso: + @if [ "${EMBED_IMAGE}" = "" ]; then\ + echo "EMBED_IMAGE is required";\ + exit 1;\ + fi + @if [ "${ORIGINAL_ISO}" = "" ]; then\ + echo "ORIGINAL_ISO is required";\ + exit 1;\ + fi + @if [ "${SSHKEY}" = "" ]; then\ + echo "SSHKEY is required";\ + exit 1;\ + fi + + mkdir -p "${OUTPUT_DIR}" + + "${CONTAINER_TOOL}" run \ + --rm \ + --volume $(shell readlink -f ${ORIGINAL_ISO}):/root/original.iso:ro \ + --volume ${OUTPUT_DIR}:/output \ + --volume $(shell ${CONTAINER_TOOL} system info --format json | jq -r '.store.graphRoot'):/var/lib/containers/storage \ + ${CONTAINER_TOOL_EXTRA_ARGS} \ + "${IMAGE}" \ + "${EMBED_IMAGE}" \ + /root/ks.template \ + "${SSHKEY}" \ + /root/original.iso \ + "/output/$(shell basename ${EMBED_IMAGE}).iso" + +centos-iso-download: + curl -sL https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-boot.iso -o ISOs/CentOS-Stream-9-latest-x86_64-boot.iso diff --git a/training/iso-builder/README.md b/training/iso-builder/README.md new file mode 100644 index 00000000..f4339f0d --- /dev/null +++ b/training/iso-builder/README.md @@ -0,0 +1,52 @@ +Installation iso builder +--- + +This directory provides an easy way to build installation `iso` images with a container and a kickstart +embedded. + +# Makefile targets + +| Target | Description | +|-------------|---------------------------------------------------------| +| image | Build the container | +| iso | Create bootable installation iso | + +# Makefile variables + +| Variable | Description | Default | +|---------------------------|-------------------------------------------------|---------------------------------------------| +| FROM | Overrides the base image for the Containerfile | `quay.io/centos/centos:stream9` | +| REGISTRY | Container Registry for storing container images | `quay.io` | +| REGISTRY_ORG | Container Registry organization | `ai-lab` | +| IMAGE_NAME | Container image name | `iso-builder` | +| IMAGE_TAG | Container image tag | `latest` | +| CONTAINER_TOOL | Container tool used for build | `podman` | +| CONTAINER_TOOL_EXTRA_ARGS | Container tool extra arguments | ` ` | +| EMBED_IMAGE | The container image to embed in the iso | *Required* | +| ORIGINAL_ISO | Path to the base installation iso | *Required* | +| SSHKEY | The SSH public key for the root account | *Required* | +| OUTPUT_DIR | Path where to store the new installation iso | `./iso` | + +# How to build the container image + +The first step is simple, the `iso-builder` container image needs to be build: + +``` +make image +``` + +This is based on CentOS Stream by default, for Red Hat Enterprise Linux you would use the `FROM` variable: + +``` +make image FROM=registry.access.redhat.com/ubi9/ubi:latest +``` + +# How to build the iso image + +First, you need to have a recent Anaconda based installation iso. The latest CentOS Stream 9, Fedora 40 or Red Hat Enterprise Linux 9 +installation isos will work. + +``` +make iso EMBED_IMAGE= ORIGINAL_ISO=/path/to/original.iso SSHKEY="my public ssh-key" +``` + diff --git a/training/iso-builder/iso-builder.sh b/training/iso-builder/iso-builder.sh new file mode 100644 index 00000000..42d255c9 --- /dev/null +++ b/training/iso-builder/iso-builder.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -e + +if [ "$#" -ne 5 ]; then + echo This script takes precisely 5 arguments: + echo " - container image to be embedded in iso" + echo " - kickstart template to use" + echo " - public ssh key" + echo " - original iso path" + echo " - output path for install iso" + exit 1 +fi + +CONTAINER_IMAGE="$1" +KS_TEMPLATE="$2" +SSHKEY="$3" +ORIGINAL_ISO="$4" +NEW_ISO="$5" + +if [ ! -f "${ORIGINAL_ISO}" ]; then + echo Cannot find original iso file + echo You can download RHEL 9.4 iso from https://developers.redhat.com/products/rhel/download + echo or CentOS Stream9 from https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-boot.iso + exit 1 +fi + +if [ ! -f "${KS_TEMPLATE}" ]; then + echo Cannot find kickstart template "${KS_TEMPLATE}" + echo For details on kickstart, see https://anaconda-installer.readthedocs.io/en/latest/kickstart.html + exit 1 +fi + +TEMPDIR=$(mktemp --directory) +cd "${TEMPDIR}" + +echo Populate kickstart +sed "s^SSHKEY^${SSHKEY}^g" "${KS_TEMPLATE}" > local.ks +cat local.ks + +echo Unpack container image +mkdir -p "${TEMPDIR}/container" + +buildah images "${CONTAINER_IMAGE}" || buildah pull "${CONTAINER_IMAGE=}" +skopeo copy "containers-storage:${CONTAINER_IMAGE}" "oci:${TEMPDIR}/container/" + +echo Pack iso to "${NEW_ISO}" +mkksiso --ks local.ks \ + --add container/ \ + "${ORIGINAL_ISO}" "${NEW_ISO}" + +echo Cleanup tempdir +#rm -rf "${TEMPDIR}" +echo Done diff --git a/training/iso-builder/ks.template b/training/iso-builder/ks.template new file mode 100644 index 00000000..b988e055 --- /dev/null +++ b/training/iso-builder/ks.template @@ -0,0 +1,11 @@ +text +network --bootproto=dhcp --device=link --activate +clearpart --all --initlabel --disklabel=gpt +reqpart --add-boot +part / --grow --fstype xfs +ostreecontainer --url=/run/install/repo/container --transport=oci --no-signature-verification +firewall --disabled +services --enabled=sshd +rootpw --iscrypted locked +sshkey --username root "SSHKEY" +reboot