diff --git a/.github/workflows/release-v2.yml b/.github/workflows/release-v2.yml deleted file mode 100644 index 87a1707f8..000000000 --- a/.github/workflows/release-v2.yml +++ /dev/null @@ -1,490 +0,0 @@ -name: Release -# Triggers: workflow_dispatch and tag push -on: - push: - tags: - - 'v*.*.*' - workflow_dispatch: - inputs: - version: - description: 'Version number (e.g., 4.30.0)' - required: true - type: string - release_notes: - description: 'Release notes (markdown) - generated by Claude' - required: false - type: string - -jobs: - extract-version: - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - version: ${{ steps.extract.outputs.version }} - tag: ${{ steps.extract.outputs.tag }} - steps: - - name: Extract version - id: extract - run: | - # Handle both tag push and workflow_dispatch - if [ "${{ github.event_name }}" = "push" ]; then - TAG="${GITHUB_REF#refs/tags/}" - VERSION="${TAG#v}" - else - VERSION="${{ inputs.version }}" - TAG="v${VERSION}" - fi - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "Version: ${VERSION}, Tag: ${TAG}" - - version-guard: - needs: extract-version - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Ensure VERSION file matches requested version - run: | - FILE_VERSION=$(cat VERSION | tr -d '\n') - REQUESTED_VERSION="${{ needs.extract-version.outputs.version }}" - if [ "$FILE_VERSION" != "$REQUESTED_VERSION" ]; then - echo "::error::VERSION file ($FILE_VERSION) does not match requested version ($REQUESTED_VERSION)." - echo "The VERSION file must be updated and committed before running release." - exit 1 - fi - echo "✓ VERSION file matches requested version ($REQUESTED_VERSION)" - - preflight-tests: - needs: version-guard - runs-on: ubuntu-latest - timeout-minutes: 90 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: 'frontend-modern/package-lock.json' - - - name: Install frontend dependencies - run: npm --prefix frontend-modern ci - - - name: Build frontend bundle for Go embed - run: | - npm --prefix frontend-modern run build - rm -rf internal/api/frontend-modern - mkdir -p internal/api/frontend-modern - cp -r frontend-modern/dist internal/api/frontend-modern/ - - - name: Lint frontend - run: npm --prefix frontend-modern run lint - - - name: Install docker-compose - run: | - sudo apt-get update - sudo apt-get install -y docker-compose - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - cache: true - - - name: Run backend tests - run: go test ./... - - - name: Cache Playwright browsers - uses: actions/cache@v4 - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-${{ hashFiles('tests/integration/package-lock.json') }} - - - name: Prepare integration test dependencies - working-directory: tests/integration - run: | - npm ci - npx playwright install --with-deps chromium - - - name: Build Pulse for integration tests - run: make build - - - name: Build Docker images for integration tests - run: | - docker build -t pulse-mock-github:test tests/integration/mock-github-server - docker build -t pulse:test -f Dockerfile . - - - name: Run update integration smoke tests - working-directory: tests/integration - env: - MOCK_CHECKSUM_ERROR: "false" - MOCK_NETWORK_ERROR: "false" - MOCK_RATE_LIMIT: "false" - MOCK_STALE_RELEASE: "false" - run: | - docker-compose -f docker-compose.test.yml up -d - - # Wait for services to be healthy - echo "Waiting for mock-github to be healthy..." - timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-mock-github | grep -q "healthy"; do sleep 2; done' || { - echo "Mock GitHub failed to become healthy" - docker logs pulse-mock-github - exit 1 - } - - echo "Waiting for pulse-test-server to be healthy..." - timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-test-server | grep -q "healthy"; do sleep 2; done' || { - echo "Pulse server failed to become healthy" - docker logs pulse-test-server - exit 1 - } - - echo "All services healthy, verifying port mapping..." - # Test that the host can actually reach the container through port mapping - for i in 1 2 3 4 5; do - if curl -f -s http://localhost:7655/api/health > /dev/null 2>&1; then - echo "Port mapping verified: Pulse server is reachable from host" - break - elif [ $i -eq 5 ]; then - echo "ERROR: Port mapping failed - cannot reach Pulse server from host" - echo "Container healthcheck passed, but host cannot connect via localhost:7655" - echo "Pulse server logs:" - docker logs pulse-test-server || true - echo "Mock GitHub logs:" - docker logs pulse-mock-github || true - exit 1 - else - echo "Attempt $i: Server not yet reachable from host, waiting..." - sleep 2 - fi - done - - echo "Running API-level update integration test..." - UPDATE_API_BASE_URL=http://localhost:7655 go test ../../tests/integration/api -run TestUpdateFlowIntegration -count=1 - - echo "Skipping legacy Playwright update scenarios (removed until they can be rebuilt)" - docker-compose -f docker-compose.test.yml down -v - - - name: Cleanup integration environment - if: always() - working-directory: tests/integration - run: docker-compose -f docker-compose.test.yml down -v || true - - build-docker-images: - needs: - - extract-version - - version-guard - - preflight-tests - runs-on: ubuntu-latest - timeout-minutes: 60 - 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 Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Pulse server image - uses: docker/build-push-action@v6 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - provenance: false - cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:buildcache - cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:buildcache,mode=max - tags: | - rcourtman/pulse:${{ needs.extract-version.outputs.tag }} - rcourtman/pulse:${{ needs.extract-version.outputs.version }} - rcourtman/pulse:latest - ghcr.io/${{ github.repository_owner }}/pulse:${{ needs.extract-version.outputs.tag }} - ghcr.io/${{ github.repository_owner }}/pulse:${{ needs.extract-version.outputs.version }} - ghcr.io/${{ github.repository_owner }}/pulse:latest - labels: | - org.opencontainers.image.title=Pulse - org.opencontainers.image.description=Proxmox monitoring system - org.opencontainers.image.version=${{ needs.extract-version.outputs.tag }} - org.opencontainers.image.created=${{ github.event.repository.updated_at }} - org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.licenses=MIT - - - name: Build and push Docker agent image - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - target: agent_runtime - platforms: linux/amd64,linux/arm64 - push: true - provenance: false - cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:buildcache - cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:buildcache,mode=max - tags: | - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.tag }} - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.version }} - rcourtman/pulse-docker-agent:latest - ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:${{ needs.extract-version.outputs.tag }} - ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:${{ needs.extract-version.outputs.version }} - ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:latest - labels: | - org.opencontainers.image.title=Pulse Docker Agent - org.opencontainers.image.description=Docker container monitoring agent for Pulse - org.opencontainers.image.version=${{ needs.extract-version.outputs.tag }} - org.opencontainers.image.created=${{ github.event.repository.updated_at }} - org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.licenses=MIT - - - name: Output Docker image information - run: | - echo "✅ Docker images built and pushed successfully!" - echo "" - echo "Server images:" - echo " - rcourtman/pulse:${{ needs.extract-version.outputs.tag }}" - echo " - rcourtman/pulse:${{ needs.extract-version.outputs.version }}" - echo " - rcourtman/pulse:latest" - echo "" - echo "Agent images:" - echo " - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.tag }}" - echo " - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.version }}" - echo " - rcourtman/pulse-docker-agent:latest" - - create-release: - needs: - - extract-version - - build-docker-images - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: write - outputs: - release_id: ${{ steps.create_release.outputs.release_id }} - release_url: ${{ steps.create_release.outputs.release_url }} - target_commitish: ${{ github.sha }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for changelog generation - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - run: | - # Install zip for Windows binaries - sudo apt-get update - sudo apt-get install -y zip - - # Install Helm for chart packaging - curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - - - name: Build release artifacts - run: | - echo "Building release ${{ needs.extract-version.outputs.tag }}..." - ./scripts/build-release.sh ${{ needs.extract-version.outputs.version }} - - - name: Prepare release notes - id: generate_notes - env: - RELEASE_NOTES_INPUT: ${{ inputs.release_notes }} - run: | - VERSION="${{ needs.extract-version.outputs.version }}" - - # Save release notes to file - NOTES_FILE=$(mktemp) - - if [ -n "$RELEASE_NOTES_INPUT" ]; then - echo "Using Claude-generated release notes from workflow input" - printf "%s\n" "$RELEASE_NOTES_INPUT" > "$NOTES_FILE" - else - echo "Tag-triggered release - using placeholder notes" - echo "Release $VERSION" > "$NOTES_FILE" - echo "" >> "$NOTES_FILE" - echo "See commit history for changes." >> "$NOTES_FILE" - fi - - # Add installation instructions - cat >> "$NOTES_FILE" << EOF - -## Installation - -**Docker (recommended):** -\`\`\`bash -docker pull rcourtman/pulse:${VERSION} -\`\`\` - -**Docker Compose:** -Update your \`docker-compose.yml\` to use \`rcourtman/pulse:${VERSION}\` - -See the [Installation Guide](https://github.com/rcourtman/Pulse#installation) for complete setup instructions. -EOF - - echo "notes_file=${NOTES_FILE}" >> $GITHUB_OUTPUT - - echo "Release notes content:" - cat "$NOTES_FILE" - - - name: Create draft release - id: create_release - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="${{ needs.extract-version.outputs.tag }}" - NOTES_FILE="${{ steps.generate_notes.outputs.notes_file }}" - - echo "Creating draft release for ${TAG}..." - - # Tag already exists (pushed by user), so don't create it - # Just create the release pointing to the existing tag - gh release create "${TAG}" \ - --draft \ - --title "Pulse ${TAG}" \ - --notes-file "$NOTES_FILE" - - rm -f "$NOTES_FILE" - - # Get the release ID - echo "Waiting for release to appear in GitHub API..." - MAX_ATTEMPTS=10 - ATTEMPT=1 - RELEASE_JSON="" - - while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do - echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Looking for release ${TAG}..." - RELEASE_JSON=$(gh api "repos/${{ github.repository }}/releases" --paginate | jq ".[] | select(.tag_name == \"${TAG}\")") - - if [ -n "$RELEASE_JSON" ]; then - echo "✓ Found release in API" - break - fi - - if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then - echo "Release not indexed yet, waiting 2 seconds..." - sleep 2 - fi - - ATTEMPT=$((ATTEMPT + 1)) - done - - if [ -z "$RELEASE_JSON" ]; then - echo "::error::Failed to find release ${TAG} in API after $MAX_ATTEMPTS attempts" - exit 1 - fi - - RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id') - - if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then - echo "::error::Failed to extract release ID from API response" - exit 1 - fi - - echo "release_url=$(echo "$RELEASE_JSON" | jq -r '.html_url')" >> $GITHUB_OUTPUT - echo "release_id=${RELEASE_ID}" >> $GITHUB_OUTPUT - - echo "✓ Release ID: ${RELEASE_ID}" - - - name: Upload checksums.txt - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="${{ needs.extract-version.outputs.tag }}" - - echo "Uploading checksums.txt..." - gh release upload "${TAG}" release/checksums.txt - - # Upload individual .sha256 files for backward compatibility - echo "Uploading .sha256 checksum files..." - gh release upload "${TAG}" release/*.sha256 - - - name: Upload release assets - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="${{ needs.extract-version.outputs.tag }}" - - echo "Uploading release assets..." - - # Upload tarballs - gh release upload "${TAG}" release/*.tar.gz - - # Upload Windows zip files - gh release upload "${TAG}" release/*.zip - - # Upload Helm chart if it exists - if ls release/*.tgz 1> /dev/null 2>&1; then - echo "Uploading Helm chart..." - gh release upload "${TAG}" release/*.tgz - fi - - # Upload install.sh - gh release upload "${TAG}" release/install.sh - - - name: Output release information - run: | - echo "✅ Release draft created successfully!" - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "📦 Release: ${{ needs.extract-version.outputs.tag }}" - echo "🔗 URL: ${{ steps.create_release.outputs.release_url }}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" - echo "⚠️ IMPORTANT: This release is in DRAFT status" - echo "" - echo "Next steps:" - echo "1. Review the automatically generated release notes" - echo "2. Edit and categorize changes as needed" - echo "3. Publish the release when ready" - echo "" - echo "All artifacts have been uploaded." - echo "Docker images are available at Docker Hub and GHCR." - echo "" - - validate-release-assets: - needs: - - extract-version - - create-release - uses: ./.github/workflows/validate-release-assets.yml - secrets: inherit - with: - tag: ${{ needs.extract-version.outputs.tag }} - version: ${{ needs.extract-version.outputs.version }} - release_id: ${{ needs.create-release.outputs.release_id }} - draft: true - target_commitish: ${{ needs.create-release.outputs.target_commitish }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index db0e9c32c..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,491 +0,0 @@ -name: Release -# Triggers: workflow_dispatch and tag push -# Updated: 2025-11-13 to force re-indexing -on: - push: - tags: - - 'v*.*.*' - workflow_dispatch: - inputs: - version: - description: 'Version number (e.g., 4.30.0)' - required: true - type: string - release_notes: - description: 'Release notes (markdown) - generated by Claude' - required: false - type: string - -jobs: - extract-version: - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - version: ${{ steps.extract.outputs.version }} - tag: ${{ steps.extract.outputs.tag }} - steps: - - name: Extract version - id: extract - run: | - # Handle both tag push and workflow_dispatch - if [ "${{ github.event_name }}" = "push" ]; then - TAG="${GITHUB_REF#refs/tags/}" - VERSION="${TAG#v}" - else - VERSION="${{ inputs.version }}" - TAG="v${VERSION}" - fi - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "Version: ${VERSION}, Tag: ${TAG}" - - version-guard: - needs: extract-version - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Ensure VERSION file matches requested version - run: | - FILE_VERSION=$(cat VERSION | tr -d '\n') - REQUESTED_VERSION="${{ needs.extract-version.outputs.version }}" - if [ "$FILE_VERSION" != "$REQUESTED_VERSION" ]; then - echo "::error::VERSION file ($FILE_VERSION) does not match requested version ($REQUESTED_VERSION)." - echo "The VERSION file must be updated and committed before running release." - exit 1 - fi - echo "✓ VERSION file matches requested version ($REQUESTED_VERSION)" - - preflight-tests: - needs: version-guard - runs-on: ubuntu-latest - timeout-minutes: 90 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: 'frontend-modern/package-lock.json' - - - name: Install frontend dependencies - run: npm --prefix frontend-modern ci - - - name: Build frontend bundle for Go embed - run: | - npm --prefix frontend-modern run build - rm -rf internal/api/frontend-modern - mkdir -p internal/api/frontend-modern - cp -r frontend-modern/dist internal/api/frontend-modern/ - - - name: Lint frontend - run: npm --prefix frontend-modern run lint - - - name: Install docker-compose - run: | - sudo apt-get update - sudo apt-get install -y docker-compose - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - cache: true - - - name: Run backend tests - run: go test ./... - - - name: Cache Playwright browsers - uses: actions/cache@v4 - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-${{ hashFiles('tests/integration/package-lock.json') }} - - - name: Prepare integration test dependencies - working-directory: tests/integration - run: | - npm ci - npx playwright install --with-deps chromium - - - name: Build Pulse for integration tests - run: make build - - - name: Build Docker images for integration tests - run: | - docker build -t pulse-mock-github:test tests/integration/mock-github-server - docker build -t pulse:test -f Dockerfile . - - - name: Run update integration smoke tests - working-directory: tests/integration - env: - MOCK_CHECKSUM_ERROR: "false" - MOCK_NETWORK_ERROR: "false" - MOCK_RATE_LIMIT: "false" - MOCK_STALE_RELEASE: "false" - run: | - docker-compose -f docker-compose.test.yml up -d - - # Wait for services to be healthy - echo "Waiting for mock-github to be healthy..." - timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-mock-github | grep -q "healthy"; do sleep 2; done' || { - echo "Mock GitHub failed to become healthy" - docker logs pulse-mock-github - exit 1 - } - - echo "Waiting for pulse-test-server to be healthy..." - timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-test-server | grep -q "healthy"; do sleep 2; done' || { - echo "Pulse server failed to become healthy" - docker logs pulse-test-server - exit 1 - } - - echo "All services healthy, verifying port mapping..." - # Test that the host can actually reach the container through port mapping - for i in 1 2 3 4 5; do - if curl -f -s http://localhost:7655/api/health > /dev/null 2>&1; then - echo "Port mapping verified: Pulse server is reachable from host" - break - elif [ $i -eq 5 ]; then - echo "ERROR: Port mapping failed - cannot reach Pulse server from host" - echo "Container healthcheck passed, but host cannot connect via localhost:7655" - echo "Pulse server logs:" - docker logs pulse-test-server || true - echo "Mock GitHub logs:" - docker logs pulse-mock-github || true - exit 1 - else - echo "Attempt $i: Server not yet reachable from host, waiting..." - sleep 2 - fi - done - - echo "Running API-level update integration test..." - UPDATE_API_BASE_URL=http://localhost:7655 go test ../../tests/integration/api -run TestUpdateFlowIntegration -count=1 - - echo "Skipping legacy Playwright update scenarios (removed until they can be rebuilt)" - docker-compose -f docker-compose.test.yml down -v - - - name: Cleanup integration environment - if: always() - working-directory: tests/integration - run: docker-compose -f docker-compose.test.yml down -v || true - - build-docker-images: - needs: - - extract-version - - version-guard - - preflight-tests - runs-on: ubuntu-latest - timeout-minutes: 60 - 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 Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Pulse server image - uses: docker/build-push-action@v6 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - provenance: false - cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:buildcache - cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:buildcache,mode=max - tags: | - rcourtman/pulse:${{ needs.extract-version.outputs.tag }} - rcourtman/pulse:${{ needs.extract-version.outputs.version }} - rcourtman/pulse:latest - ghcr.io/${{ github.repository_owner }}/pulse:${{ needs.extract-version.outputs.tag }} - ghcr.io/${{ github.repository_owner }}/pulse:${{ needs.extract-version.outputs.version }} - ghcr.io/${{ github.repository_owner }}/pulse:latest - labels: | - org.opencontainers.image.title=Pulse - org.opencontainers.image.description=Proxmox monitoring system - org.opencontainers.image.version=${{ needs.extract-version.outputs.tag }} - org.opencontainers.image.created=${{ github.event.repository.updated_at }} - org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.licenses=MIT - - - name: Build and push Docker agent image - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - target: agent_runtime - platforms: linux/amd64,linux/arm64 - push: true - provenance: false - cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:buildcache - cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:buildcache,mode=max - tags: | - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.tag }} - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.version }} - rcourtman/pulse-docker-agent:latest - ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:${{ needs.extract-version.outputs.tag }} - ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:${{ needs.extract-version.outputs.version }} - ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:latest - labels: | - org.opencontainers.image.title=Pulse Docker Agent - org.opencontainers.image.description=Docker container monitoring agent for Pulse - org.opencontainers.image.version=${{ needs.extract-version.outputs.tag }} - org.opencontainers.image.created=${{ github.event.repository.updated_at }} - org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.licenses=MIT - - - name: Output Docker image information - run: | - echo "✅ Docker images built and pushed successfully!" - echo "" - echo "Server images:" - echo " - rcourtman/pulse:${{ needs.extract-version.outputs.tag }}" - echo " - rcourtman/pulse:${{ needs.extract-version.outputs.version }}" - echo " - rcourtman/pulse:latest" - echo "" - echo "Agent images:" - echo " - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.tag }}" - echo " - rcourtman/pulse-docker-agent:${{ needs.extract-version.outputs.version }}" - echo " - rcourtman/pulse-docker-agent:latest" - - create-release: - needs: - - extract-version - - build-docker-images - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: write - outputs: - release_id: ${{ steps.create_release.outputs.release_id }} - release_url: ${{ steps.create_release.outputs.release_url }} - target_commitish: ${{ github.sha }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for changelog generation - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - run: | - # Install zip for Windows binaries - sudo apt-get update - sudo apt-get install -y zip - - # Install Helm for chart packaging - curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - - - name: Build release artifacts - run: | - echo "Building release ${{ needs.extract-version.outputs.tag }}..." - ./scripts/build-release.sh ${{ needs.extract-version.outputs.version }} - - - name: Prepare release notes - id: generate_notes - env: - RELEASE_NOTES_INPUT: ${{ inputs.release_notes }} - run: | - VERSION="${{ needs.extract-version.outputs.version }}" - - # Save release notes to file - NOTES_FILE=$(mktemp) - - if [ -n "$RELEASE_NOTES_INPUT" ]; then - echo "Using Claude-generated release notes from workflow input" - printf "%s\n" "$RELEASE_NOTES_INPUT" > "$NOTES_FILE" - else - echo "Tag-triggered release - using placeholder notes" - echo "Release $VERSION" > "$NOTES_FILE" - echo "" >> "$NOTES_FILE" - echo "See commit history for changes." >> "$NOTES_FILE" - fi - - # Add installation instructions - cat >> "$NOTES_FILE" << EOF - -## Installation - -**Docker (recommended):** -\`\`\`bash -docker pull rcourtman/pulse:${VERSION} -\`\`\` - -**Docker Compose:** -Update your \`docker-compose.yml\` to use \`rcourtman/pulse:${VERSION}\` - -See the [Installation Guide](https://github.com/rcourtman/Pulse#installation) for complete setup instructions. -EOF - - echo "notes_file=${NOTES_FILE}" >> $GITHUB_OUTPUT - - echo "Release notes content:" - cat "$NOTES_FILE" - - - name: Create draft release - id: create_release - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="${{ needs.extract-version.outputs.tag }}" - NOTES_FILE="${{ steps.generate_notes.outputs.notes_file }}" - - echo "Creating draft release for ${TAG}..." - - # Tag already exists (pushed by user), so don't create it - # Just create the release pointing to the existing tag - gh release create "${TAG}" \ - --draft \ - --title "Pulse ${TAG}" \ - --notes-file "$NOTES_FILE" - - rm -f "$NOTES_FILE" - - # Get the release ID - echo "Waiting for release to appear in GitHub API..." - MAX_ATTEMPTS=10 - ATTEMPT=1 - RELEASE_JSON="" - - while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do - echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Looking for release ${TAG}..." - RELEASE_JSON=$(gh api "repos/${{ github.repository }}/releases" --paginate | jq ".[] | select(.tag_name == \"${TAG}\")") - - if [ -n "$RELEASE_JSON" ]; then - echo "✓ Found release in API" - break - fi - - if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then - echo "Release not indexed yet, waiting 2 seconds..." - sleep 2 - fi - - ATTEMPT=$((ATTEMPT + 1)) - done - - if [ -z "$RELEASE_JSON" ]; then - echo "::error::Failed to find release ${TAG} in API after $MAX_ATTEMPTS attempts" - exit 1 - fi - - RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id') - - if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then - echo "::error::Failed to extract release ID from API response" - exit 1 - fi - - echo "release_url=$(echo "$RELEASE_JSON" | jq -r '.html_url')" >> $GITHUB_OUTPUT - echo "release_id=${RELEASE_ID}" >> $GITHUB_OUTPUT - - echo "✓ Release ID: ${RELEASE_ID}" - - - name: Upload checksums.txt - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="${{ needs.extract-version.outputs.tag }}" - - echo "Uploading checksums.txt..." - gh release upload "${TAG}" release/checksums.txt - - # Upload individual .sha256 files for backward compatibility - echo "Uploading .sha256 checksum files..." - gh release upload "${TAG}" release/*.sha256 - - - name: Upload release assets - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="${{ needs.extract-version.outputs.tag }}" - - echo "Uploading release assets..." - - # Upload tarballs - gh release upload "${TAG}" release/*.tar.gz - - # Upload Windows zip files - gh release upload "${TAG}" release/*.zip - - # Upload Helm chart if it exists - if ls release/*.tgz 1> /dev/null 2>&1; then - echo "Uploading Helm chart..." - gh release upload "${TAG}" release/*.tgz - fi - - # Upload install.sh - gh release upload "${TAG}" release/install.sh - - - name: Output release information - run: | - echo "✅ Release draft created successfully!" - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "📦 Release: ${{ needs.extract-version.outputs.tag }}" - echo "🔗 URL: ${{ steps.create_release.outputs.release_url }}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" - echo "⚠️ IMPORTANT: This release is in DRAFT status" - echo "" - echo "Next steps:" - echo "1. Review the automatically generated release notes" - echo "2. Edit and categorize changes as needed" - echo "3. Publish the release when ready" - echo "" - echo "All artifacts have been uploaded." - echo "Docker images are available at Docker Hub and GHCR." - echo "" - - validate-release-assets: - needs: - - extract-version - - create-release - uses: ./.github/workflows/validate-release-assets.yml - secrets: inherit - with: - tag: ${{ needs.extract-version.outputs.tag }} - version: ${{ needs.extract-version.outputs.version }} - release_id: ${{ needs.create-release.outputs.release_id }} - draft: true - target_commitish: ${{ needs.create-release.outputs.target_commitish }}