Sign and Release packages #183
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
| name: Sign and Release packages | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| workflow_url: | |
| description: 'The URL to the workflow run that produced the packages' | |
| required: true | |
| release_environment: | |
| description: 'The environment to release to. "staging" or "production"' | |
| required: true | |
| default: 'staging' | |
| package_version: | |
| description: 'The version of the package to release' | |
| required: true | |
| type: string | |
| docker_release: | |
| description: 'Publish Docker images' | |
| required: true | |
| default: true | |
| type: boolean | |
| GPG_PASSPHRASE: | |
| description: 'GPG passphrase for signing (required for production releases)' | |
| required: false | |
| type: string | |
| folder_time: | |
| description: 'Optional timestamp for the release folder. If not provided, one will be generated.' | |
| required: false | |
| type: string | |
| env: | |
| ARTIFACT_NAME: build_report_package_release | |
| AWS_REGION: us-east-1 | |
| SRC_BUCKET: altinity-build-artifacts | |
| S3_STORAGE_BUCKET: altinity-test-reports-temp | |
| jobs: | |
| extract-package-info: | |
| runs-on: [altinity-style-checker-aarch64, altinity-on-demand] | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| FOLDER_TIME: ${{ inputs.folder_time }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| outputs: | |
| docker_version: ${{ env.DOCKER_VERSION }} | |
| commit_hash: ${{ env.COMMIT_HASH }} | |
| folder_time: ${{ env.FOLDER_TIME }} | |
| needs_binary_processing: ${{ env.NEEDS_BINARY_PROCESSING }} | |
| test_results_src: ${{ env.TEST_RESULTS_SRC }} | |
| repo_prefix: ${{ env.REPO_PREFIX }} | |
| src_url: ${{ env.SRC_URL }} | |
| dest_url: ${{ env.DEST_URL }} | |
| major_version: ${{ env.MAJOR_VERSION }} | |
| build_dir: ${{ env.BUILD_DIR }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Run script | |
| run: | | |
| python3 tests/ci/release/extract-package-info.py \ | |
| --artifact-dest-bucket $S3_STORAGE_BUCKET \ | |
| --artifact-src-bucket $SRC_BUCKET \ | |
| --package-version ${{ inputs.package_version }} \ | |
| --workflow-url ${{ inputs.workflow_url }} | |
| - name: Display Extracted Information | |
| run: | | |
| echo "Extracted information:" | |
| echo "commit_hash: ${COMMIT_HASH}" | |
| echo "docker_version: ${DOCKER_VERSION}" | |
| echo "folder_time: ${FOLDER_TIME}" | |
| echo "needs_binary_processing: ${NEEDS_BINARY_PROCESSING}" | |
| echo "repo_prefix: ${REPO_PREFIX}" | |
| echo "src_bucket: ${SRC_BUCKET}" | |
| echo "test_results_src: ${TEST_RESULTS_SRC}" | |
| echo "src_url: ${SRC_URL}" | |
| echo "dest_url: ${DEST_URL}" | |
| echo "major_version: ${MAJOR_VERSION}" | |
| - name: Install aws cli | |
| if: ${{ env.NEEDS_BINARY_PROCESSING == 'true' }} | |
| uses: unfor19/install-aws-cli-action@v1 | |
| with: | |
| version: 2 | |
| arch: arm64 | |
| - name: Process ARM binary | |
| if: ${{ env.NEEDS_BINARY_PROCESSING == 'true' }} | |
| run: | | |
| ARM_PATH=$([ "$BUILD_DIR" = "build_amd_release" ] && echo "build_arm_release" || echo "package_aarch64") | |
| echo "Downloading clickhouse binary..." | |
| if ! aws s3 cp "${SRC_URL}/${ARM_PATH}/clickhouse" clickhouse; then | |
| echo "Failed to download clickhouse binary" | |
| exit 1 | |
| fi | |
| chmod +x clickhouse | |
| echo "Running clickhouse binary..." | |
| ./clickhouse -q'q' | |
| echo "Downloading clickhouse-stripped binary..." | |
| if ! aws s3 cp "${SRC_URL}/${ARM_PATH}/clickhouse-stripped" clickhouse-stripped; then | |
| echo "Failed to download clickhouse-stripped binary" | |
| exit 1 | |
| fi | |
| chmod +x clickhouse-stripped | |
| echo "Running clickhouse-stripped binary..." | |
| ./clickhouse-stripped -q'q' | |
| echo "Downloading clickhouse-common-static tar..." | |
| if ! aws s3 cp "${SRC_URL}/${ARM_PATH}/clickhouse-common-static-${{ inputs.package_version }}-arm64.tgz" clickhouse-common-static.tgz; then | |
| echo "Failed to download clickhouse-common-static tar" | |
| exit 1 | |
| fi | |
| tar -xvf clickhouse-common-static.tgz | |
| if readelf -S "clickhouse-common-static-${{ inputs.package_version }}/usr/bin/clickhouse" | grep -q '\.symtab'; then | |
| echo "✓ Binary has symtable" | |
| else | |
| echo "✗ Binary is missing symtable" | |
| exit 1 | |
| fi | |
| echo "Uploading processed binaries..." | |
| if ! aws s3 cp clickhouse "${SRC_URL}/${ARM_PATH}/arm64-bin/non-self-extracting/"; then | |
| echo "Failed to upload clickhouse binary" | |
| exit 1 | |
| fi | |
| if ! aws s3 cp clickhouse-stripped "${SRC_URL}/${ARM_PATH}/arm64-bin/non-self-extracting/"; then | |
| echo "Failed to upload stripped clickhouse binary" | |
| exit 1 | |
| fi | |
| if ! aws s3 cp "clickhouse-common-static-${{ inputs.package_version }}/usr/bin/clickhouse" "${SRC_URL}/${ARM_PATH}/arm64-bin/non-self-extracting/clickhouse-with-symtable"; then | |
| echo "Failed to upload clickhouse-with-symtable binary" | |
| exit 1 | |
| fi | |
| copy-packages: | |
| needs: extract-package-info | |
| runs-on: [altinity-func-tester, altinity-on-demand] | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| SRC_URL: ${{ needs.extract-package-info.outputs.src_url }} | |
| DEST_URL: ${{ needs.extract-package-info.outputs.dest_url }} | |
| NEEDS_BINARY_PROCESSING: ${{ needs.extract-package-info.outputs.needs_binary_processing }} | |
| MAJOR_VERSION: ${{ needs.extract-package-info.outputs.major_version }} | |
| BUILD_DIR: ${{ needs.extract-package-info.outputs.build_dir }} | |
| steps: | |
| - name: Install aws cli | |
| uses: unfor19/install-aws-cli-action@v1 | |
| with: | |
| version: 2 | |
| arch: amd64 | |
| - name: Move verified packages to destination | |
| run: | | |
| # Move ARM packages | |
| ARM_PATH=$([ "$BUILD_DIR" = "build_amd_release" ] && echo "build_arm_release" || echo "package_aarch64") | |
| echo "Moving verified ARM packages to destination..." | |
| if ! aws s3 sync "${SRC_URL}/${ARM_PATH}/" "${DEST_URL}/packages/ARM_PACKAGES/"; then | |
| echo "Failed to move ARM packages to destination" | |
| exit 1 | |
| fi | |
| # Move AMD packages | |
| echo "Moving verified AMD packages to destination..." | |
| if ! aws s3 sync "${SRC_URL}/${BUILD_DIR}/" "${DEST_URL}/packages/AMD_PACKAGES/"; then | |
| echo "Failed to move AMD packages to destination" | |
| exit 1 | |
| fi | |
| # Clean up temporary directories | |
| rm -rf /tmp/arm_packages /tmp/amd_packages | |
| - name: Separate ARM binary | |
| run: | | |
| aws s3 mv "${DEST_URL}/packages/ARM_PACKAGES/clickhouse" "${DEST_URL}/packages/ARM_PACKAGES/arm64-bin/clickhouse" | |
| aws s3 mv "${DEST_URL}/packages/ARM_PACKAGES/clickhouse-stripped" "${DEST_URL}/packages/ARM_PACKAGES/arm64-bin/clickhouse-stripped" | |
| - name: Separate AMD binary | |
| run: | | |
| aws s3 mv "${DEST_URL}/packages/AMD_PACKAGES/clickhouse" "${DEST_URL}/packages/AMD_PACKAGES/amd64-bin/clickhouse" | |
| aws s3 mv "${DEST_URL}/packages/AMD_PACKAGES/clickhouse-stripped" "${DEST_URL}/packages/AMD_PACKAGES/amd64-bin/clickhouse-stripped" | |
| - name: Process AMD binary | |
| if: ${{ env.NEEDS_BINARY_PROCESSING == 'true' }} | |
| run: | | |
| echo "Downloading clickhouse binary..." | |
| if ! aws s3 cp "${DEST_URL}/packages/AMD_PACKAGES/amd64-bin/clickhouse" clickhouse; then | |
| echo "Failed to download clickhouse binary" | |
| exit 1 | |
| fi | |
| chmod +x clickhouse | |
| echo "Running clickhouse binary..." | |
| ./clickhouse -q'q' | |
| echo "Downloading clickhouse-stripped binary..." | |
| if ! aws s3 cp "${DEST_URL}/packages/AMD_PACKAGES/amd64-bin/clickhouse-stripped" clickhouse-stripped; then | |
| echo "Failed to download clickhouse-stripped binary" | |
| exit 1 | |
| fi | |
| chmod +x clickhouse-stripped | |
| echo "Running clickhouse-stripped binary..." | |
| ./clickhouse-stripped -q'q' | |
| echo "Downloading clickhouse-common-static tar..." | |
| if ! aws s3 cp "${DEST_URL}/packages/AMD_PACKAGES/clickhouse-common-static-${{ inputs.package_version }}-amd64.tgz" clickhouse-common-static.tgz; then | |
| echo "Failed to download clickhouse-common-static tar" | |
| exit 1 | |
| fi | |
| tar -xvf clickhouse-common-static.tgz | |
| if readelf -S "clickhouse-common-static-${{ inputs.package_version }}/usr/bin/clickhouse" | grep -q '\.symtab'; then | |
| echo "✓ Binary has symtable" | |
| else | |
| echo "✗ Binary is missing symtable" | |
| exit 1 | |
| fi | |
| echo "Uploading processed binaries..." | |
| if ! aws s3 cp clickhouse "${DEST_URL}/packages/AMD_PACKAGES/amd64-bin/non-self-extracting/"; then | |
| echo "Failed to upload clickhouse binary" | |
| exit 1 | |
| fi | |
| if ! aws s3 cp clickhouse-stripped "${DEST_URL}/packages/AMD_PACKAGES/amd64-bin/non-self-extracting/"; then | |
| echo "Failed to upload stripped clickhouse binary" | |
| exit 1 | |
| fi | |
| if ! aws s3 cp clickhouse-common-static-${{ inputs.package_version }}/usr/bin/clickhouse "${DEST_URL}/packages/AMD_PACKAGES/amd64-bin/non-self-extracting/clickhouse-with-symtable"; then | |
| echo "Failed to upload stripped clickhouse binary" | |
| exit 1 | |
| fi | |
| copy-test-results: | |
| needs: extract-package-info | |
| runs-on: [altinity-style-checker-aarch64, altinity-on-demand] | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| TEST_RESULTS_SRC: ${{ needs.extract-package-info.outputs.test_results_src }} | |
| COMMIT_HASH: ${{ needs.extract-package-info.outputs.commit_hash }} | |
| DEST_URL: ${{ needs.extract-package-info.outputs.dest_url }} | |
| steps: | |
| - name: Install aws cli | |
| uses: unfor19/install-aws-cli-action@v1 | |
| with: | |
| version: 2 | |
| arch: arm64 | |
| - name: Copy test results to S3 | |
| run: | | |
| # Copy test results | |
| echo "Copying test results..." | |
| if ! aws s3 sync "s3://${SRC_BUCKET}/${TEST_RESULTS_SRC}/${COMMIT_HASH}" \ | |
| "${DEST_URL}/test_results/"; then | |
| echo "Failed to copy test results" | |
| exit 1 | |
| fi | |
| publish-docker: | |
| if: ${{ github.event.inputs.docker_release == 'true' }} | |
| needs: extract-package-info | |
| strategy: | |
| matrix: | |
| image_type: [server, keeper] | |
| variant: ['', '-alpine'] | |
| uses: ./.github/workflows/docker_publish.yml | |
| with: | |
| docker_image: altinityinfra/clickhouse-${{ matrix.image_type }}:${{ needs.extract-package-info.outputs.docker_version }}${{ matrix.variant }} | |
| release_environment: ${{ inputs.release_environment }} | |
| upload_artifacts: false | |
| s3_upload_path: "${{ needs.extract-package-info.outputs.dest_url }}/docker_images/${{ matrix.image_type }}${{ matrix.variant }}/" | |
| secrets: inherit | |
| sign-and-publish: | |
| needs: [extract-package-info, copy-packages] | |
| runs-on: ${{ | |
| inputs.release_environment == 'staging' && 'arc-runners-clickhouse-signer' || | |
| inputs.release_environment == 'production' && 'arc-runners-clickhouse-signer-prod' | |
| }} | |
| env: | |
| REPO_DNS_NAME: ${{ inputs.release_environment == 'production' && 'builds.altinity.cloud' || 'builds.staging.altinity.cloud' }} | |
| REPO_NAME: ${{ inputs.release_environment == 'production' && 'altinity' || 'altinity-staging' }} | |
| REPO_SUBTITLE: ${{ inputs.release_environment == 'production' && 'Stable Builds' || 'Staging Builds' }} | |
| PACKAGE_VERSION: ${{ inputs.package_version }} | |
| FOLDER_TIME: ${{ needs.extract-package-info.outputs.folder_time }} | |
| REPO_PREFIX: ${{ needs.extract-package-info.outputs.repo_prefix }} | |
| NEEDS_BINARY_PROCESSING: ${{ needs.extract-package-info.outputs.needs_binary_processing }} | |
| DEST_URL: ${{ needs.extract-package-info.outputs.dest_url }} | |
| RELEASE_ENVIRONMENT: ${{ inputs.release_environment }} | |
| steps: | |
| - name: Install aws cli | |
| uses: unfor19/install-aws-cli-action@v1 | |
| with: | |
| version: 2 | |
| arch: arm64 | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: Altinity/ClickHouse | |
| path: ClickHouse | |
| - name: Install required packages | |
| run: | | |
| echo "Installing required packages..." | |
| sudo add-apt-repository -y universe | |
| sudo apt-get update | |
| sudo apt-get install -y software-properties-common python3-pip apt-utils gnupg rpm createrepo-c file wget | |
| wget -q http://archive.ubuntu.com/ubuntu/pool/universe/d/dpkg-sig/dpkg-sig_0.13.1+nmu4_all.deb | |
| sudo dpkg -i dpkg-sig_0.13.1+nmu4_all.deb || sudo apt-get install -f -y | |
| rm dpkg-sig_0.13.1+nmu4_all.deb | |
| sudo apt-add-repository --yes --update ppa:ansible/ansible | |
| sudo apt-get install -y ansible | |
| sudo ln -s /usr/bin/createrepo_c /usr/bin/createrepo | |
| pip3 install boto3 botocore natsort --break-system-packages | |
| - name: Download packages | |
| run: | | |
| if ! aws s3 sync "${DEST_URL}/packages/ARM_PACKAGES/" /home/runner/.cache/tmp/packages --exact-timestamps; then | |
| echo "Failed to download ARM packages" | |
| exit 1 | |
| fi | |
| if ! aws s3 sync "${DEST_URL}/packages/AMD_PACKAGES/" /home/runner/.cache/tmp/packages --exact-timestamps; then | |
| echo "Failed to download AMD packages" | |
| exit 1 | |
| fi | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| - name: Set up GPG passphrase | |
| run: | | |
| if [ "${RELEASE_ENVIRONMENT}" == "production" ]; then | |
| if [ -z "${{ inputs.GPG_PASSPHRASE }}" ]; then | |
| echo "Error: GPG_PASSPHRASE is required for production releases" | |
| exit 1 | |
| fi | |
| echo "${{ inputs.GPG_PASSPHRASE }}" > /tmp/gpg_passphrase | |
| else | |
| echo "${{ secrets.GPG_PASSPHRASE }}" > /tmp/gpg_passphrase | |
| fi | |
| chmod 600 /tmp/gpg_passphrase | |
| - name: Sign and publish packages | |
| run: | | |
| if [ ! -f /tmp/gpg_passphrase ]; then | |
| echo "Error: GPG passphrase file not found" | |
| exit 1 | |
| fi | |
| echo "Processing GPG key..." | |
| if [ "${RELEASE_ENVIRONMENT}" == "production" ]; then | |
| if ! aws secretsmanager get-secret-value --secret-id arn:aws:secretsmanager:us-east-1:${{ secrets.SIGNING_PROD_SECRET_ID }} --query SecretString --output text | sed -e "s/^'//" -e "s/'$//" | jq -r '.altinity_prod_gpg | @base64d' | gpg --quiet --batch --import >/dev/null 2>&1; then | |
| echo "Failed to import prod GPG key" | |
| exit 1 | |
| fi | |
| else | |
| if ! aws secretsmanager get-secret-value --secret-id arn:aws:secretsmanager:us-east-1:${{ secrets.SIGNING_STAGING_SECRET_ID }} --query SecretString --output text | sed -e "s/^'//" -e "s/'$//" | jq -r '.altinity_staging_gpg | @base64d' | gpg --quiet --batch --import >/dev/null 2>&1; then | |
| echo "Failed to import staging GPG key" | |
| exit 1 | |
| fi | |
| fi | |
| gpg --quiet --list-secret-keys --with-keygrip >/dev/null 2>&1 | |
| gpgconf --kill gpg-agent >/dev/null 2>&1 | |
| gpg-agent --daemon --allow-preset-passphrase >/dev/null 2>&1 | |
| if ! aws ssm get-parameter --name /gitlab-runner/key-encrypting-key --with-decryption --query Parameter.Value --output text | sudo tee /root/.key-encrypting-key >/dev/null; then | |
| echo "Failed to get key encrypting key" | |
| exit 1 | |
| fi | |
| GPG_KEY_NAME=$(gpg --quiet --list-secret-keys | grep uid | head --lines 1 | tr -s " " | cut -d " " -f 4-) | |
| GPG_KEY_ID=$(gpg --quiet --list-secret-keys --with-keygrip "${GPG_KEY_NAME}" | grep Keygrip | head --lines 1 | tr -s " " | cut -d " " -f 4) | |
| cat /tmp/gpg_passphrase | base64 -d | sudo openssl enc -d -aes-256-cbc -pbkdf2 -pass file:/root/.key-encrypting-key -in - -out - | /usr/lib/gnupg/gpg-preset-passphrase --preset $GPG_KEY_ID >/dev/null 2>&1 | |
| echo "Running Ansible playbook for signing and publishing..." | |
| echo "ansible-playbook -i ClickHouse/tests/ci/release/packaging/ansible/inventory/localhost.yml -e aws_region=$AWS_REGION -e local_repo_path="/home/runner/.cache/${{ inputs.release_environment }}" -e pkgver=\"${PACKAGE_VERSION}\" -e release_environment=$RELEASE_ENVIRONMENT -e repo_dns_name=$REPO_DNS_NAME -e repo_name=$REPO_NAME -e repo_prefix=\"$REPO_PREFIX\" -e repo_subtitle=\"$REPO_SUBTITLE\" -e s3_pkgs_bucket=$S3_STORAGE_BUCKET -e s3_pkgs_path=\"builds/stable/v${PACKAGE_VERSION}/${FOLDER_TIME}\" -e repo_path=\"/home/runner/.cache/${{ inputs.release_environment }}\" ClickHouse/tests/ci/release/packaging/ansible/sign-and-release.yml " | |
| if ! ansible-playbook -i ClickHouse/tests/ci/release/packaging/ansible/inventory/localhost.yml \ | |
| -e aws_region=$AWS_REGION \ | |
| -e gpg_key_id="$GPG_KEY_ID" \ | |
| -e gpg_key_name="$GPG_KEY_NAME" \ | |
| -e local_repo_path="/home/runner/.cache/${{ inputs.release_environment }}" \ | |
| -e pkgver="${PACKAGE_VERSION}" \ | |
| -e release_environment=$RELEASE_ENVIRONMENT \ | |
| -e repo_dns_name=$REPO_DNS_NAME \ | |
| -e repo_name=$REPO_NAME \ | |
| -e repo_prefix="$REPO_PREFIX" \ | |
| -e repo_subtitle="$REPO_SUBTITLE" \ | |
| -e s3_pkgs_bucket=$S3_STORAGE_BUCKET \ | |
| -e s3_pkgs_path="builds/stable/v${PACKAGE_VERSION}/${FOLDER_TIME}" \ | |
| ClickHouse/tests/ci/release/packaging/ansible/sign-and-release.yml; then | |
| echo "Ansible playbook failed" | |
| exit 1 | |
| fi | |
| gpgconf --kill gpg-agent | |
| - name: Cleanup temporary files | |
| if: always() | |
| run: | | |
| echo "Cleaning up temporary files..." | |
| rm -rf /home/runner/.cache/tmp/packages || true | |
| rm -f /tmp/gpg_passphrase || true | |
| repo-sanity-check: | |
| needs: sign-and-publish | |
| uses: ./.github/workflows/repo-sanity-checks.yml | |
| copy-to-released: | |
| needs: [extract-package-info, sign-and-publish] | |
| if: ${{ inputs.release_environment == 'production' }} | |
| runs-on: [altinity-style-checker-aarch64, altinity-on-demand] | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| PACKAGE_VERSION: ${{ inputs.package_version }} | |
| DEST_URL: ${{ needs.extract-package-info.outputs.dest_url }} | |
| steps: | |
| - name: Install aws cli | |
| uses: unfor19/install-aws-cli-action@v1 | |
| with: | |
| version: 2 | |
| arch: arm64 | |
| - name: Copy to released directory | |
| run: | | |
| echo "Copying to released directory..." | |
| echo "Source: ${DEST_URL}/" | |
| echo "Destination: s3://${S3_STORAGE_BUCKET}/builds/released/v${PACKAGE_VERSION}/" | |
| if ! aws s3 sync "${DEST_URL}/" "s3://${S3_STORAGE_BUCKET}/builds/released/v${PACKAGE_VERSION}/" --no-progress; then | |
| echo "Failed to copy to released directory" | |
| exit 1 | |
| fi | |
| echo "Verifying copy operation..." | |
| if ! aws s3 ls "s3://${S3_STORAGE_BUCKET}/builds/released/v${PACKAGE_VERSION}/" | grep -q "packages"; then | |
| echo "Error: Packages directory not found in destination" | |
| exit 1 | |
| fi |