diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 9499c3e82e..1290f78f15 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -776,54 +776,12 @@ jobs: name: quarto daily: ${{ inputs.pre-release }} - cloudsmith-push: + call-cloudsmith-publish: if: ${{ inputs.publish-release && ! (inputs.pre-release == true) }} - runs-on: ubuntu-latest needs: [configure, publish-release] - strategy: - matrix: - arch: [x86_64, aarch64] - format: [deb, rpm] - repo: [open] - steps: - - uses: actions/checkout@v4 - with: - sparse-checkout: | - .github - - - name: Prevent Re-run - if: ${{ inputs.publish-release }} - uses: ./.github/workflows/actions/prevent-rerun - - - name: Download Artifacts - uses: actions/download-artifact@v4 - - - name: Set package file name - id: pkg_file - run: | - if [ "${{ matrix.format }}" == "deb" ]; then - if [ "${{ matrix.arch }}" == "x86_64" ]; then - echo "arch_name=amd64" >> $GITHUB_OUTPUT - else - echo "arch_name=arm64" >> $GITHUB_OUTPUT - fi - else - if [ "${{ matrix.arch }}" == "x86_64" ]; then - echo "arch_name=x86_64" >> $GITHUB_OUTPUT - else - echo "arch_name=aarch64" >> $GITHUB_OUTPUT - fi - fi - - - name: Push ${{ matrix.format }} ${{ matrix.arch }} to Cloudsmith ${{ matrix.repo }} - uses: cloudsmith-io/action@master - with: - api-key: ${{ secrets.CLOUDSMITH_API_KEY }} - command: "push" - format: "${{ matrix.format }}" - owner: "posit" - repo: "${{ matrix.repo }}" - distro: "any-distro" - release: "any-version" - republish: "true" - file: "./Linux-${{ matrix.format }}-${{ matrix.arch }}-Installer/quarto-${{needs.configure.outputs.version}}-linux-${{ steps.pkg_file.outputs.arch_name }}.${{ matrix.format }}" + uses: ./.github/workflows/publish-cloudsmith.yml + with: + version: ${{ needs.configure.outputs.tag_name }} + dry-run: false + secrets: + CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} diff --git a/.github/workflows/publish-cloudsmith.yml b/.github/workflows/publish-cloudsmith.yml new file mode 100644 index 0000000000..fc88321e8e --- /dev/null +++ b/.github/workflows/publish-cloudsmith.yml @@ -0,0 +1,186 @@ +name: Publish to Cloudsmith + +on: + workflow_dispatch: + inputs: + version: + description: "Release version/tag (e.g., v1.9.123 or 1.9.123, or 'latest' for most recent)" + required: false + type: string + default: "latest" + dry-run: + description: "Dry run (don't actually publish)" + required: false + type: boolean + default: true + workflow_call: + inputs: + version: + description: "Release version/tag" + required: true + type: string + dry-run: + description: "Dry run (don't actually publish)" + required: false + type: boolean + default: false + secrets: + CLOUDSMITH_API_KEY: + required: true + +jobs: + determine-version: + runs-on: ubuntu-latest + outputs: + tag_name: ${{ steps.resolve.outputs.tag_name }} + version: ${{ steps.resolve.outputs.version }} + is_prerelease: ${{ steps.resolve.outputs.is_prerelease }} + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: | + .github + + - name: Resolve version and tag + id: resolve + run: | + VERSION_INPUT="${{ inputs.version }}" + + # If version is "latest" or empty, get the latest release + if [ "$VERSION_INPUT" == "latest" ] || [ -z "$VERSION_INPUT" ]; then + echo "Fetching latest release..." + TAG_NAME=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') + IS_PRERELEASE=$(gh release view "$TAG_NAME" --json isPrerelease --jq '.isPrerelease') + else + # Ensure tag has 'v' prefix + if [[ "$VERSION_INPUT" == v* ]]; then + TAG_NAME="$VERSION_INPUT" + else + TAG_NAME="v$VERSION_INPUT" + fi + + # Check if release exists and get prerelease status + if ! gh release view "$TAG_NAME" > /dev/null 2>&1; then + echo "::error::Release $TAG_NAME not found" + exit 1 + fi + IS_PRERELEASE=$(gh release view "$TAG_NAME" --json isPrerelease --jq '.isPrerelease') + fi + + # Extract version without 'v' prefix + VERSION="${TAG_NAME#v}" + + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT + + echo "Resolved to: $TAG_NAME (version: $VERSION, prerelease: $IS_PRERELEASE)" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check not prerelease + if: steps.resolve.outputs.is_prerelease == 'true' + run: | + echo "::error::Release ${{ steps.resolve.outputs.tag_name }} is a prerelease. Cloudsmith publishing is only for stable releases." + exit 1 + + validate-all-assets: + runs-on: ubuntu-latest + needs: [determine-version] + steps: + - name: Validate all packages exist in release + run: | + TAG_NAME="${{ needs.determine-version.outputs.tag_name }}" + VERSION="${{ needs.determine-version.outputs.version }}" + + echo "Validating packages for release $TAG_NAME..." + + # Get list of assets from release (API call, no download) + ASSETS=$(gh release view "$TAG_NAME" --json assets --jq '.assets[].name') + + MISSING=0 + REQUIRED_FILES=( + "quarto-${VERSION}-linux-amd64.deb" + "quarto-${VERSION}-linux-arm64.deb" + "quarto-${VERSION}-linux-x86_64.rpm" + "quarto-${VERSION}-linux-aarch64.rpm" + ) + + for FILE in "${REQUIRED_FILES[@]}"; do + if echo "$ASSETS" | grep -q "^${FILE}$"; then + echo "✓ Found: $FILE" + else + echo "::error::Missing: $FILE" + MISSING=$((MISSING + 1)) + fi + done + + if [ $MISSING -gt 0 ]; then + echo "::error::$MISSING package(s) missing from release. All 4 packages required for Cloudsmith publishing." + exit 1 + fi + + echo "All required packages present in release" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-cloudsmith: + runs-on: ubuntu-latest + needs: [determine-version, validate-all-assets] + strategy: + matrix: + arch: [x86_64, aarch64] + format: [deb, rpm] + steps: + - name: Set package architecture name + id: pkg_arch + run: | + if [ "${{ matrix.format }}" == "deb" ]; then + if [ "${{ matrix.arch }}" == "x86_64" ]; then + echo "arch_name=amd64" >> $GITHUB_OUTPUT + else + echo "arch_name=arm64" >> $GITHUB_OUTPUT + fi + else + if [ "${{ matrix.arch }}" == "x86_64" ]; then + echo "arch_name=x86_64" >> $GITHUB_OUTPUT + else + echo "arch_name=aarch64" >> $GITHUB_OUTPUT + fi + fi + + - name: Download and validate package from release + run: | + TAG_NAME="${{ needs.determine-version.outputs.tag_name }}" + VERSION="${{ needs.determine-version.outputs.version }}" + ARCH_NAME="${{ steps.pkg_arch.outputs.arch_name }}" + FILE_NAME="quarto-${VERSION}-linux-${ARCH_NAME}.${{ matrix.format }}" + + echo "Downloading $FILE_NAME from release $TAG_NAME..." + + if ! gh release download "$TAG_NAME" --pattern "$FILE_NAME"; then + echo "::error::Failed to download $FILE_NAME from release $TAG_NAME" + exit 1 + fi + + if [ ! -f "$FILE_NAME" ]; then + echo "::error::Package file $FILE_NAME not found in release" + exit 1 + fi + + echo "✓ Successfully downloaded and validated: $FILE_NAME" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install and authenticate Cloudsmith CLI + uses: cloudsmith-io/cloudsmith-cli-action@v1.0.8 + with: + api-key: ${{ secrets.CLOUDSMITH_API_KEY }} + + - name: Push ${{ matrix.format }} ${{ matrix.arch }} to Cloudsmith${{ inputs.dry-run && ' (DRY RUN)' || '' }} + run: | + cloudsmith push ${{ matrix.format }} \ + posit/open/any-distro/any-version \ + "quarto-${{ needs.determine-version.outputs.version }}-linux-${{ steps.pkg_arch.outputs.arch_name }}.${{ matrix.format }}" \ + --republish \ + ${{ inputs.dry-run && '--dry-run' || '' }}