From fd7669719d2d40faf8fe1b5e7b0ac0a1671d8b50 Mon Sep 17 00:00:00 2001 From: alexafshar Date: Thu, 3 Sep 2026 13:33:45 -0400 Subject: [PATCH] Build universal Linux bundle and tag release bundle architectures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docker-multiarch.yml | 68 +++++++++++++-- ...ubuntu.yml => executable-bundle-linux.yml} | 50 +++++------ .github/workflows/executable-bundle-macos.yml | 19 ++-- .../workflows/executable-bundle-windows.yml | 16 ++-- Dockerfile-rhel-runtime | 87 +++++++++++++++++++ VERSION | 2 +- backend/Dockerfile-rhel | 66 ++++++++++++++ input/thresholds/DefaultThresholds.json | 2 +- 8 files changed, 259 insertions(+), 51 deletions(-) rename .github/workflows/{executable-bundle-ubuntu.yml => executable-bundle-linux.yml} (60%) create mode 100644 Dockerfile-rhel-runtime create mode 100644 backend/Dockerfile-rhel diff --git a/.github/workflows/docker-multiarch.yml b/.github/workflows/docker-multiarch.yml index 2aab2ba..4b20529 100644 --- a/.github/workflows/docker-multiarch.yml +++ b/.github/workflows/docker-multiarch.yml @@ -3,8 +3,8 @@ name: docker-multiarch on: push: tags: + - '[0-9]*.[0-9]*.[0-9]*' - 'v*.*.*' - - '[0-9]+.[0-9]+.[0-9]+*' workflow_dispatch: env: @@ -40,7 +40,7 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # Generate ONLY platform-specific tags here. - # We do NOT push 'latest' or the bare version 'v1.2.3' in this job. + # We do NOT push 'latest' or the bare version '1.2.3' in this job. # Those are reserved for the final merge step to avoid race conditions. tags: | type=semver,pattern={{version}},suffix=-linux @@ -58,6 +58,54 @@ jobs: provenance: false outputs: type=image,rewrite-timestamp=false + build-rhel-linux-multiarch: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ubi_major: [8, 9] + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version from tag + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{version}},suffix=-rhel${{ matrix.ubi_major }}-linux + type=ref,event=branch,suffix=-rhel${{ matrix.ubi_major }}-linux + + - name: Build and push RHEL ${{ matrix.ubi_major }} Linux image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile-rhel-runtime + build-args: | + UBI_MAJOR=${{ matrix.ubi_major }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + provenance: false + outputs: type=image,rewrite-timestamp=false + # Windows build is separate and less frequent, keeping it here but isolated build-windows: if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'workflow_dispatch' @@ -91,11 +139,21 @@ jobs: run: | echo "IMAGE_NAME_LOWER=${{ env.IMAGE_NAME }}".ToLower() >> $env:GITHUB_ENV + - name: Ensure Docker daemon is running (Windows) + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + if (-not (Get-Service -Name docker -ErrorAction SilentlyContinue)) { + throw 'Docker service was not found on the Windows runner.' + } + Start-Service docker + docker version + # Standardize on -windows suffix for consistent intermediary tags # Ensure IMAGE_NAME is lowercase primarily for the manual docker build command # Note: We use the default docker builder here, not buildx, because setup-buildx-action # fails to start the buildkit container on Windows runners (which run in Windows container mode). - # The default 'docker build' command works fine for Windows images. + # The default 'docker build' command works fine for Windows images once the daemon is running. - name: Build and push Windows image run: | docker build -f Dockerfile-windows -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:${{ steps.meta.outputs.version }}-windows . @@ -103,7 +161,7 @@ jobs: # Manifest creation: This is the ONLY place where user-facing tags (v1.2.3, latest) are pushed. merge-manifest: - needs: [build-linux-multiarch, build-windows] + needs: [build-linux-multiarch, build-rhel-linux-multiarch, build-windows] if: success() && ((github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || github.event_name == 'workflow_dispatch') runs-on: ubuntu-latest permissions: @@ -142,7 +200,7 @@ jobs: # In the build-windows job, we push: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}-windows WINDOWS_IMAGE="${IMAGE_ROOT}:${VERSION}-windows" - # Tags to generate (v1.8.1 and latest) + # Tags to generate (1.8.1 and latest) TARGET_TAG="${IMAGE_ROOT}:${VERSION}" LATEST_TAG="${IMAGE_ROOT}:latest" diff --git a/.github/workflows/executable-bundle-ubuntu.yml b/.github/workflows/executable-bundle-linux.yml similarity index 60% rename from .github/workflows/executable-bundle-ubuntu.yml rename to .github/workflows/executable-bundle-linux.yml index 421529a..87a4177 100644 --- a/.github/workflows/executable-bundle-ubuntu.yml +++ b/.github/workflows/executable-bundle-linux.yml @@ -1,4 +1,4 @@ -name: executable-bundle-ubuntu +name: executable-bundle-linux on: release: @@ -9,56 +9,48 @@ permissions: contents: write jobs: - build-ubuntu-linux: - name: build ubuntu linux binary + build-linux: + name: Build Linux Executable Bundle runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install dependencies and Build Bundle (Ubuntu) - id: get_bundle_name # Added an ID to this step to capture output + - name: Install dependencies and Build Bundle (Linux) + id: get_bundle_name run: | # Ensure repository owner is lowercase for Docker tag REPO_OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - IMAGE_TAG="ghcr.io/${REPO_OWNER}/config-assessment-tool-ubuntu-linux:latest" + IMAGE_TAG="ghcr.io/${REPO_OWNER}/config-assessment-tool-build:latest" - # build image which builds the bundle for Ubuntu 20.x+ - # IMPORTANT: Use the new Dockerfile for Ubuntu - make build-image DOCKERFILE=backend/Dockerfile-ubuntu DOCKER_IMAGE_TAG=${IMAGE_TAG} - echo "finished building docker image with bundle included" - # copy bundle out of built image + # Build on a RHEL 8-compatible base so the bundle works on both + # RHEL 8.x and RHEL 9.x hosts (and Ubuntu 20+). + # Use NO_CACHE=true to avoid buildkit caching issues + make build-image DOCKERFILE=backend/Dockerfile-rhel DOCKER_IMAGE_TAG=${IMAGE_TAG} NO_CACHE=true + echo "finished building docker image with Linux bundle included" + + # Copy bundle out of built image # The `find` command is more robust for dynamically named files tarball_path=$(docker run --rm ${IMAGE_TAG} find /root/config-assessment-tool/dist/ -name "*.tgz") echo "copying bundle out of image using $tarball_path" docker cp $(docker create --name temp_image ${IMAGE_TAG}):$tarball_path ./ && docker rm temp_image echo "finished copying bundle onto hosted runner at $(pwd)" echo "files in current directory $(ls -altr)" - # Capture the exact bundle filename - ORIGINAL_BUNDLE_NAME=$(ls -1 *.tgz | head -n 1) - - # Inject -executable- into the filename - # Expecting format like config-assessment-tool-ubuntu-linux-1.8.0-beta.1.tgz - # converting to config-assessment-tool-ubuntu-linux-executable-1.8.0-beta.1.tgz - # OR just append -executable before extension if regex is too complex - - # Using sed to append -executable before the .tgz extension - NEW_BUNDLE_NAME=$(echo "$ORIGINAL_BUNDLE_NAME" | sed 's/\.tgz$/-executable.tgz/') - # fallback if substitution didn't happen (e.g. name format changed) - if [[ "$NEW_BUNDLE_NAME" == "$ORIGINAL_BUNDLE_NAME" ]]; then - NEW_BUNDLE_NAME="${ORIGINAL_BUNDLE_NAME%.tgz}-executable.tgz" + ORIGINAL_BUNDLE_NAME=$(ls -1 *.tgz | head -n 1) + VERSION="${{ github.ref_name }}" + if [[ -z "$VERSION" ]]; then + VERSION="$(cat VERSION)" fi - + NEW_BUNDLE_NAME="config-assessment-tool-linux-${VERSION}-x86_64-executable.tgz" mv "$ORIGINAL_BUNDLE_NAME" "$NEW_BUNDLE_NAME" - echo "Renamed $ORIGINAL_BUNDLE_NAME to $NEW_BUNDLE_NAME" BUNDLE_NAME="$NEW_BUNDLE_NAME" echo "BUNDLE_NAME=$BUNDLE_NAME" >> $GITHUB_ENV # Set as an environment variable echo "bundle_name=$BUNDLE_NAME" >> $GITHUB_OUTPUT # Set as step output for clarity - - name: Release the bundle for Ubuntu Linux + - name: Release the bundle for Linux if: startsWith(github.ref, 'refs/tags/') env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -69,10 +61,10 @@ jobs: fi gh release create ${{ github.ref_name }} ${{ env.BUNDLE_NAME }} --generate-notes --title "${{ github.ref_name }}" $PRERELEASE_FLAG || gh release upload ${{ github.ref_name }} ${{ env.BUNDLE_NAME }} --clobber - - name: Keep workflow artifact within the current workflow namespace (Ubuntu) + - name: Keep workflow artifact within the current workflow namespace (Linux) uses: actions/upload-artifact@v4 with: name: ${{ env.BUNDLE_NAME }} # Use the exact bundle name for the artifact path: ./${{ env.BUNDLE_NAME }} # Specify the full path to the bundle compression-level: 0 # The .tgz is already compressed, no need to compress again - if-no-files-found: ignore \ No newline at end of file + if-no-files-found: ignore diff --git a/.github/workflows/executable-bundle-macos.yml b/.github/workflows/executable-bundle-macos.yml index f31da5a..b3bcaa2 100644 --- a/.github/workflows/executable-bundle-macos.yml +++ b/.github/workflows/executable-bundle-macos.yml @@ -45,16 +45,12 @@ jobs: echo "PyInstaller created directory: ${BUNDLE_DIR_NAME}" # Define the output bundle filename - # Consistent naming: config-assessment-tool-macos--executable.zip - # We check if 'macosx' is present and replace it with 'macos', then append '-executable' - - if [[ "$BUNDLE_DIR_NAME" == *"macosx"* ]]; then - BASE_NAME="${BUNDLE_DIR_NAME/macosx/macos}" - else - BASE_NAME="$BUNDLE_DIR_NAME" + # Consistent naming: config-assessment-tool-macos--arm64-executable.zip + VERSION="${{ github.ref_name }}" + if [[ -z "$VERSION" || ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-].*)?$ ]]; then + VERSION="$(cat ../VERSION)" fi - - BUNDLE_FILENAME="${BASE_NAME}-executable.zip" + BUNDLE_FILENAME="config-assessment-tool-macos-${VERSION}-arm64-executable.zip" echo "Bundle filename: ${BUNDLE_FILENAME}" @@ -64,15 +60,18 @@ jobs: # Set output variable for subsequent steps echo "::set-output name=bundle_name::${BUNDLE_FILENAME}" + echo "::set-output name=release_tag::${VERSION}" echo "Current directory contents after zipping:" ls -altr - name: Release the bundle for macOS uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' with: + tag_name: ${{ steps.build_bundle.outputs.release_tag }} files: dist/${{ steps.build_bundle.outputs.bundle_name }} token: ${{ secrets.GITHUB_TOKEN }} + overwrite_files: true - name: Keep workflow artifact within the current workflow namespace uses: actions/upload-artifact@v4 diff --git a/.github/workflows/executable-bundle-windows.yml b/.github/workflows/executable-bundle-windows.yml index 79a3bb4..d49a89d 100644 --- a/.github/workflows/executable-bundle-windows.yml +++ b/.github/workflows/executable-bundle-windows.yml @@ -55,9 +55,13 @@ jobs: $sourcePathFor7z = Join-Path -Path "./dist/appdynamics" -ChildPath $pyinstallerOutputDirName # Define the desired name for the output zip file - # format: config-assessment-tool-windows--executable.zip - # We append '-executable' to the end of the base name. - $zipFileName = "${pyinstallerOutputDirName}-executable.zip" + # format: config-assessment-tool-windows--x86_64-executable.zip + $version = "${{ github.ref_name }}" + if ([string]::IsNullOrWhiteSpace($version) -or ($version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+([.-].*)?$')) { + $version = Get-Content -Path "VERSION" -Raw + $version = $version.Trim() + } + $zipFileName = "config-assessment-tool-windows-${version}-x86_64-executable.zip" # Define the full path where the zip file should be created (in the ./dist folder) $zipFilePath = Join-Path -Path "./dist" -ChildPath $zipFileName @@ -69,16 +73,18 @@ jobs: # Set the output variable with the correct zip file name # This variable will be used by subsequent steps to locate the file echo "bundle_name=$zipFileName" >> $env:GITHUB_OUTPUT + echo "release_tag=$version" >> $env:GITHUB_OUTPUT - name: Release the bundle for Windows uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' with: # Reference the output variable correctly using ${{ steps..outputs. }} + tag_name: ${{ steps.install_it.outputs.release_tag }} files: dist/${{ steps.install_it.outputs.bundle_name }} token: ${{ secrets.GITHUB_TOKEN }} overwrite_files: true - prerelease: ${{ contains(github.ref_name, '-') }} + prerelease: ${{ contains(steps.install_it.outputs.release_tag, '-') }} - name: Keep workflow artifact within the current workflow namespace uses: actions/upload-artifact@v4 diff --git a/Dockerfile-rhel-runtime b/Dockerfile-rhel-runtime new file mode 100644 index 0000000..dfe92cd --- /dev/null +++ b/Dockerfile-rhel-runtime @@ -0,0 +1,87 @@ +ARG UBI_MAJOR=8 + +FROM redhat/ubi${UBI_MAJOR}:latest AS python-build + +ENV PYTHON_VERSION=3.12.3 +ENV LD_LIBRARY_PATH=/usr/local/lib +ENV PATH=/usr/local/bin:${PATH} + +RUN dnf install -y \ + gcc \ + gcc-c++ \ + make \ + wget \ + tar \ + gzip \ + findutils \ + which \ + zlib-devel \ + bzip2 \ + bzip2-devel \ + ncurses-devel \ + sqlite \ + sqlite-devel \ + openssl-devel \ + libffi-devel \ + xz \ + xz-devel \ + ca-certificates \ + && dnf clean all + +WORKDIR /root + +RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tar.xz && \ + tar -xf Python-${PYTHON_VERSION}.tar.xz + +WORKDIR /root/Python-${PYTHON_VERSION} + +RUN ./configure --prefix=/usr/local --with-system-ffi --enable-shared && \ + make -j"$(nproc)" && \ + make altinstall + +WORKDIR /app + +COPY Pipfile . +COPY Pipfile.lock . + +RUN /usr/local/bin/python3.12 -m pip install --upgrade pip pipenv && \ + PIPENV_VENV_IN_PROJECT=1 /usr/local/bin/python3.12 -m pipenv install --deploy + +FROM redhat/ubi${UBI_MAJOR}:latest AS runtime + +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONFAULTHANDLER=1 +ENV PYTHONPATH=/app:/app/backend +ENV LD_LIBRARY_PATH=/usr/local/lib +ENV PATH=/app/.venv/bin:/usr/local/bin:${PATH} + +RUN dnf install -y \ + bzip2-libs \ + libffi \ + ncurses-libs \ + openssl-libs \ + sqlite-libs \ + xz-libs \ + zlib \ + && dnf clean all + +WORKDIR /app + +COPY --from=python-build /usr/local /usr/local +COPY --from=python-build /app/.venv /app/.venv + +COPY backend /app/backend +COPY frontend /app/frontend +COPY plugins /app/plugins +COPY bin /app/bin +COPY VERSION . +COPY entrypoint.sh /app/entrypoint.sh + +RUN chmod +x /app/entrypoint.sh + +EXPOSE 8501 + +ENTRYPOINT ["/app/entrypoint.sh"] +CMD [] diff --git a/VERSION b/VERSION index 53adb84..a7ee35a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.2 +1.8.3 diff --git a/backend/Dockerfile-rhel b/backend/Dockerfile-rhel new file mode 100644 index 0000000..978fa98 --- /dev/null +++ b/backend/Dockerfile-rhel @@ -0,0 +1,66 @@ +# Build executable bundle on a RHEL 8-compatible base so the resulting +# Linux executable works on both RHEL 8.x and newer glibc-based distros. +FROM redhat/ubi8:latest + +ENV PYTHON_VERSION=3.12.3 +ENV LD_LIBRARY_PATH=/usr/local/lib +ENV PATH=/usr/local/bin:/root/.local/bin:${PATH} + +WORKDIR /root + +RUN dnf install -y \ + gcc \ + gcc-c++ \ + make \ + wget \ + tar \ + gzip \ + git \ + zip \ + findutils \ + which \ + zlib-devel \ + bzip2 \ + bzip2-devel \ + ncurses-devel \ + sqlite \ + sqlite-devel \ + openssl-devel \ + libffi-devel \ + xz \ + xz-devel \ + ca-certificates \ + && dnf clean all + +RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tar.xz && \ + tar -xf Python-${PYTHON_VERSION}.tar.xz + +WORKDIR /root/Python-${PYTHON_VERSION} + +RUN ./configure --prefix=/usr/local --with-system-ffi --enable-shared && \ + make -j"$(nproc)" && \ + make altinstall + +WORKDIR /root/config-assessment-tool + +COPY backend ./backend +COPY input ./input +COPY plugins ./plugins +COPY frontend ./frontend +COPY bin ./bin +COPY VERSION . +COPY Pipfile . +COPY Pipfile.lock . +COPY Makefile . + +RUN /usr/local/bin/python3.12 -m pip install --upgrade pip && \ + make exec-bundle PYTHON=/usr/local/bin/python3.12 + +WORKDIR /root + +RUN ARCH=$(uname -m) && \ + tar -zcvf /root/config-assessment-tool/dist/config-assessment-tool-linux-$(cat /root/config-assessment-tool/VERSION)-rhel-${ARCH}.tgz \ + -C /root/config-assessment-tool/dist/appdynamics . && \ + ls -altrh /root/config-assessment-tool/dist/ + +CMD ["find", "/root/config-assessment-tool/dist/", "-name", "*.tgz"] diff --git a/input/thresholds/DefaultThresholds.json b/input/thresholds/DefaultThresholds.json index 3d905d5..4ab507a 100644 --- a/input/thresholds/DefaultThresholds.json +++ b/input/thresholds/DefaultThresholds.json @@ -1,5 +1,5 @@ { - "version": "1.8.2", + "version": "1.8.3", "apm": { "AppAgentsAPM": { "platinum": {