diff --git a/.github/workflows/gha-e2e-tests.yaml b/.github/workflows/gha-e2e-tests.yaml index 07e2ccfe..0a5b1b66 100644 --- a/.github/workflows/gha-e2e-tests.yaml +++ b/.github/workflows/gha-e2e-tests.yaml @@ -26,6 +26,30 @@ concurrency: cancel-in-progress: true jobs: + default-setup2: + runs-on: ubuntu-latest + timeout-minutes: 20 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + env: + TARGET_ORG: ${{ env.TARGET_ORG }} + TARGET_REPO: ${{ env.TARGET_REPO }} + steps: + - uses: actions/checkout@v3 + with: + ref: ${{github.head_ref}} + + - name: Get configure token + id: config-token + uses: peter-murray/workflow-application-token-action@8e1ba3bf1619726336414f1014e37f17fbadf1db + with: + application_id: ${{ secrets.E2E_TESTS_ACCESS_APP_ID }} + application_private_key: ${{ secrets.E2E_TESTS_ACCESS_PK }} + organization: ${{ env.TARGET_ORG }} + + - name: Run default setup test + run: hack/e2e-test.sh default-setup + shell: bash + default-setup: runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/Makefile b/Makefile index ce92592b..f7f1ef7e 100644 --- a/Makefile +++ b/Makefile @@ -300,6 +300,10 @@ acceptance/runner/startup: e2e: go test -count=1 -v -timeout 600s -run '^TestE2E$$' ./test/e2e +.PHONY: gha-e2e +gha-e2e: + bash hack/e2e-test.sh + # Upload release file to GitHub. github-release: release ghr ${VERSION} release/ diff --git a/hack/e2e-test.sh b/hack/e2e-test.sh new file mode 100755 index 00000000..d532662e --- /dev/null +++ b/hack/e2e-test.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +DIR="$(dirname "${BASH_SOURCE[0]}")" + +DIR="$(realpath "${DIR}")" + +TEST_DIR="$(realpath "${DIR}/../test/actions.github.com")" + +export PLATFORMS="linux/amd64" + +TARGETS=() + +function set_targets() { + local cases="$(find "${TEST_DIR}" -name '*.test.sh' | sed "s#^${TEST_DIR}/##g" )" + + mapfile -t TARGETS < <(echo "${cases}") + + echo $TARGETS +} + +function env_test() { + if [[ -z "${GITHUB_TOKEN}" ]]; then + echo "Error: GITHUB_TOKEN is not set" + exit 1 + fi + + if [[ -z "${TARGET_ORG}" ]]; then + echo "Error: TARGET_ORG is not set" + exit 1 + fi + + if [[ -z "${TARGET_REPO}" ]]; then + echo "Error: TARGET_REPO is not set" + exit 1 + fi +} + +function usage() { + echo "Usage: $0 [test_name]" + echo " test_name: the name of the test to run" + echo " if not specified, all tests will be run" + echo " test_name should be the name of the test file without the .test.sh suffix" + echo "" + exit 1 +} + +function main() { + local failed=() + + env_test + + if [[ -z "${1}" ]]; then + echo "Running all tests" + set_targets + elif [[ -f "${TEST_DIR}/${1}.test.sh" ]]; then + echo "Running test ${1}" + TARGETS=("${1}.test.sh") + else + usage + fi + + for target in "${TARGETS[@]}"; do + echo "============================================================" + test="${TEST_DIR}/${target}" + if [[ ! -x "${test}" ]]; then + echo "Error: test ${test} is not executable or not found" + failed+=("${test}") + continue + fi + + echo "Running test ${target}" + if ! "${test}"; then + failed+=("${target}") + echo "---------------------------------" + echo "FAILED: ${target}" + else + echo "---------------------------------" + echo "PASSED: ${target}" + fi + echo "============================================================" + done + + if [[ "${#failed[@]}" -gt 0 ]]; then + echo "Failed tests:" + for fail in "${failed[@]}"; do + echo " ${fail}" + done + exit 1 + fi +} + +main $@ diff --git a/test/actions.github.com/default-setup.test.sh b/test/actions.github.com/default-setup.test.sh new file mode 100755 index 00000000..e09a6238 --- /dev/null +++ b/test/actions.github.com/default-setup.test.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +set -e + +DIR="$(dirname "${BASH_SOURCE[0]}")" + +DIR="$(realpath "${DIR}")" + +ROOT_DIR="$(realpath "${DIR}/../..")" + +source "${DIR}/helper.sh" + +SCALE_SET_NAME="default-$(date +'%M%S')$(((${RANDOM} + 100) % 100 + 1))" +SCALE_SET_NAMESPACE="arc-runners" +WORKFLOW_FILE="arc-test-workflow.yaml" +ARC_NAME="arc" +ARC_NAMESPACE="arc-systems" + +function install_arc() { + echo "Creating namespace ${ARC_NAMESPACE}" + kubectl create namespace "${SCALE_SET_NAMESPACE}" + + echo "Installing ARC" + helm install "${ARC_NAME}" \ + --namespace "${ARC_NAMESPACE}" \ + --create-namespace \ + --set image.repository="${IMAGE_NAME}" \ + --set image.tag="${IMAGE_TAG}" \ + ${ROOT_DIR}/charts/gha-runner-scale-set-controller \ + --debug + + if ! NAME="${ARC_NAME}" NAMESPACE="${ARC_NAMESPACE}" wait_for_arc; then + NAMESPACE="${ARC_NAMESPACE}" log_arc + return 1 + fi +} + +function install_scale_set() { + echo "Installing scale set ${SCALE_SET_NAMESPACE}/${SCALE_SET_NAME}" + helm install "${SCALE_SET_NAME}" \ + --namespace "${SCALE_SET_NAMESPACE}" \ + --create-namespace \ + --set githubConfigUrl="https://github.com/${TARGET_ORG}/${TARGET_REPO}" \ + --set githubConfigSecret.github_token="${GITHUB_TOKEN}" \ + ${ROOT_DIR}/charts/gha-runner-scale-set \ + --version="${VERSION}" \ + --debug + + if ! NAME="${SCALE_SET_NAME}" NAMESPACE="${ARC_NAMESPACE}" wait_for_scale_set; then + NAMESPACE="${ARC_NAMESPACE}" log_arc + return 1 + fi +} + +function main() { + local failed=() + + build_image + create_cluster + + install_arc + install_scale_set + + WORKFLOW_FILE="${WORKFLOW_FILE}" SCALE_SET_NAME="${SCALE_SET_NAME}" run_workflow || failed+=("run_workflow") + + INSTALLATION_NAME="${SCALE_SET_NAME}" NAMESPACE="${SCALE_SET_NAMESPACE}" cleanup_scale_set || failed+=("cleanup_scale_set") + + NAMESPACE="${ARC_NAMESPACE}" log_arc || failed+=("log_arc") + + delete_cluster + + print_results "${failed[@]}" +} + +main diff --git a/test/actions.github.com/envrc.example b/test/actions.github.com/envrc.example new file mode 100644 index 00000000..5db64c18 --- /dev/null +++ b/test/actions.github.com/envrc.example @@ -0,0 +1,3 @@ +export TARGET_ORG="org" +export TARGET_REPO="repo" +export GITHUB_TOKEN="token" diff --git a/test/actions.github.com/helper.sh b/test/actions.github.com/helper.sh new file mode 100644 index 00000000..af1dc6ae --- /dev/null +++ b/test/actions.github.com/helper.sh @@ -0,0 +1,165 @@ +#!/bin/bash + +DIR="$(dirname "${BASH_SOURCE[0]}")" + +DIR="$(realpath "${DIR}")" + +ROOT_DIR="$(realpath "${DIR}/../..")" + +export TARGET_ORG="${TARGET_ORG:-actions-runner-controller}" +export TARGET_REPO="${TARGET_REPO:-arc_e2e_test_dummy}" +export IMAGE_NAME="${IMAGE_NAME:-arc-test-image}" +export VERSION="${VERSION:-$(yq .version < "${ROOT_DIR}/charts/gha-runner-scale-set-controller/Chart.yaml")}" +export IMAGE_VERSION="${IMAGE_VERSION:-${VERSION}}" + +function build_image() { + echo "Building ARC image ${IMAGE_NAME}:${IMAGE_VERSION}" + + cd ${ROOT_DIR} + + export DOCKER_CLI_EXPERIMENTAL=enabled + export DOCKER_BUILDKIT=1 + docker buildx build --platform ${PLATFORMS} \ + --build-arg RUNNER_VERSION=${RUNNER_VERSION} \ + --build-arg DOCKER_VERSION=${DOCKER_VERSION} \ + --build-arg VERSION=${VERSION} \ + --build-arg COMMIT_SHA=${COMMIT_SHA} \ + -t "${IMAGE_NAME}:${IMAGE_VERSION}" \ + -f Dockerfile \ + . --load + + echo "Created image ${IMAGE_NAME}:${IMAGE_VERSION}" + cd - +} + +function create_cluster() { + echo "Deleting minikube cluster if exists" + minikube delete || true + + echo "Creating minikube cluster" + minikube start + + echo "Loading image into minikube cluster" + minikube image load "${IMAGE_NAME}:${IMAGE_VERSION}" +} + +function delete_cluster() { + echo "Deleting minikube cluster" + minikube delete +} + +function log_arc() { + echo "ARC logs" + kubectl logs -n "${NAMESPACE}" -l app.kubernetes.io/name=gha-rs-controller +} + +function wait_for_arc() { + echo "Waiting for ARC to be ready" + local count=0; + while true; do + POD_NAME=$(kubectl get pods -n ${NAMESPACE} -l app.kubernetes.io/name=gha-rs-controller -o name) + if [ -n "$POD_NAME" ]; then + echo "Pod found: $POD_NAME" + break + fi + if [ "$count" -ge 60 ]; then + echo "Timeout waiting for controller pod with label app.kubernetes.io/name=gha-rs-controller" + return 1 + fi + sleep 1 + count=$((count+1)) + done + + kubectl wait --timeout=30s --for=condition=ready pod -n "${NAMESPACE}" -l app.kubernetes.io/name=gha-rs-controller + kubectl get pod -n "${NAMESPACE}" + kubectl describe deployment "${NAME}" -n "${NAMESPACE}" +} + +function wait_for_scale_set() { + local count=0 + while true; do + POD_NAME=$(kubectl get pods -n ${NAMESPACE} -l actions.github.com/scale-set-name=${NAME} -o name) + if [ -n "$POD_NAME" ]; then + echo "Pod found: ${POD_NAME}" + break + fi + + if [ "$count" -ge 60 ]; then + echo "Timeout waiting for listener pod with label actions.github.com/scale-set-name=${NAME}" + return 1 + fi + + sleep 1 + count=$((count+1)) + done + kubectl wait --timeout=30s --for=condition=ready pod -n ${NAMESPACE} -l actions.github.com/scale-set-name=${NAME} + kubectl get pod -n ${NAMESPACE} +} + +function cleanup_scale_set() { + helm uninstall "${INSTALLATION_NAME}" --namespace "${NAMESPACE}" --debug + + kubectl wait --timeout=40s --for=delete autoscalingrunnersets -n "${NAMESPACE}" -l app.kubernetes.io/instance="${INSTALLATION_NAME}" +} + +function install_openebs() { + echo "Install openebs/dynamic-localpv-provisioner" + helm repo add openebs https://openebs.github.io/charts + helm repo update + helm install openebs openebs/openebs --namespace openebs --create-namespace +} + +function print_results() { + local failed=("$@") + + if [[ "${#failed[@]}" -ne 0 ]]; then + echo "----------------------------------" + echo "The following tests failed:" + for test in "${failed[@]}"; do + echo " - ${test}" + done + return 1 + else + echo "----------------------------------" + echo "All tests passed!" + fi +} + +function run_workflow() { + echo "Checking if the workflow file exists" + gh workflow view -R "${TARGET_ORG}/${TARGET_REPO}" "${WORKFLOW_FILE}" &> /dev/null || return 1 + + local queue_time="$(date -u +%FT%TZ)" + + echo "Running workflow ${workflow_file}" + gh workflow run -R "${TARGET_ORG}/${TARGET_REPO}" "${WORKFLOW_FILE}" --ref main -f arc_name="${SCALE_SET_NAME}" || return 1 + + echo "Waiting for run to start" + local count=0 + local run_id= + while true; do + if [[ "${count}" -ge 12 ]]; then + echo "Timeout waiting for run to start" + return 1 + fi + run_id=$(gh run list -R "${TARGET_ORG}/${TARGET_REPO}" --workflow "${WORKFLOW_FILE}" --created ">${queue_time}" --json "name,databaseId" --jq ".[] | select(.name | contains(\"${SCALE_SET_NAME}\")) | .databaseId") + echo "Run ID: ${run_id}" + if [ -n "$run_id" ]; then + echo "Run found!" + break + fi + + echo "Run not found yet, waiting 5 seconds" + sleep 5 + count=$((count+1)) + done + + echo "Waiting for run to complete" + local code=$(gh run watch "${run_id}" -R "${TARGET_ORG}/${TARGET_REPO}" --exit-status &> /dev/null) + if [[ "${code}" -ne 0 ]]; then + echo "Run failed with exit code ${code}" + return 1 + fi + + echo "Run completed successfully" +}