diff --git a/.github/scripts/detect-new-fmas-in-pr.sh b/.github/scripts/detect-new-fmas-in-pr.sh new file mode 100755 index 0000000000..4107a376e2 --- /dev/null +++ b/.github/scripts/detect-new-fmas-in-pr.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +# Script to detect changed/new maintained apps in a PR +# This script compares the PR branch with the base branch to find: +# 1. New apps added to apps.json +# 2. Apps with changed manifest files + +set -euo pipefail + +# Get repository root +REPO_ROOT="${GITHUB_WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +APPS_JSON="${REPO_ROOT}/ee/maintained-apps/outputs/apps.json" +OUTPUTS_DIR="${REPO_ROOT}/ee/maintained-apps/outputs" + +# Base branch (usually main or the PR's base branch) +# In GitHub Actions, GITHUB_BASE_REF is set for pull_request events +BASE_BRANCH="${GITHUB_BASE_REF:-main}" +# Use origin/ prefix for remote branch reference +BASE_BRANCH_REF="origin/${BASE_BRANCH}" + +# Check if jq is available +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed" >&2 + exit 1 +fi + +# Function to extract app slugs from apps.json +extract_slugs() { + local apps_file="$1" + if [ ! -f "$apps_file" ]; then + echo "" + return + fi + jq -r '.apps[].slug' "$apps_file" | sort +} + +# Function to extract app slugs from changed manifest files +extract_slugs_from_changed_manifests() { + local changed_files="$1" + local slugs=() + + while IFS= read -r file; do + # Extract slug from path like: outputs/app-name/darwin.json or outputs/app-name/windows.json + if [[ "$file" =~ outputs/([^/]+)/(darwin|windows)\.json$ ]]; then + app_name="${BASH_REMATCH[1]}" + platform="${BASH_REMATCH[2]}" + slug="${app_name}/${platform}" + slugs+=("$slug") + fi + done <<< "$changed_files" + + # Remove duplicates and sort + printf '%s\n' "${slugs[@]}" | sort -u +} + +# Get changed files in outputs directory +echo "Detecting changed files in outputs directory..." +echo "Comparing HEAD with ${BASE_BRANCH_REF}..." +# Use merge-base to find the common ancestor for comparison +MERGE_BASE=$(git merge-base "${BASE_BRANCH_REF}" HEAD 2>/dev/null || echo "${BASE_BRANCH_REF}") +CHANGED_FILES=$(git diff --name-only "$MERGE_BASE" HEAD -- "ee/maintained-apps/outputs/" 2>/dev/null || echo "") + +# Extract slugs from changed manifest files +CHANGED_MANIFEST_SLUGS=$(extract_slugs_from_changed_manifests "$CHANGED_FILES") + +# Get current apps.json slugs +CURRENT_SLUGS=$(extract_slugs "$APPS_JSON") + +# Get base branch apps.json slugs +echo "Fetching base branch apps.json from ${MERGE_BASE}..." +BASE_APPS_JSON=$(git show "${MERGE_BASE}:ee/maintained-apps/outputs/apps.json" 2>/dev/null || echo "") +BASE_SLUGS="" +if [ -n "$BASE_APPS_JSON" ]; then + BASE_SLUGS=$(echo "$BASE_APPS_JSON" | jq -r '.apps[].slug' | sort) +else + echo "Warning: Could not find apps.json in base branch, treating all current apps as new" +fi + +# Find new slugs in apps.json +NEW_SLUGS=$(comm -13 <(echo "$BASE_SLUGS" || echo "") <(echo "$CURRENT_SLUGS" || echo "") || echo "") + +# Combine all changed slugs (from manifest changes and new apps) +ALL_CHANGED_SLUGS=$(printf '%s\n' "$CHANGED_MANIFEST_SLUGS" "$NEW_SLUGS" | grep -v '^$' | sort -u) + +# Output results +if [ -z "$ALL_CHANGED_SLUGS" ]; then + echo "No changed apps detected." + echo "CHANGED_APPS=" >> "$GITHUB_OUTPUT" + echo "HAS_CHANGES=false" >> "$GITHUB_OUTPUT" + exit 0 +fi + +echo "Detected changed apps:" +echo "$ALL_CHANGED_SLUGS" | while read -r slug; do + echo " - $slug" +done + +# Output as JSON array for GitHub Actions +CHANGED_APPS_JSON=$(echo "$ALL_CHANGED_SLUGS" | jq -R -s -c 'split("\n") | map(select(length > 0))') + +echo "CHANGED_APPS=$CHANGED_APPS_JSON" >> "$GITHUB_OUTPUT" +echo "HAS_CHANGES=true" >> "$GITHUB_OUTPUT" + + diff --git a/.github/scripts/filter-apps-json.sh b/.github/scripts/filter-apps-json.sh new file mode 100755 index 0000000000..9cbaef3421 --- /dev/null +++ b/.github/scripts/filter-apps-json.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Script to filter apps.json to only include specified app slugs +# Usage: filter-apps-json.sh + +set -euo pipefail + +# Get repository root +REPO_ROOT="${GITHUB_WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +APPS_JSON="${REPO_ROOT}/ee/maintained-apps/outputs/apps.json" + +# Check if jq is available +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed" >&2 + exit 1 +fi + +# Parse arguments +SLUGS_JSON="$1" +OUTPUT_FILE="$2" + +if [ -z "$SLUGS_JSON" ] || [ "$SLUGS_JSON" == "[]" ] || [ "$SLUGS_JSON" == "null" ]; then + echo "No slugs provided, creating empty apps.json" + echo '{"version": 2, "apps": []}' > "$OUTPUT_FILE" + exit 0 +fi + +# Read the original apps.json +if [ ! -f "$APPS_JSON" ]; then + echo "Error: apps.json not found at $APPS_JSON" >&2 + exit 1 +fi + +# Filter apps.json to only include the specified slugs +jq --argjson slugs "$SLUGS_JSON" '.apps = (.apps | map(select(.slug as $slug | $slugs | index($slug) != null)))' "$APPS_JSON" > "$OUTPUT_FILE" + +echo "Filtered apps.json created with $(jq '.apps | length' "$OUTPUT_FILE") app(s)" + diff --git a/.github/workflows/test-fma-darwin-pr-only.yml b/.github/workflows/test-fma-darwin-pr-only.yml new file mode 100644 index 0000000000..a66d9710b6 --- /dev/null +++ b/.github/workflows/test-fma-darwin-pr-only.yml @@ -0,0 +1,145 @@ +name: Test Fleet Maintained Apps - Darwin (PR Only) + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - ee/maintained-apps/inputs/** + - ee/maintained-apps/outputs/** + - cmd/maintained-apps/validate/** + workflow_dispatch: # Manual trigger + inputs: + log_level: + description: "Log level (debug, info, warn, error)" + required: false + default: "info" + type: choice + options: + - debug + - info + - warn + - error + +env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + test-fma-pr-only: + env: + LOG_LEVEL: ${{ github.event.inputs.log_level || 'info' }} + runs-on: macos-latest + + steps: + - name: Checkout Fleet + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: fleetdm/fleet + fetch-depth: 0 # Need full history to compare with base branch + ref: ${{ github.ref }} + path: fleet + + - name: Setup Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version-file: "fleet/go.mod" + + - name: Fetch base branch + run: | + cd fleet + BASE_BRANCH="${{ github.event.pull_request.base.ref || github.base_ref || 'main' }}" + echo "Fetching base branch: $BASE_BRANCH" + git fetch origin "$BASE_BRANCH:$BASE_BRANCH" || true + shell: bash + + - name: Detect changed apps + id: detect-changed + env: + GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref || 'main' }} + run: | + cd fleet + export GITHUB_WORKSPACE="$PWD" + .github/scripts/detect-new-fmas-in-pr.sh + shell: bash + + - name: Check if there are changes + id: check-changes + run: | + if [ "${{ steps.detect-changed.outputs.HAS_CHANGES }}" == "true" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "Changed apps detected: ${{ steps.detect-changed.outputs.CHANGED_APPS }}" + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "No changed apps detected, skipping validation" + fi + + - name: Check if there are Darwin apps + id: check-darwin-apps + run: | + if [ "${{ steps.check-changes.outputs.has_changes }}" != "true" ]; then + echo "has_darwin_apps=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Filter changed apps to only include darwin platform + DARWIN_SLUGS=$(echo '${{ steps.detect-changed.outputs.CHANGED_APPS }}' | jq -r '.[] | select(endswith("/darwin"))') + + if [ -z "$DARWIN_SLUGS" ]; then + echo "has_darwin_apps=false" >> $GITHUB_OUTPUT + echo "No darwin apps changed, skipping Darwin workflow" + else + echo "has_darwin_apps=true" >> $GITHUB_OUTPUT + echo "Darwin apps detected:" + echo "$DARWIN_SLUGS" | while read -r slug; do + echo " - $slug" + done + fi + shell: bash + + - name: Install osquery mac + if: steps.check-darwin-apps.outputs.has_darwin_apps == 'true' + run: | + echo "Runner architecture: $(uname -m)" + curl -L -o osquery.tar.gz "https://github.com/osquery/osquery/releases/download/5.18.1/osquery-5.18.1_1.macos_arm64.tar.gz" + tar -xzf osquery.tar.gz + sudo cp -r opt / + sudo cp -r private / + sudo ln -sf /opt/osquery/lib/osquery.app/Contents/MacOS/osqueryd /usr/local/bin/osqueryi + sudo ln -sf /opt/osquery/lib/osquery.app/Contents/Resources/osqueryctl /usr/local/bin/osqueryctl + + - name: Remove pre-installed google chrome mac + if: steps.check-darwin-apps.outputs.has_darwin_apps == 'true' + run: | + ls /Applications | grep -i "Chrome" + find /Applications -name "*Chrome*.app" -type d | while read app; + do + echo "Removing $app..." + sudo rm -rf "$app" + done + + - name: Filter apps.json and verify changed apps + if: steps.check-darwin-apps.outputs.has_darwin_apps == 'true' + run: | + cd fleet + # Set GITHUB_WORKSPACE to current directory so scripts can find files + export GITHUB_WORKSPACE="$PWD" + + # Filter changed apps to only include darwin platform + DARWIN_SLUGS=$(echo '${{ steps.detect-changed.outputs.CHANGED_APPS }}' | jq -r '.[] | select(endswith("/darwin"))') + DARWIN_SLUGS_JSON=$(echo "$DARWIN_SLUGS" | jq -R -s -c 'split("\n") | map(select(length > 0))') + + # Backup original apps.json + cp ee/maintained-apps/outputs/apps.json ee/maintained-apps/outputs/apps.json.backup + + # Create filtered apps.json + FILTERED_APPS_JSON=$(mktemp) + .github/scripts/filter-apps-json.sh "$DARWIN_SLUGS_JSON" "$FILTERED_APPS_JSON" + + # Replace apps.json with filtered version + mv "$FILTERED_APPS_JSON" ee/maintained-apps/outputs/apps.json + + # Run validation + ls /Applications + sudo -E go run ./cmd/maintained-apps/validate + + # Restore original apps.json + mv ee/maintained-apps/outputs/apps.json.backup ee/maintained-apps/outputs/apps.json diff --git a/.github/workflows/test-fma-darwin.yml b/.github/workflows/test-fma-darwin.yml index 87d614498b..6d587ecba8 100644 --- a/.github/workflows/test-fma-darwin.yml +++ b/.github/workflows/test-fma-darwin.yml @@ -1,12 +1,8 @@ name: Test Fleet Maintained Apps - Darwin on: - pull_request: - types: [opened, synchronize, reopened] - paths: - - ee/maintained-apps/inputs/** - - ee/maintained-apps/outputs/** - - cmd/maintained-apps/validate/** + # Note: PR triggers removed - use test-fma-darwin-pr-only.yml for PRs + # This workflow is kept for manual testing of all FMAs via workflow_dispatch workflow_dispatch: # Manual trigger inputs: log_level: diff --git a/.github/workflows/test-fma-windows-pr-only.yml b/.github/workflows/test-fma-windows-pr-only.yml new file mode 100644 index 0000000000..04dc6342e8 --- /dev/null +++ b/.github/workflows/test-fma-windows-pr-only.yml @@ -0,0 +1,176 @@ +name: Test Fleet Maintained Apps - Windows (PR Only) + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - ee/maintained-apps/inputs/** + - ee/maintained-apps/outputs/** + - cmd/maintained-apps/validate/** + workflow_dispatch: # Manual trigger + inputs: + log_level: + description: "Log level (debug, info, warn, error)" + required: false + default: "info" + type: choice + options: + - debug + - info + - warn + - error + +env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + test-fma-pr-only: + env: + LOG_LEVEL: ${{ github.event.inputs.log_level || 'info' }} + runs-on: windows-latest + + steps: + - name: Checkout Fleet + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: fleetdm/fleet + fetch-depth: 0 # Need full history to compare with base branch + ref: ${{ github.ref }} + path: fleet + + - name: Setup Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version-file: "fleet/go.mod" + + - name: Setup Git for base branch comparison + run: | + cd fleet + git config --global --add safe.directory $PWD + shell: pwsh + + - name: Fetch base branch + run: | + cd fleet + $baseBranch = "${{ github.event.pull_request.base.ref || github.base_ref || 'main' }}" + Write-Host "Fetching base branch: $baseBranch" + git fetch origin "$baseBranch`:$baseBranch" || exit 0 + shell: pwsh + + - name: Detect changed apps + id: detect-changed + env: + GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref || 'main' }} + run: | + cd fleet + $env:GITHUB_WORKSPACE = (Get-Location).Path + bash .github/scripts/detect-new-fmas-in-pr.sh + shell: pwsh + + - name: Check if there are changes + id: check-changes + run: | + if ("${{ steps.detect-changed.outputs.HAS_CHANGES }}" -eq "true") { + "has_changes=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + Write-Host "Changed apps detected: ${{ steps.detect-changed.outputs.CHANGED_APPS }}" + } else { + "has_changes=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + Write-Host "No changed apps detected, skipping validation" + } + shell: pwsh + + - name: Check if there are Windows apps + id: check-windows-apps + run: | + if ("${{ steps.check-changes.outputs.has_changes }}" -ne "true") { + "has_windows_apps=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + exit 0 + } + + # Filter changed apps to only include windows platform + $changedAppsJson = '${{ steps.detect-changed.outputs.CHANGED_APPS }}' + $windowsSlugs = ($changedAppsJson | ConvertFrom-Json | Where-Object { $_ -like "*/windows" }) + + if ($null -eq $windowsSlugs -or $windowsSlugs.Count -eq 0) { + "has_windows_apps=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + Write-Host "No windows apps changed, skipping Windows workflow" + } else { + "has_windows_apps=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + Write-Host "Windows apps detected:" + $windowsSlugs | ForEach-Object { Write-Host " - $_" } + } + shell: pwsh + + - name: Install osquery windows + if: steps.check-windows-apps.outputs.has_windows_apps == 'true' + run: | + Write-Host "Runner architecture: $env:PROCESSOR_ARCHITECTURE" + curl -L -o osquery.zip "https://github.com/osquery/osquery/releases/download/5.18.1/osquery-5.18.1.windows_x86_64.zip" + Expand-Archive -Path osquery.zip -DestinationPath osquery + Get-ChildItem -Recurse osquery | Where-Object { $_.Name -like "*osquery*" -and $_.Extension -eq ".exe" } + $osqueryPath = (Get-ChildItem -Recurse osquery | Where-Object { $_.Name -eq "osqueryi.exe" }).Directory.FullName + echo "Adding to PATH: $osqueryPath" + echo $osqueryPath | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + shell: pwsh + + - name: Remove pre-installed google chrome + if: steps.check-windows-apps.outputs.has_windows_apps == 'true' + run: | + Write-Host "Listing all installed packages containing 'Chrome':" + Get-Package | Where-Object { $_.Name -like "*Chrome*" } | ForEach-Object { + Write-Host " - $($_.Name) (Version: $($_.Version))" + } + + $uninstallPath = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | Where-Object { $_.DisplayName -like "*Google Chrome*" } | Select-Object -ExpandProperty UninstallString + if ($uninstallPath) { + Write-Host "Found Chrome uninstall path: $uninstallPath" + try { + $guid = ($uninstallPath -split "/X")[1] + Write-Host "Uninstalling Chrome MSI with GUID: $guid" + Start-Process -FilePath "msiexec.exe" -ArgumentList "/X$guid", "/quiet", "/norestart" -Wait -NoNewWindow + Write-Host "Successfully removed Google Chrome via MSI uninstaller" + } catch { + Write-Host "Failed to remove Chrome: $($_.Exception.Message)" + } + } else { + Write-Host "Chrome uninstall path not found in registry" + } + shell: pwsh + + - name: Filter apps.json and verify changed apps + if: steps.check-windows-apps.outputs.has_windows_apps == 'true' + run: | + cd fleet + # Set GITHUB_WORKSPACE to current directory so scripts can find files + $env:GITHUB_WORKSPACE = (Get-Location).Path + + # Filter changed apps to only include windows platform + $changedAppsJson = '${{ steps.detect-changed.outputs.CHANGED_APPS }}' + $windowsSlugs = ($changedAppsJson | ConvertFrom-Json | Where-Object { $_ -like "*/windows" }) + $windowsSlugsJson = ($windowsSlugs | ConvertTo-Json -Compress) + Write-Host "Filtering apps.json for slugs: $windowsSlugsJson" + + # Backup original apps.json + Copy-Item -Path "ee\maintained-apps\outputs\apps.json" -Destination "ee\maintained-apps\outputs\apps.json.backup" + + # Create filtered apps.json + # Use a fixed path for the temp file to avoid issues with bash + $filteredAppsJson = Join-Path $env:TEMP "filtered-apps-$(New-Guid).json" + bash .github/scripts/filter-apps-json.sh "$windowsSlugsJson" "$filteredAppsJson" + + # Verify the filtered file was created + if (-not (Test-Path $filteredAppsJson)) { + Write-Host "Error: Filtered apps.json was not created at $filteredAppsJson" + exit 1 + } + + # Replace apps.json with filtered version + Move-Item -Path $filteredAppsJson -Destination "ee\maintained-apps\outputs\apps.json" -Force + + # Run validation + ls "C:\Program Files" + go run ./cmd/maintained-apps/validate + + # Restore original apps.json + Move-Item -Path "ee\maintained-apps\outputs\apps.json.backup" -Destination "ee\maintained-apps\outputs\apps.json" -Force + shell: pwsh diff --git a/.github/workflows/test-fma-windows.yml b/.github/workflows/test-fma-windows.yml index 9477dd998d..77184cc348 100644 --- a/.github/workflows/test-fma-windows.yml +++ b/.github/workflows/test-fma-windows.yml @@ -1,12 +1,8 @@ name: Test Fleet Maintained Apps - Windows on: - pull_request: - types: [opened, synchronize, reopened] - paths: - - ee/maintained-apps/inputs/** - - ee/maintained-apps/outputs/** - - cmd/maintained-apps/validate/** + # Note: PR triggers removed - use test-fma-windows-pr-only.yml for PRs + # This workflow is kept for manual testing of all FMAs via workflow_dispatch workflow_dispatch: # Manual trigger inputs: log_level: