diff --git a/.github/workflows/build-cloud-qa.yaml b/.github/workflows/build-cloud-qa.yaml new file mode 100644 index 0000000000..0af9a20266 --- /dev/null +++ b/.github/workflows/build-cloud-qa.yaml @@ -0,0 +1,68 @@ +name: Weekly Docker Image Build - perconalab/cloud-qa + +on: + schedule: + - cron: '0 3 * * 1' # Every Monday at 03:00 UTC + workflow_dispatch: + +jobs: + build-and-push: + runs-on: ubuntu-latest + + env: + DOCKER_PROJECT: perconalab/cloud-qa + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + + steps: + - name: Fetch latest version tag from Docker Hub + id: get_version + run: | + echo "Fetching tags of $DOCKER_PROJECT from Docker Hub..." + + RESPONSE=$(curl -s -u "$DOCKERHUB_USERNAME:$DOCKERHUB_TOKEN" \ + "https://hub.docker.com/v2/repositories/$DOCKER_PROJECT/tags/?page_size=100") + + TAGS=$(echo "$RESPONSE" | jq -r '.results // [] | .[].name') + + if [ -z "$TAGS" ]; then + echo "No tags found. Starting from v1.0.0" + NEW_VERSION="v1.0.0" + else + LATEST_TAG=$(echo "$TAGS" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1) + echo "Latest tag found: $LATEST_TAG" + + if [ -z "$LATEST_TAG" ]; then + NEW_VERSION="v1.0.0" + else + IFS='.' read -r MAJOR MINOR PATCH <<< "${LATEST_TAG#v}" + PATCH=$((PATCH + 1)) + NEW_VERSION="v$MAJOR.$MINOR.$PATCH" + fi + fi + + echo "New version will be: $NEW_VERSION" + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ env.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ./cloud/jenkins/docker + file: ./cloud/jenkins/docker/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: | + ${{ env.DOCKER_PROJECT }}:latest + ${{ env.DOCKER_PROJECT }}:${{ steps.get_version.outputs.new_version }} diff --git a/cloud/jenkins/docker/Dockerfile b/cloud/jenkins/docker/Dockerfile new file mode 100644 index 0000000000..a88f546756 --- /dev/null +++ b/cloud/jenkins/docker/Dockerfile @@ -0,0 +1,91 @@ +FROM debian:bullseye-slim + +ARG TARGETARCH +ENV DEBIAN_FRONTEND=noninteractive +ENV HOME=/home/clouduser +ENV PATH="${HOME}/.local/bin:${HOME}/.venv/bin:${HOME}/.local/bin/google-cloud-sdk/bin:${PATH}" +ENV CLOUDSDK_CORE_DISABLE_PROMPTS=1 +WORKDIR "${HOME}" + +# Base dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + sudo bash curl wget tar unzip git python3 python3-venv python3-pip jq gnupg ca-certificates docker.io && \ + ln -sf /bin/bash /bin/sh && \ + curl -L "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${TARGETARCH}" -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Cloud user +RUN useradd -u 1000 -m clouduser && \ + echo "clouduser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \ + mkdir -p "${HOME}/.local/bin" && \ + chown -R clouduser:clouduser /home/clouduser +USER clouduser + +# Create a checksum verifier for downloaded files +COPY --chmod=755 install_dependency.sh $HOME/.local/bin/install_dependency + +# Install yq, uv, Helm, kubectl, kuttl, kubectl-assert, +RUN set -euo pipefail && \ + # uv + UV_LATEST="$(curl -s https://api.github.com/repos/astral-sh/uv/releases/latest | jq -r .tag_name)" && \ + UV_ARCH="${TARGETARCH/amd64/x86_64}" && UV_ARCH="${UV_ARCH/arm64/aarch64}" && \ + install_dependency "https://github.com/astral-sh/uv/releases/download/${UV_LATEST}/uv-${UV_ARCH}-unknown-linux-gnu.tar.gz" && \ + uv --version && \ + # Helm + HELM_VERSION="$(curl -sSL https://api.github.com/repos/helm/helm/releases/latest | jq -r '.tag_name')" && \ + install_dependency "https://get.helm.sh/helm-${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" && \ + helm version --client && \ + # kubectl + KUBECTL_VERSION="$(curl -L -s https://dl.k8s.io/release/stable.txt)" && \ + install_dependency "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" && \ + kubectl version --client && \ + # kubectl-assert - has no published checksum + KUTTL_VERSION="$(curl -s https://api.github.com/repos/kudobuilder/kuttl/releases/latest | jq -r .tag_name)" && \ + KUTTL_ARCH="${TARGETARCH/amd64/x86_64}" && \ + KUTTL_CHECKSUM="https://github.com/kudobuilder/kuttl/releases/download/${KUTTL_VERSION}/checksums.txt" && \ + KUTTL_FILENAME="kubectl-kuttl_${KUTTL_VERSION#v}_linux_${KUTTL_ARCH}" && \ + install_dependency "https://github.com/kudobuilder/kuttl/releases/download/${KUTTL_VERSION}/${KUTTL_FILENAME}" "1" "${KUTTL_CHECKSUM}" && \ + mv "$HOME/.local/bin/$KUTTL_FILENAME" "$HOME/.local/bin/kubectl-kuttl" && \ + kubectl-kuttl --version && \ + # kubectl-assert - has no published checksum + install_dependency "https://raw.githubusercontent.com/morningspace/kubeassert/master/kubectl-assert.sh" "0" && \ + mv "$HOME/.local/bin/kubectl-assert.sh" "$HOME/.local/bin/kubectl-assert" && \ + kubectl-assert --version + +# Install clients: eksctl, doctl, gcloud +RUN set -euo pipefail && \ + # AWS client + EKS_VERSION=$(curl -s https://api.github.com/repos/eksctl-io/eksctl/releases/latest | jq -r .tag_name) && \ + EKS_CHECKSUM="https://github.com/eksctl-io/eksctl/releases/download/${EKS_VERSION}/eksctl_checksums.txt" && \ + EKS_FILENAME="eksctl_linux_${TARGETARCH}.tar.gz" && \ + install_dependency "https://github.com/eksctl-io/eksctl/releases/download/${EKS_VERSION}/$EKS_FILENAME" "1" "${EKS_CHECKSUM}" "0" && \ + chmod +x "${HOME}/.local/bin/eksctl" && eksctl version && \ + # Digital Ocean client + DO_LATEST=$(curl -s https://api.github.com/repos/digitalocean/doctl/releases/latest | jq -r '.tag_name') && \ + DO_CHECKSUM="https://github.com/digitalocean/doctl/releases/download/${DO_LATEST}/doctl-${DO_LATEST#v}-checksums.sha256" && \ + DO_FILENAME="doctl-${DO_LATEST#v}-linux-${TARGETARCH}.tar.gz" && \ + install_dependency "https://github.com/digitalocean/doctl/releases/download/${DO_LATEST}/${DO_FILENAME}" "1" "${DO_CHECKSUM}" "0" && \ + chmod +x "${HOME}/.local/bin/doctl" && doctl version && \ + # Google client + GC_LATEST=$(curl -sSL https://dl.google.com/dl/cloudsdk/channels/rapid/components-2.json | jq -r '.version') && \ + install_dependency "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-${GC_LATEST}-linux-x86_64.tar.gz" "0" "" "0" && \ + "${HOME}/.local/bin/google-cloud-sdk/install.sh" --quiet && \ + "${HOME}/.local/bin/google-cloud-sdk/bin/gcloud" components install gke-gcloud-auth-plugin --quiet && gcloud --version && \ + # Minikube + install_dependency "https://storage.googleapis.com/minikube/releases/latest/minikube-linux-${TARGETARCH}" && \ + mv "$HOME/.local/bin/minikube-linux-${TARGETARCH}" "$HOME/.local/bin/minikube" && minikube version + +# Azure cli requires root +USER root +RUN echo "Installing Azure client" && \ + curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /etc/apt/trusted.gpg.d/microsoft.gpg && \ + AZ_REPO=$(grep VERSION_CODENAME= /etc/os-release | cut -d= -f2) && \ + echo "deb [arch=$(dpkg --print-architecture)] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" > /etc/apt/sources.list.d/azure-cli.list && \ + apt-get update && apt-get install -y --no-install-recommends azure-cli && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +USER clouduser +WORKDIR /home/clouduser +ENV PATH="${HOME}/.local/bin:${HOME}/.venv/bin:${HOME}/.local/bin/google-cloud-sdk/bin:${PATH}" +ENTRYPOINT ["/bin/bash", "-c"] \ No newline at end of file diff --git a/cloud/jenkins/docker/install_dependency.sh b/cloud/jenkins/docker/install_dependency.sh new file mode 100755 index 0000000000..c7452dc96d --- /dev/null +++ b/cloud/jenkins/docker/install_dependency.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -euo pipefail + +url="$1" +verify="${2:-1}" +checksum_url="${3:-}" +strip_components="${4:-1}" + +file="$(basename "$url")" +bin_folder="$HOME/.local/bin" +verified_checksum=0 + +verify_checksum() { + local local_checksum_url="$1" + local checksum_file="checksum_file" + if curl -fsSL "$local_checksum_url" -o "$checksum_file"; then + if grep -q " " "$checksum_file"; then + new_checksum=$(grep -i "$(basename "$url")" "$checksum_file" | awk '{print $1}') + echo "$new_checksum" > "$checksum_file" + fi + echo "$(cat $checksum_file) $file" > "$checksum_file" + echo "Filtered checksum file is: $(cat $checksum_file)" + sha256sum -c "$checksum_file" && verified_checksum=1 + fi +} + +checksum_verification() { + echo "Verifying checksum" + if [[ -n "$checksum_url" ]]; then + verify_checksum "$checksum_url" + else + for type in sha256 sha256sum sha256.txt; do + verify_checksum "$url.$type" + [[ $verified_checksum -eq 1 ]] && break + done + fi + [[ $verified_checksum -eq 1 ]] && echo "Checksum verified" || { echo "Checksum mismatch!"; exit 1; } +} + +echo "Downloading $url" +curl -fsSL "$url" -o "$file" +[[ ! -f "$file" ]] && echo "File not downloaded!" && exit 1 +[[ "$verify" == "1" ]] && checksum_verification + +mkdir -p "$bin_folder" +if [[ "$file" == *.tar.gz ]]; then + echo "Extracting $file → $bin_folder" + tar -xzf "$file" -C "$bin_folder" --strip-components="$strip_components" + echo "Extracted into $bin_folder" +else + echo "Installing $file → $bin_folder" + mv "$file" "$bin_folder/" && chmod 755 "$bin_folder/$file" +fi