Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
35d4d75
chore(ci): add e2e shellcheck and actionlint tasks
universal-itengineer May 27, 2026
f1dd87b
chore(ci): rename nightly e2e workflows
universal-itengineer May 27, 2026
5a74ac0
chore(ci): add e2e composite actions
universal-itengineer May 28, 2026
6711875
chore(ci): extract nested VM power-off script
universal-itengineer May 28, 2026
ea4111d
chore(ci): extract e2e wait helpers
universal-itengineer May 28, 2026
266c0db
chore(ci): drop unrelated script changes
universal-itengineer May 28, 2026
2a093cf
chore(ci): extract remaining nightly e2e scripts
universal-itengineer May 28, 2026
a0ee1fe
chore(ci): extract e2e ssh helpers
universal-itengineer May 28, 2026
8f421d1
chore(ci): add e2e workflow lint gates
universal-itengineer May 28, 2026
cc5cfaa
chore(ci): unify e2e nightly action versions
universal-itengineer May 29, 2026
d219a17
refactor conf-vir detect-k8s
universal-itengineer May 29, 2026
e0513e5
fix formatting
universal-itengineer May 29, 2026
65f8eea
chore(ci): reuse e2e scripts in release pipeline
universal-itengineer Jun 1, 2026
277df12
chore(ci): extract shared e2e workflow scripts
universal-itengineer Jun 1, 2026
39fbd69
chore(ci): extract registry login action
universal-itengineer Jun 1, 2026
ae56ee5
chore(ci): extract release e2e scripts
universal-itengineer Jun 1, 2026
52b8b75
fix(ci): correct vm-route-forge path filter
universal-itengineer Jun 1, 2026
9956c2b
refactor(ci): unify e2e values template and parameterize disk size
universal-itengineer Jun 1, 2026
6b39c04
rm task pin ip
universal-itengineer Jun 2, 2026
94ea561
refactor(ci): simplify dvp static values rendering
universal-itengineer Jun 2, 2026
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/registry-login/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# registry-login

Parses a base64-encoded dockerconfigjson secret, logs in to its registry, and exposes the parsed registry host as the `registry` output.

The action keeps the existing E2E workflow parsing approach based on `base64 -d`, `jq`, a second `base64 -d`, and `cut`.
28 changes: 28 additions & 0 deletions .github/actions/registry-login/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Registry login
description: Parse a dockerconfigjson secret, log in to the registry, and expose the registry host.
inputs:
docker_cfg:
description: Base64-encoded dockerconfigjson content.
required: true
outputs:
registry:
description: Registry host parsed from the dockerconfigjson.
value: ${{ steps.login.outputs.registry }}
runs:
using: composite
steps:
- name: Log in to private registry
id: login
shell: bash
env:
DOCKER_CFG: ${{ inputs.docker_cfg }}
run: |
REGISTRY=$(base64 -d <<< "$DOCKER_CFG" | jq '.auths | to_entries | .[] | .key' -r)
USERNAME=$(base64 -d <<< "$DOCKER_CFG" | jq -r '.auths | to_entries | .[] | .value.auth' | base64 -d | cut -d ':' -f1)
PASSWORD=$(base64 -d <<< "$DOCKER_CFG" | jq -r '.auths | to_entries | .[] | .value.auth' | base64 -d | cut -d ':' -f2)

echo "::add-mask::$USERNAME"
echo "::add-mask::$PASSWORD"
echo "$PASSWORD" | docker login "$REGISTRY" --username "$USERNAME" --password-stdin

echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT"
6 changes: 6 additions & 0 deletions .github/actions/setup-e2e-toolchain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 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.
Set `install-htpasswd: "true"` for jobs that need the `htpasswd` utility from `apache2-utils`.
67 changes: 67 additions & 0 deletions .github/actions/setup-e2e-toolchain/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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"
install-htpasswd:
description: Install htpasswd via apache2-utils.
required: false
default: "false"
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

- name: Install htpasswd utility
if: inputs.install-htpasswd == 'true'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y apache2-utils
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"
Loading
Loading