Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
self-hosted-runner:
labels:
- large
config-variables: null
3 changes: 3 additions & 0 deletions .github/actions/append-encrypted-artifacts-help/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# append-encrypted-artifacts-help

Appends `.github/scripts/templates/encrypted-artifacts-help.md` to `GITHUB_STEP_SUMMARY`.
15 changes: 15 additions & 0 deletions .github/actions/append-encrypted-artifacts-help/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Append encrypted artifacts help
description: Append encrypted artifact decryption instructions to the job summary.
inputs:
template:
description: Help template path.
required: false
default: .github/scripts/templates/encrypted-artifacts-help.md
runs:
using: composite
steps:
- name: Append encrypted artifacts help
shell: bash
env:
TEMPLATE: ${{ inputs.template }}
run: cat "$TEMPLATE" >> "$GITHUB_STEP_SUMMARY"
3 changes: 3 additions & 0 deletions .github/actions/gen-run-id/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# gen-run-id

Generates the `date_start` and `randuuid4c` outputs used to name E2E namespaces and artifacts.
18 changes: 18 additions & 0 deletions .github/actions/gen-run-id/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Generate E2E run id
description: Generate timestamp and short random suffix for E2E workflow runs.
outputs:
date_start:
description: Timestamp in %Y%m%d-%H%M%S format.
value: ${{ steps.vars.outputs.date-start }}
randuuid4c:
description: Four random hexadecimal characters.
value: ${{ steps.vars.outputs.randuuid4c }}
runs:
using: composite
steps:
- name: Generate run id
id: vars
shell: bash
run: |
echo "date-start=$(date +%Y%m%d-%H%M%S)" >> "$GITHUB_OUTPUT"
echo "randuuid4c=$(openssl rand -hex 2)" >> "$GITHUB_OUTPUT"
5 changes: 5 additions & 0 deletions .github/actions/gpg-encrypt-and-upload/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# gpg-encrypt-and-upload

Encrypts artifacts with GPG symmetric AES256 encryption and uploads the resulting `.gpg` file.

Set `archive: "true"` for directory or multi-path inputs that should be zipped before encryption. Set `archive: "false"` for a single file such as a kubeconfig.
81 changes: 81 additions & 0 deletions .github/actions/gpg-encrypt-and-upload/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: GPG encrypt and upload
description: Encrypt a file or archived paths with GPG and upload the encrypted artifact.
inputs:
path:
description: File path to encrypt, or paths to zip when archive is true.
required: true
passphrase:
description: GPG symmetric encryption passphrase.
required: true
artifact_name:
description: Base artifact name without .gpg suffix.
required: true
working-directory:
description: Directory used for archive path resolution.
required: false
default: "."
archive:
description: Zip the provided paths before encryption.
required: false
default: "true"
retention-days:
description: Artifact retention in days.
required: false
default: "3"
overwrite:
description: Whether to overwrite an existing artifact.
required: false
default: "true"
include-hidden-files:
description: Whether upload-artifact includes hidden files.
required: false
default: "true"
runs:
using: composite
steps:
- name: Encrypt artifact
id: encrypt
shell: bash
env:
ARTIFACT_NAME: ${{ inputs.artifact_name }}
ARCHIVE: ${{ inputs.archive }}
GPG_PASSPHRASE: ${{ inputs.passphrase }}
INPUT_PATH: ${{ inputs.path }}
WORKING_DIRECTORY: ${{ inputs.working-directory }}
run: |
if [ "$ARCHIVE" = "true" ]; then
pushd "$WORKING_DIRECTORY"
# INPUT_PATH intentionally supports a whitespace-separated path list.
zip -r "$RUNNER_TEMP/${ARTIFACT_NAME}.zip" $INPUT_PATH
popd
input_file="$RUNNER_TEMP/${ARTIFACT_NAME}.zip"
encrypted_file="$RUNNER_TEMP/${ARTIFACT_NAME}.zip.gpg"
upload_name="${ARTIFACT_NAME}.zip.gpg"
else
input_file="$INPUT_PATH"
encrypted_file="$RUNNER_TEMP/${ARTIFACT_NAME}.gpg"
upload_name="${ARTIFACT_NAME}.gpg"
fi

gpg --symmetric --batch --yes --pinentry-mode loopback \
--passphrase "$GPG_PASSPHRASE" \
--cipher-algo AES256 \
--output "$encrypted_file" \
"$input_file"

if [ "$ARCHIVE" = "true" ]; then
rm -f "$input_file"
fi

echo "encrypted_path=$encrypted_file" >> "$GITHUB_OUTPUT"
echo "upload_name=$upload_name" >> "$GITHUB_OUTPUT"

- name: Upload encrypted artifact
uses: actions/upload-artifact@v7
with:
name: ${{ steps.encrypt.outputs.upload_name }}
path: ${{ steps.encrypt.outputs.encrypted_path }}
overwrite: ${{ inputs.overwrite }}
include-hidden-files: ${{ inputs.include-hidden-files }}
retention-days: ${{ inputs.retention-days }}
archive: false
5 changes: 5 additions & 0 deletions .github/actions/setup-e2e-toolchain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# setup-e2e-toolchain

Installs the common E2E workflow toolchain: checkout, Task, deckhouse-cli (`d8`), and kubectl.

Use `checkout: "false"` for jobs that already checked out the repository before calling this action.
56 changes: 56 additions & 0 deletions .github/actions/setup-e2e-toolchain/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Setup E2E toolchain
description: Checkout repository and install common E2E CLI tools.
inputs:
checkout:
description: Run actions/checkout before installing tools.
required: false
default: "true"
task-version:
description: go-task version to install.
required: false
default: 3.x
d8-version:
description: deckhouse-cli version to install.
required: false
default: v0.29.24
install-kubectl:
description: Install kubectl via azure/setup-kubectl.
required: false
default: "true"
github-token:
description: GitHub token passed to go-task/setup-task.
required: false
default: ""
runs:
using: composite
steps:
- name: Checkout
if: inputs.checkout == 'true'
uses: actions/checkout@v6

- name: Install Task
uses: go-task/setup-task@v2
with:
version: ${{ inputs.task-version }}
repo-token: ${{ inputs.github-token }}

- name: Restore d8 cache
id: d8-cache
uses: actions/cache@v4
with:
path: /opt/deckhouse/bin/d8
key: d8-${{ inputs.d8-version }}-${{ runner.os }}

- name: Setup d8
if: steps.d8-cache.outputs.cache-hit != 'true'
uses: ./.github/actions/install-d8
with:
version: ${{ inputs.d8-version }}

- name: Add d8 to PATH
shell: bash
run: echo "/opt/deckhouse/bin" >> "$GITHUB_PATH"

- name: Install kubectl CLI
if: inputs.install-kubectl == 'true'
uses: azure/setup-kubectl@v4
3 changes: 3 additions & 0 deletions .github/actions/use-nested-kubeconfig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# use-nested-kubeconfig

Decodes the nested-cluster kubeconfig used by E2E workflows into `~/.kube/config`, fixes permissions, selects the requested context, and can wait for `kubectl get nodes` to succeed.
58 changes: 58 additions & 0 deletions .github/actions/use-nested-kubeconfig/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Use nested kubeconfig
description: Decode a nested-cluster kubeconfig and optionally wait for the API.
inputs:
kubeconfig:
description: Double-base64 encoded kubeconfig.
required: true
context:
description: Context to select after writing kubeconfig.
required: false
default: nested-e2e-nested-sa
check-api:
description: Run kubectl get nodes with retries.
required: false
default: "true"
attempts:
description: Number of kubectl get nodes attempts.
required: false
default: "30"
delay-seconds:
description: Delay between API check attempts.
required: false
default: "10"
runs:
using: composite
steps:
- name: Configure nested kubeconfig
shell: bash
env:
KUBECONFIG_B64: ${{ inputs.kubeconfig }}
KUBECONTEXT: ${{ inputs.context }}
CHECK_API: ${{ inputs.check-api }}
ATTEMPTS: ${{ inputs.attempts }}
DELAY_SECONDS: ${{ inputs.delay-seconds }}
run: |
mkdir -p ~/.kube
echo "[INFO] Configure kubeconfig for nested cluster"
printf '%s' "$KUBECONFIG_B64" | base64 -d | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config

if [ -n "$KUBECONTEXT" ]; then
kubectl config use-context "$KUBECONTEXT"
fi

if [ "$CHECK_API" != "true" ]; then
exit 0
fi

for i in $(seq 1 "$ATTEMPTS"); do
echo "[INFO] Check nested kube-api availability ${i}/${ATTEMPTS}"
if kubectl get nodes; then
echo "[SUCCESS] Nested kube-api is available"
exit 0
fi
sleep "$DELAY_SECONDS"
done

echo "[ERROR] Nested kube-api is not available"
exit 1
85 changes: 85 additions & 0 deletions .github/scripts/bash/e2e/cleanup-nightly-resources.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash

# Copyright 2026 Flant JSC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -Eeuo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=.github/scripts/bash/e2e/common.sh
source "${SCRIPT_DIR}/common.sh"

LABEL_SELECTOR="${LABEL_SELECTOR:-test=nightly-e2e}"
KEEP_HOURS="${KEEP_HOURS:-47}"
FRIDAY_KEEP_HOURS="${FRIDAY_KEEP_HOURS:-71}"

current_date_seconds="$(date -u +%s)"

collect_items_json() {
local resource="$1"

kubectl get "${resource}" -l "${LABEL_SELECTOR}" -o json \
| jq -c '.items[] | {name: .metadata.name, created_at: .metadata.creationTimestamp}'
}

should_keep() {
local created_at="$1"
local resource_created_at_seconds
local age_seconds
local weekday_of_day

resource_created_at_seconds="$(date -d "${created_at}" -u +%s)"
age_seconds="$(( current_date_seconds - resource_created_at_seconds ))"
weekday_of_day="$(date -d "${created_at}" -u +%u)"

if [ "${age_seconds}" -lt "$(( KEEP_HOURS * 3600 ))" ]; then
echo "keep"
return 0
fi

if [ "${weekday_of_day}" -eq 5 ] && [ "${age_seconds}" -lt "$(( FRIDAY_KEEP_HOURS * 3600 ))" ]; then
echo "keep"
return 0
fi

echo "delete"
}

cleanup_kind() {
local kind="$1"
local item
local name
local created_at
local decision

echo "[INFO] Process ${kind} with label ${LABEL_SELECTOR}"
collect_items_json "${kind}" | while read -r item; do
name="$(echo "${item}" | jq -r '.name')"
created_at="$(echo "${item}" | jq -r '.created_at')"
[ -z "${name}" ] && continue

decision="$(should_keep "${created_at}")"
if [ "${decision}" = "keep" ]; then
printf "%-63s %22s\n" "[INFO] Keep ${kind}/${name}:" "created_at ${created_at}"
continue
fi

printf "%-63s %22s\n" "[INFO] Delete ${kind}/${name}:" "created_at ${created_at}"
kubectl delete "${kind}" "${name}" --timeout=300s || true
done || true
}

cleanup_kind "namespaces"
echo " "
cleanup_kind "vmclass"
2 changes: 2 additions & 0 deletions .github/scripts/bash/e2e/common.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env bash

set -Eeuo pipefail

on_error() {
local exit_code=$?
echo "[ERROR] Command failed with exit code ${exit_code} at line ${BASH_LINENO[0]}: ${BASH_COMMAND}" >&2
Expand Down
Loading
Loading