Skip to content

Sign and Release packages #183

Sign and Release packages

Sign and Release packages #183

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