Several visualization fixes and tweaks #15830
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). | |
| # All rights reserved. | |
| # | |
| # SPDX-License-Identifier: BSD-3-Clause | |
| name: Python Dependency Licenses Check | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| license-check: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| filter: tree:0 | |
| # - name: Install jq | |
| # run: sudo apt-get update && sudo apt-get install -y jq | |
| - name: Clean up disk space | |
| run: | | |
| # Remove pre-installed tools | |
| rm -rf /opt/hostedtoolcache | |
| rm -rf /usr/share/dotnet | |
| rm -rf /opt/ghc | |
| sudo rm -rf /usr/local/lib/android | |
| rm -rf /usr/share/swift | |
| rm -rf /usr/local/share/boost | |
| sudo rm -rf /usr/local/.ghcup | |
| sudo rm -rf /usr/local/lib/node_modules | |
| sudo rm -rf /usr/local/share/chromium | |
| sudo rm -rf /usr/local/share/powershell | |
| # Clean apt cache | |
| sudo apt-get clean | |
| sudo rm -rf /var/lib/apt/lists/* | |
| # Docker cleanup | |
| docker container prune -f | |
| docker image prune -af | |
| docker volume prune -f | |
| docker system prune -af | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v6 | |
| with: | |
| python-version: '3.12' | |
| enable-cache: true | |
| - name: Install dependencies with uv | |
| env: | |
| OMNI_KIT_ACCEPT_EULA: yes | |
| ACCEPT_EULA: Y | |
| ISAACSIM_ACCEPT_EULA: YES | |
| run: | | |
| # ``all`` covers every backend (Isaac Sim included), RL library, and visualizer. | |
| # ``rlinf`` and ``mimic`` are outside ``all``, so name them to keep them scanned. | |
| # No extras conflict, so this is a single resolution -- Isaac Sim no longer needs | |
| # an imperative install after the sync. | |
| uv sync --extra all --extra test --extra rlinf --extra mimic | |
| # ``[tool.uv.pip] prerelease = "allow"`` lets unpinned tools float onto | |
| # prereleases. pip-licenses 6.0.0a1 reports an empty License where 5.x reports | |
| # ``UNKNOWN``, which license-exceptions.json keys on, and joins multi-license | |
| # strings unsorted. Cap below 6.0 until 6.x ships stable and is re-reviewed. | |
| uv pip install "pip-licenses<6.0" pipdeptree \ | |
| -r tools/template/requirements.txt | |
| # Put the venv on PATH for later steps. | |
| echo "$PWD/.venv/bin" >> "$GITHUB_PATH" | |
| # Optional: Print the license report for visibility | |
| - name: Print License Report | |
| run: pip-licenses --from=mixed --format=markdown | |
| # Print pipdeptree | |
| - name: Print pipdeptree | |
| run: pipdeptree | |
| - name: Check licenses against whitelist and exceptions | |
| run: | | |
| # Define the whitelist of allowed licenses | |
| ALLOWED_LICENSES="MIT Apache BSD ISC zlib" | |
| # Load the exceptions list from the exceptions.json file | |
| EXCEPTIONS_FILE=".github/workflows/license-exceptions.json" | |
| # Initialize counter for failed packages | |
| FAILED_PACKAGES=0 | |
| # Get the list of installed packages and their licenses | |
| pip-licenses --from=mixed --format=json > licenses.json | |
| # Check the output of pip-licenses to ensure it is valid JSON | |
| if ! jq empty licenses.json; then | |
| echo "ERROR: Failed to parse pip-licenses output. Exiting..." | |
| exit 1 | |
| fi | |
| # Split ALLOWED_LICENSES into individual words | |
| IFS=' ' read -r -a allowed_licenses <<< "$ALLOWED_LICENSES" | |
| # Loop through the installed packages and their licenses | |
| for pkg in $(jq -r '.[].Name' licenses.json); do | |
| # Skip packages starting with nvidia (case-insensitive) | |
| if [[ "${pkg,,}" == nvidia* ]]; then | |
| continue | |
| fi | |
| LICENSE=$(jq -r --arg pkg "$pkg" '.[] | select(.Name == $pkg) | .License' licenses.json) | |
| # Check if any of the allowed licenses are a substring of the package's license | |
| match_found=false | |
| for allowed_license in "${allowed_licenses[@]}"; do | |
| if [[ "$LICENSE" == *"$allowed_license"* ]]; then | |
| match_found=true | |
| break | |
| fi | |
| done | |
| if [ "$match_found" = false ]; then | |
| # Check if the package is in the exceptions list | |
| EXCEPTION=$(jq -r --arg pkg "$pkg" --arg license "$LICENSE" \ | |
| '.[] | select( | |
| .package == $pkg | |
| and ((.package_type == null) or (.package_type == "Python")) | |
| )' "$EXCEPTIONS_FILE") | |
| # If the package is in the exceptions list | |
| if [ -n "$EXCEPTION" ]; then | |
| # If the license is provided in the exceptions list, check the license | |
| EXCEPTION_LICENSE=$(echo "$EXCEPTION" | jq -r '.license') | |
| # echo "Comparing licenses for $pkg:" | |
| # echo " EXCEPTION_LICENSE='${EXCEPTION_LICENSE}' (len=${#EXCEPTION_LICENSE})" | |
| # echo " LICENSE='${LICENSE}' (len=${#LICENSE})" | |
| # If the exceptions list has a license and doesn't match the current license | |
| if [ "$EXCEPTION_LICENSE" != "null" ] && [ "$EXCEPTION_LICENSE" != "$LICENSE" ]; then | |
| echo "ERROR: $pkg has license: $LICENSE" | |
| FAILED_PACKAGES=$((FAILED_PACKAGES + 1)) # Increment the counter | |
| fi | |
| else | |
| # If the package is not in the exceptions list | |
| echo "ERROR: $pkg has license: $LICENSE" | |
| FAILED_PACKAGES=$((FAILED_PACKAGES + 1)) # Increment the counter | |
| fi | |
| fi | |
| done | |
| # After all packages are processed, check if there were any errors | |
| if [ "$FAILED_PACKAGES" -gt 0 ]; then | |
| echo "ERROR: $FAILED_PACKAGES packages were flagged." | |
| exit 1 # Fail the build | |
| else | |
| echo "All packages were checked." | |
| fi | |
| docker-license-check: | |
| name: Docker Dependency Licenses Check | |
| runs-on: [self-hosted, gpu] | |
| timeout-minutes: 90 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| lfs: true | |
| - name: Load image configuration | |
| id: config | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| config=.github/workflows/config.yaml | |
| # The tag may carry a digest, so strip the key rather than splitting on every colon. | |
| base_image="$(awk '$1 == "isaacsim_image_name:" { sub(/^[^:]+:[[:space:]]*/, ""); print; exit }' "$config")" | |
| base_tag="$(awk '$1 == "isaacsim_image_tag:" { sub(/^[^:]+:[[:space:]]*/, ""); print; exit }' "$config")" | |
| if [[ -z "$base_image" || -z "$base_tag" ]]; then | |
| echo "::error::Missing Isaac Sim image configuration in $config" | |
| exit 1 | |
| fi | |
| image_tag="isaac-lab-ci:pr-${{ github.event.pull_request.number }}-${{ github.sha }}" | |
| echo "base_image=${base_image}:${base_tag}" >> "$GITHUB_OUTPUT" | |
| echo "base_image_name=${base_image}" >> "$GITHUB_OUTPUT" | |
| echo "base_image_tag=${base_tag}" >> "$GITHUB_OUTPUT" | |
| echo "image_tag=${image_tag}" >> "$GITHUB_OUTPUT" | |
| - name: Build or fetch Isaac Lab image | |
| uses: ./.github/actions/ecr-build-push-pull | |
| with: | |
| image-tag: ${{ steps.config.outputs.image_tag }} | |
| isaacsim-base-image: ${{ steps.config.outputs.base_image_name }} | |
| isaacsim-version: ${{ steps.config.outputs.base_image_tag }} | |
| dockerfile-path: docker/Dockerfile.base | |
| cache-tag: cache-base | |
| - name: Log in to NGC for the base image | |
| uses: ./.github/actions/_lib/setup-docker-config | |
| - name: Ensure Isaac Lab image is available locally | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| image_tag="${{ steps.config.outputs.image_tag }}" | |
| if docker image inspect "$image_tag" >/dev/null 2>&1; then | |
| exit 0 | |
| fi | |
| if [[ -z "${ECR_IMAGE:-}" ]]; then | |
| echo "::error::Isaac Lab image is neither local nor available from ECR" | |
| exit 1 | |
| fi | |
| registry="${ECR_IMAGE%%/*}" | |
| region="${registry#*.dkr.ecr.}" | |
| region="${region%.amazonaws.com}" | |
| aws ecr get-login-password --region "$region" \ | |
| | docker login --username AWS --password-stdin "$registry" | |
| docker pull "$ECR_IMAGE" | |
| docker tag "$ECR_IMAGE" "$image_tag" | |
| - name: Pull Isaac Sim base image | |
| shell: bash | |
| run: docker pull "${{ steps.config.outputs.base_image }}" | |
| - name: Scan Isaac Sim base dependency licenses | |
| uses: aquasecurity/trivy-action@v0.36.0 | |
| env: | |
| TRIVY_PKG_TYPES: os,library | |
| TRIVY_SKIP_VERSION_CHECK: true | |
| with: | |
| scan-type: image | |
| image-ref: ${{ steps.config.outputs.base_image }} | |
| scanners: license | |
| format: json | |
| output: trivy-isaacsim-licenses.json | |
| exit-code: '0' | |
| severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL | |
| timeout: 30m | |
| - name: Scan Isaac Lab dependency licenses | |
| uses: aquasecurity/trivy-action@v0.36.0 | |
| env: | |
| TRIVY_PKG_TYPES: os,library | |
| TRIVY_SKIP_VERSION_CHECK: true | |
| with: | |
| scan-type: image | |
| image-ref: ${{ steps.config.outputs.image_tag }} | |
| scanners: license | |
| format: json | |
| output: trivy-isaaclab-licenses.json | |
| exit-code: '0' | |
| severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL | |
| timeout: 30m | |
| skip-setup-trivy: true | |
| - name: Verify standalone FFmpeg is not distributed | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| image_tag="${{ steps.config.outputs.image_tag }}" | |
| docker run --rm --entrypoint bash "$image_tag" -lc ' | |
| set -euo pipefail | |
| system_ffmpeg="$(command -v ffmpeg || true)" | |
| if [[ -n "$system_ffmpeg" ]]; then | |
| echo "Standalone system FFmpeg must not be distributed: $system_ffmpeg" >&2 | |
| "$system_ffmpeg" -version >&2 || true | |
| exit 1 | |
| fi | |
| python_output="$(./isaaclab.sh -p -c \ | |
| "import importlib.util; print(\"present\" if importlib.util.find_spec(\"imageio_ffmpeg\") else \"absent\")" \ | |
| 2>&1)" | |
| printf "%s\n" "$python_output" | |
| imageio_ffmpeg_state="$( | |
| printf "%s\n" "$python_output" \ | |
| | grep -E "^(present|absent)$" | tail -n 1 || true | |
| )" | |
| if [[ -z "$imageio_ffmpeg_state" ]]; then | |
| echo "Could not determine whether imageio-ffmpeg is installed" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$imageio_ffmpeg_state" == "present" ]]; then | |
| echo "imageio-ffmpeg and its standalone FFmpeg binary must remain opt-in" >&2 | |
| exit 1 | |
| fi | |
| echo "No standalone system or imageio-ffmpeg executable is distributed" | |
| ' | tee bundled-ffmpeg-inspection.txt | |
| : > bundled-components.tsv | |
| - name: Check licenses against policy and exceptions | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| CURRENT_REPORT=trivy-isaaclab-licenses.json | |
| BASE_REPORT=trivy-isaacsim-licenses.json | |
| EXCEPTIONS_FILE=.github/workflows/license-exceptions.json | |
| FINDINGS_DELIMITER=$'\x1f' | |
| ALLOWED_LICENSES=( | |
| "0bsd" | |
| "apache-2.0" | |
| "apache-2.0 and cnri-python" | |
| "apache-2.0 and mit" | |
| "apache-2.0 with llvm-exception" | |
| "bsd-0-clause" | |
| "bsd-1-clause" | |
| "bsd-2-clause" | |
| "bsd-2-clause and apache-2.0 with llvm-exception" | |
| "bsd-2-clause-netbsd" | |
| "bsd-3-clause" | |
| "bsd-3-clause and 0bsd and mit and zlib and cc0-1.0" | |
| "bsd-4-clause" | |
| "bsd-4-clause-uc" | |
| "cc0-1.0" | |
| "isc" | |
| "mit" | |
| "mit or apache-2.0" | |
| "mit/x11" | |
| "x11" | |
| "zlib" | |
| ) | |
| for report in "$CURRENT_REPORT" "$BASE_REPORT"; do | |
| if ! jq empty "$report"; then | |
| echo "::error::Failed to parse Trivy report: $report" | |
| exit 1 | |
| fi | |
| if ! jq -e 'any(.Results[]; .Class == "license")' "$report" >/dev/null; then | |
| echo "::error::Trivy report contains no license results: $report" | |
| exit 1 | |
| fi | |
| done | |
| # Refuse to pass if Trivy did not inventory both dependency types in | |
| # the final image. Loose-file licenses are intentionally excluded. | |
| for package_class in os-pkgs lang-pkgs; do | |
| if ! jq -e --arg class "$package_class" \ | |
| 'any(.Results[]; .Class == $class)' "$CURRENT_REPORT" >/dev/null; then | |
| echo "::error::Trivy report is missing the $package_class inventory" | |
| exit 1 | |
| fi | |
| done | |
| extract_findings() { | |
| jq -r ' | |
| ( | |
| [ | |
| .Results[] | |
| | select(.Class == "os-pkgs" or .Class == "lang-pkgs") | |
| | ( | |
| if .Class == "os-pkgs" | |
| then "OS Packages" | |
| else .Target | |
| end | |
| ) as $package_type | |
| | .Packages[]? | |
| | { | |
| key: ( | |
| ($package_type + "\u0000" + .Name) | |
| | ascii_downcase | |
| ), | |
| value: (.Version // "") | |
| } | |
| ] | |
| | from_entries | |
| ) as $versions | |
| | | |
| .Results[] | |
| | select(.Class == "license") | |
| | .Target as $package_type | |
| | .Licenses[]? | |
| | select((.PkgName // "") != "") | |
| | [ | |
| $package_type, | |
| .PkgName, | |
| ( | |
| $versions[ | |
| (($package_type + "\u0000" + .PkgName) | ascii_downcase) | |
| ] // "" | |
| ), | |
| .Name, | |
| .Category, | |
| .Severity, | |
| "" | |
| ] | |
| | map(tostring) | |
| | join("\u001f") | |
| ' "$1" | sort -u | |
| } | |
| extract_findings "$CURRENT_REPORT" > current-findings.tsv | |
| if [ ! -s current-findings.tsv ]; then | |
| echo "::error::Trivy found no package licenses in the Isaac Lab image" | |
| exit 1 | |
| fi | |
| extract_findings "$BASE_REPORT" > base-findings.tsv | |
| if [ ! -s base-findings.tsv ]; then | |
| echo "::error::Trivy found no package licenses in the Isaac Sim base image" | |
| exit 1 | |
| fi | |
| tr '\t' '\037' < bundled-components.tsv >> current-findings.tsv | |
| sort -u -o current-findings.tsv current-findings.tsv | |
| cut -d "$FINDINGS_DELIMITER" -f1-4 base-findings.tsv \ | |
| | tr '[:upper:]' '[:lower:]' > base-identities.tsv | |
| TOTAL_FINDINGS=$(wc -l < current-findings.tsv) | |
| INHERITED_FINDINGS=0 | |
| ALLOWED_FINDINGS=0 | |
| NVIDIA_FINDINGS=0 | |
| EXCEPTED_FINDINGS=0 | |
| FAILED_FINDINGS=0 | |
| : > license-violations.md | |
| : > reviewed-license-declarations.md | |
| record_violation() { | |
| local reason="$1" | |
| FAILED_FINDINGS=$((FAILED_FINDINGS + 1)) | |
| printf '| %s | %s | %s | %s | %s | %s | %s |\n' \ | |
| "$package_type" "$package" "$version" "$license" "$category" \ | |
| "$severity" "$reason" >> license-violations.md | |
| } | |
| while IFS="$FINDINGS_DELIMITER" read -r \ | |
| package_type package version license category severity detected_linkage; do | |
| if [[ -z "$version" ]]; then | |
| record_violation "Package is missing from Trivy's version inventory" | |
| continue | |
| fi | |
| identity=$(printf '%s\x1f%s\x1f%s\x1f%s' \ | |
| "$package_type" "$package" "$version" "$license" \ | |
| | tr '[:upper:]' '[:lower:]') | |
| if grep -Fqx -- "$identity" base-identities.tsv; then | |
| INHERITED_FINDINGS=$((INHERITED_FINDINGS + 1)) | |
| continue | |
| fi | |
| if [[ "${package,,}" == nvidia* ]]; then | |
| NVIDIA_FINDINGS=$((NVIDIA_FINDINGS + 1)) | |
| continue | |
| fi | |
| license_is_allowed=false | |
| for allowed_license in "${ALLOWED_LICENSES[@]}"; do | |
| if [[ "${license,,}" == "$allowed_license" ]]; then | |
| license_is_allowed=true | |
| break | |
| fi | |
| done | |
| if "$license_is_allowed"; then | |
| ALLOWED_FINDINGS=$((ALLOWED_FINDINGS + 1)) | |
| continue | |
| fi | |
| exception="$( | |
| jq -c \ | |
| --arg package "$package" \ | |
| --arg license "$license" \ | |
| --arg package_type "$package_type" ' | |
| first( | |
| .[] | |
| | select( | |
| (.package | ascii_downcase) == ($package | ascii_downcase) | |
| and ( | |
| (.package_type == $package_type) | |
| or ($package_type == "Python" and .package_type == null) | |
| ) | |
| and ( | |
| (.license == null) | |
| or ((.license | ascii_downcase) == ($license | ascii_downcase)) | |
| or any(.license_aliases[]?; | |
| ascii_downcase == ($license | ascii_downcase) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ' "$EXCEPTIONS_FILE" | |
| )" | |
| if [[ -z "$exception" ]]; then | |
| record_violation "No reviewed exception" | |
| continue | |
| fi | |
| usage="$(jq -r '.usage // ""' <<<"$exception")" | |
| interaction="$(jq -r '.interaction // ""' <<<"$exception")" | |
| declared_linkage="$(jq -r '.linkage // ""' <<<"$exception")" | |
| selected_license="$(jq -r '.selected_license // ""' <<<"$exception")" | |
| effective_license="${selected_license:-$license}" | |
| linkage="${detected_linkage:-$declared_linkage}" | |
| if [[ -z "$usage" ]]; then | |
| record_violation "Reviewed exception is missing usage" | |
| continue | |
| fi | |
| case "$interaction" in | |
| standalone_process|standalone_process_dependency|not_loaded|build_input|\ | |
| same_process_dynamic|same_process_static) | |
| ;; | |
| *) | |
| record_violation "Reviewed exception has missing or invalid interaction" | |
| continue | |
| ;; | |
| esac | |
| normalized_license="${effective_license,,}" | |
| is_lgpl=false | |
| is_gpl=false | |
| if [[ "$normalized_license" == *"lgpl"* || | |
| "$normalized_license" == *"lesser general public license"* ]]; then | |
| is_lgpl=true | |
| elif [[ "$normalized_license" == *"gpl"* || | |
| "$normalized_license" == *"general public license"* ]]; then | |
| is_gpl=true | |
| fi | |
| if "$is_gpl" && | |
| [[ "$interaction" == same_process_dynamic || | |
| "$interaction" == same_process_static ]]; then | |
| record_violation "GPL component is declared in the Isaac Lab process" | |
| continue | |
| fi | |
| if "$is_lgpl" && [[ "$linkage" == "static" ]]; then | |
| relinking_materials="$(jq -r '.relinking_materials // ""' <<<"$exception")" | |
| relinking_instructions="$(jq -r '.relinking_instructions // ""' <<<"$exception")" | |
| if [[ -z "$relinking_materials" || -z "$relinking_instructions" ]]; then | |
| record_violation "Statically linked LGPL component lacks relinking materials" | |
| continue | |
| fi | |
| fi | |
| EXCEPTED_FINDINGS=$((EXCEPTED_FINDINGS + 1)) | |
| printf '| %s | %s | %s | %s | %s | %s |\n' \ | |
| "$package_type" "$package" "$version" "$effective_license" \ | |
| "$usage" "$interaction" >> reviewed-license-declarations.md | |
| done < current-findings.tsv | |
| { | |
| echo "## Docker dependency license check" | |
| echo | |
| if [ "$FAILED_FINDINGS" -eq 0 ]; then | |
| echo "**Passed.** No unapproved dependency licenses were introduced by the Isaac Lab image." | |
| else | |
| echo "**Failed.** Found $FAILED_FINDINGS unapproved dependency license finding(s)." | |
| fi | |
| echo | |
| echo "| Result | Count |" | |
| echo "|---|---:|" | |
| echo "| Total package-license findings | $TOTAL_FINDINGS |" | |
| echo "| Inherited at the same version from Isaac Sim base image | $INHERITED_FINDINGS |" | |
| echo "| Allowed permissive licenses | $ALLOWED_FINDINGS |" | |
| echo "| NVIDIA package policy | $NVIDIA_FINDINGS |" | |
| echo "| Reviewed exceptions | $EXCEPTED_FINDINGS |" | |
| echo "| Violations | $FAILED_FINDINGS |" | |
| if [ -s reviewed-license-declarations.md ]; then | |
| echo | |
| echo "### Reviewed dependency declarations" | |
| echo | |
| echo "| Package type | Package | Version | Effective license | Usage | Interaction |" | |
| echo "|---|---|---|---|---|---|" | |
| sort -u reviewed-license-declarations.md | |
| fi | |
| if [ "$FAILED_FINDINGS" -gt 0 ]; then | |
| echo | |
| echo "### Unapproved licenses" | |
| echo | |
| echo "| Package type | Package | Version | License | Category | Severity | Reason |" | |
| echo "|---|---|---|---|---|---|---|" | |
| cat license-violations.md | |
| fi | |
| } | tee docker-license-summary.md | |
| cat docker-license-summary.md >> "$GITHUB_STEP_SUMMARY" | |
| if [ "$FAILED_FINDINGS" -gt 0 ]; then | |
| exit 1 | |
| fi | |
| - name: Upload license reports | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: docker-license-reports | |
| path: | | |
| trivy-isaacsim-licenses.json | |
| trivy-isaaclab-licenses.json | |
| bundled-components.tsv | |
| bundled-ffmpeg-inspection.txt | |
| docker-license-summary.md | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| - name: Clean up Docker resources | |
| if: always() | |
| shell: bash | |
| run: | | |
| image_tag="${{ steps.config.outputs.image_tag }}" | |
| docker image rm -f "$image_tag" >/dev/null 2>&1 || true | |
| if [ -n "${ECR_IMAGE:-}" ] && [ "$ECR_IMAGE" != "$image_tag" ]; then | |
| docker image rm -f "$ECR_IMAGE" >/dev/null 2>&1 || true | |
| fi | |
| if [ -n "${DOCKER_CONFIG:-}" ] && [ -d "${DOCKER_CONFIG}" ]; then | |
| rm -rf "${DOCKER_CONFIG}" | |
| else | |
| echo "::warning::Temporary DOCKER_CONFIG directory was not available for cleanup" | |
| fi |