add: HAR traces

This commit is contained in:
“Naeel”
2026-06-30 15:46:41 +04:00
parent 1a52506fb1
commit 250a8a6be2
940 changed files with 14409 additions and 0 deletions
+338
View File
@@ -0,0 +1,338 @@
#!/usr/bin/env python3
"""
Cloud Instance Status Checker
Проверяет статус ресурсов в облаке по манифесту Terraform
"""
import json
import requests
import sys
from typing import Dict, Optional, List
from dataclasses import dataclass
from enum import Enum
from datetime import datetime
class InstanceState(Enum):
"""Возможные состояния инстанса"""
NOT_CREATED = "not_created"
CREATING = "creating"
RUNNING = "running"
MODIFYING = "modifying"
SUSPENDING = "suspending"
SUSPENDED = "suspended"
RESUMING = "resuming"
DELETING = "deleting"
DELETED = "deleted"
OPERATION_PENDING = "operation_pending"
OPERATION_FAILED = "operation_failed"
UNKNOWN = "unknown"
@dataclass
class InstanceInfo:
"""Информация об инстансе"""
uid: str
name: str
state: InstanceState
is_created: bool
is_deleted: bool
is_suspended: bool
uptime_seconds: float
operation_in_progress: bool
operation_pending: bool
last_operation: Optional[str]
last_operation_status: Optional[str]
error_log: Optional[str]
timestamp: str
class CloudInstanceChecker:
"""Проверяет статус инстансов в облаке"""
BASE_URL = "https://deck-api.ngcloud.ru/api/v1/index.cfm"
def __init__(self, token: str):
"""
Инициализация чекера
Args:
token: JWT токен для аутентификации в API
"""
self.token = token
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
})
def find_instance(self, display_name: str, service_type: Optional[str] = None) -> Optional[Dict]:
"""
Найти инстанс по отображаемому имени
Args:
display_name: Имя ресурса в облаке
service_type: Тип сервиса (опционально, для фильтрации)
Returns:
Данные инстанса или None если не найден
"""
try:
params = {
"fields": "instanceConfigDtCreated,instanceUid,displayName,svc,uptime,explainedStatus,svcExtendedName,updaterLogin,updaterShortname,operationIsInProgress,operationIsPending,monitoringUrl",
"search": display_name,
"isAuxiliary": "false",
"isDeleted": "false",
"page": "1",
"pageSize": "100"
}
response = self.session.get(f"{self.BASE_URL}/instances", params=params)
response.raise_for_status()
data = response.json()
results = data.get("results", [])
# Поиск точного совпадения по имени
for instance in results:
if instance.get("displayName") == display_name:
if service_type is None or instance.get("svc") == service_type:
return instance
return None
except requests.RequestException as e:
print(f"❌ Ошибка при поиске инстанса '{display_name}': {e}", file=sys.stderr)
return None
def get_instance_details(self, instance_uid: str) -> Optional[Dict]:
"""
Получить полную информацию об инстансе
Args:
instance_uid: UUID инстанса
Returns:
Полные данные инстанса или None при ошибке
"""
try:
params = {
"fields": "instanceConfigDtCreated,instanceUid,displayName,descr,svc,state,operations,availableOperations,uptime,isDeleted,updaterLogin,updaterShortname,explainedStatus,man,dependencies,dependentInstances,svcExtendedName"
}
response = self.session.get(f"{self.BASE_URL}/instances/{instance_uid}", params=params)
response.raise_for_status()
return response.json().get("instance")
except requests.RequestException as e:
print(f"❌ Ошибка при получении информации об инстансе {instance_uid}: {e}", file=sys.stderr)
return None
def determine_state(self, instance_data: Dict) -> InstanceState:
"""
Определить состояние инстанса на основе флагов
Args:
instance_data: Данные инстанса от API
Returns:
InstanceState enum
"""
# Проверка удаления
if instance_data.get("isDeleted"):
return InstanceState.DELETED
# Проверка создания
if not instance_data.get("isCreated"):
return InstanceState.NOT_CREATED
# Проверка операции в процессе
if instance_data.get("operationIsInProgress"):
operations = instance_data.get("operations", [])
if operations:
last_op = operations[-1]
operation = last_op.get("operation", "unknown")
if operation == "create":
return InstanceState.CREATING
elif operation == "modify":
return InstanceState.MODIFYING
elif operation == "delete":
return InstanceState.DELETING
elif operation == "suspend":
return InstanceState.SUSPENDING
elif operation == "resume":
return InstanceState.RESUMING
# Проверка ожидающей операции
if instance_data.get("operationIsPending"):
operations = instance_data.get("operations", [])
if operations:
last_op = operations[-1]
if not last_op.get("dtStart"):
return InstanceState.OPERATION_PENDING
# Проверка приостановки
if instance_data.get("isSuspended"):
return InstanceState.SUSPENDED
# Проверка основного статуса
if instance_data.get("explainedStatus") == "running":
return InstanceState.RUNNING
# Проверка ошибок в операциях
operations = instance_data.get("operations", [])
if operations:
last_op = operations[-1]
if last_op.get("isSuccessful") == False:
return InstanceState.OPERATION_FAILED
return InstanceState.UNKNOWN
def check_instance(self, resource_name: str, service_type: Optional[str] = None) -> Optional[InstanceInfo]:
"""
Проверить статус инстанса
Args:
resource_name: Имя ресурса в манифесте
service_type: Тип сервиса (опционально)
Returns:
InstanceInfo с информацией о статусе
"""
# Поиск инстанса
instance_short = self.find_instance(resource_name, service_type)
if not instance_short:
return InstanceInfo(
uid="unknown",
name=resource_name,
state=InstanceState.NOT_CREATED,
is_created=False,
is_deleted=False,
is_suspended=False,
uptime_seconds=0,
operation_in_progress=False,
operation_pending=False,
last_operation=None,
last_operation_status=None,
error_log=None,
timestamp=datetime.now().isoformat()
)
# Получить полную информацию
instance_uid = instance_short.get("instanceUid")
instance_full = self.get_instance_details(instance_uid)
if not instance_full:
instance_full = instance_short
# Определить состояние
state = self.determine_state(instance_full)
# Извлечь информацию об операции
operations = instance_full.get("operations", [])
last_operation = None
last_operation_status = None
error_log = None
if operations:
last_op = operations[-1]
last_operation = last_op.get("operation")
last_operation_status = "success" if last_op.get("isSuccessful") == True else "pending" if last_op.get("isSuccessful") is None else "failed"
error_log = last_op.get("errorLog")
return InstanceInfo(
uid=instance_uid,
name=resource_name,
state=state,
is_created=instance_full.get("isCreated", False),
is_deleted=instance_full.get("isDeleted", False),
is_suspended=instance_full.get("isSuspended", False),
uptime_seconds=instance_full.get("uptime", 0),
operation_in_progress=instance_full.get("operationIsInProgress", False),
operation_pending=instance_full.get("operationIsPending", False),
last_operation=last_operation,
last_operation_status=last_operation_status,
error_log=error_log,
timestamp=datetime.now().isoformat()
)
def format_output(self, info: InstanceInfo) -> str:
"""Форматировать результат для вывода"""
# Выбрать эмодзи в зависимости от состояния
emoji_map = {
InstanceState.NOT_CREATED: "",
InstanceState.CREATING: "🔄",
InstanceState.RUNNING: "",
InstanceState.MODIFYING: "🔧",
InstanceState.SUSPENDING: "⏸️",
InstanceState.SUSPENDED: "⏹️",
InstanceState.RESUMING: "▶️",
InstanceState.DELETING: "🗑️",
InstanceState.DELETED: "",
InstanceState.OPERATION_PENDING: "",
InstanceState.OPERATION_FAILED: "💥",
InstanceState.UNKNOWN: "",
}
emoji = emoji_map.get(info.state, "?")
result = f"{emoji} {info.name}: {info.state.value.upper()}"
if info.uid != "unknown":
result += f" (UID: {info.uid[:8]}...)"
if info.last_operation:
result += f" | Last op: {info.last_operation} ({info.last_operation_status})"
if info.uptime_seconds > 0:
uptime_hours = info.uptime_seconds / 3600
result += f" | Uptime: {uptime_hours:.1f}h"
if info.error_log:
result += f"\n ⚠️ Error: {info.error_log[:100]}"
return result
def main():
"""Главная функция"""
# Пример использования
token_file = "/home/naeel/remote_dev/terraform/secrets/prod.token"
try:
with open(token_file, 'r') as f:
token = f.read().strip()
except FileNotFoundError:
print(f"❌ Токен не найден: {token_file}", file=sys.stderr)
sys.exit(1)
checker = CloudInstanceChecker(token)
# Примеры ресурсов для проверки из PROD_STAND/PG1/resources.tf
resources_to_check = [
("pg-testing", "nubes_postgres"),
("lucy11", "nubes_lucee"),
("node_0111", "nubes_nodejs"),
("baba-buck-test2", "nubes_s3bucket"),
]
print("=" * 80)
print("🔍 Cloud Instance Status Report")
print("=" * 80)
print()
for resource_name, service_type in resources_to_check:
print(f"Checking: {resource_name}")
info = checker.check_instance(resource_name, service_type)
if info:
print(checker.format_output(info))
print()
print("=" * 80)
if __name__ == "__main__":
main()
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# Create namespace if missing and apply kustomize overlay for dev
kubectl create namespace terra-dev --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -k k8s/overlays/dev
echo "Applied kustomize overlay for terra-dev."
echo "To init terraform backend for dev: terraform init -reconfigure -backend-config=terraform/backend-dev.hcl"
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
set -e
# Cleanup
rm -rf build_artifacts
mkdir -p build_artifacts
VERSION="${1:-2.0.1}"
# Function to build and zip
build_and_zip() {
OS=$1
ARCH=$2
echo "Building for $OS/$ARCH..."
BINARY_NAME="terraform-provider-nubes_v${VERSION}"
if [ "$OS" == "windows" ]; then
BINARY_NAME="${BINARY_NAME}.exe"
fi
env CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCH go build -o build_artifacts/${BINARY_NAME} .
cd build_artifacts
ZIP_NAME="terraform-provider-nubes_${VERSION}_${OS}_${ARCH}.zip"
# Zip the binary
if [[ "$OS" == "windows" ]]; then
# For windows, we might need zip to handle the .exe extension properly if we were relying on unix permissions, but zip handles it ok.
# Using python zipfile as 'zip' command was missing earlier
python3 -m zipfile -c $ZIP_NAME $BINARY_NAME
else
# Ensure executable permission
chmod +x $BINARY_NAME
python3 -m zipfile -c $ZIP_NAME $BINARY_NAME
fi
# Remove binary to save space/confusion (optional, but cleaner)
rm $BINARY_NAME
cd ..
}
# Build for all targets
build_and_zip linux amd64
build_and_zip windows amd64
build_and_zip darwin amd64
build_and_zip darwin arm64
echo "Calculating SHA256SUMS..."
cd build_artifacts
sha256sum *.zip > terraform-provider-nubes_${VERSION}_SHA256SUMS
echo "Signing SHA256SUMS..."
# Detached binary signature
gpg --batch --detach-sign --default-key 866FD93D456DCA800F2448413EC4673EB798238A --output terraform-provider-nubes_${VERSION}_SHA256SUMS.sig terraform-provider-nubes_${VERSION}_SHA256SUMS
echo "Uploading to S3..."
# Base S3 path
S3_PATH="registry/terraform-registry/terra.k8c.ru/nubes/nubes/${VERSION}/"
# Upload zips
for f in *.zip; do
mc cp $f $S3_PATH
done
# Upload sums and sig
mc cp terraform-provider-nubes_${VERSION}_SHA256SUMS $S3_PATH
mc cp terraform-provider-nubes_${VERSION}_SHA256SUMS.sig $S3_PATH
echo "Done!"
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env bash
set -euo pipefail
# Publish a single rendered docs page (or a small file set) to a specific docs version path.
#
# Usage examples:
# ./scripts/publish-doc-page.sh \
# --profile devops/profiles/test \
# --version 5.0.17 \
# --page 30_registry/guides/getting-started/index.html
#
# ./scripts/publish-doc-page.sh \
# --profile devops/profiles/prod \
# --version 2.1.26 \
# --page 30_registry/guides/getting-started/
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
PROFILE_DIR=""
SITE_DIR="${ROOT_DIR}/site"
PAGE_REL=""
VERSION=""
REGISTRY_HOST="${REGISTRY_HOST:-terra.k8c.ru}"
NAMESPACE="${NAMESPACE:-nubes}"
PROVIDER_NAME="${PROVIDER_NAME:-nubes}"
S3CFG_REGISTRY="${S3CFG_REGISTRY:-${ROOT_DIR}/secrets/.s3cfg_registry}"
usage() {
cat <<'EOF'
Usage:
publish-doc-page.sh --profile <path> --version <ver> --page <relative-path> [--site-dir <dir>]
Required:
--profile Path to stand profile dir (e.g. devops/profiles/test)
--version Docs version (e.g. 5.0.17)
--page Relative path inside built site dir, e.g.:
30_registry/guides/getting-started/index.html
30_registry/guides/getting-started/ (auto -> index.html)
Optional:
--site-dir Built MkDocs site directory (default: <repo>/site)
Behavior:
Uploads exactly one file to:
docs/<namespace>/<provider>/<version>/<page>
in S3 bucket terraform-registry.
EOF
}
resolve_root_path() {
local path_value="$1"
if [[ -z "$path_value" ]]; then
echo ""
return
fi
if [[ "$path_value" = /* ]]; then
echo "$path_value"
return
fi
echo "${ROOT_DIR}/${path_value}"
}
load_s3cfg_registry() {
local cfg="$1"
if [[ ! -f "$cfg" ]]; then
echo "Error: S3 config not found: $cfg" >&2
exit 2
fi
local access_key secret_key host_base use_https endpoint
access_key=$(awk -F '=' '/^\s*access_key\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$cfg")
secret_key=$(awk -F '=' '/^\s*secret_key\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$cfg")
host_base=$(awk -F '=' '/^\s*host_base\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$cfg")
use_https=$(awk -F '=' '/^\s*use_https\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print tolower($2); exit}' "$cfg")
if [[ -z "${S3_ACCESS_KEY:-}" && -n "$access_key" ]]; then
export S3_ACCESS_KEY="$access_key"
fi
if [[ -z "${S3_SECRET_KEY:-}" && -n "$secret_key" ]]; then
export S3_SECRET_KEY="$secret_key"
fi
if [[ -z "${S3_ENDPOINT:-}" && -n "$host_base" ]]; then
if [[ "$host_base" == http* ]]; then
endpoint="$host_base"
else
if [[ "$use_https" == "false" || "$use_https" == "0" || "$use_https" == "no" ]]; then
endpoint="http://${host_base}"
else
endpoint="https://${host_base}"
fi
fi
export S3_ENDPOINT="$endpoint"
fi
if [[ -z "${S3_ENDPOINT:-}" || -z "${S3_ACCESS_KEY:-}" || -z "${S3_SECRET_KEY:-}" ]]; then
echo "Error: S3 credentials are not set (S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY)" >&2
exit 2
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
--profile)
PROFILE_DIR="${2:-}"
shift 2
;;
--version)
VERSION="${2:-}"
shift 2
;;
--page)
PAGE_REL="${2:-}"
shift 2
;;
--site-dir)
SITE_DIR="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Error: unknown argument: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "$PROFILE_DIR" || -z "$VERSION" || -z "$PAGE_REL" ]]; then
echo "Error: --profile, --version and --page are required" >&2
usage
exit 2
fi
PROFILE_DIR="$(resolve_root_path "$PROFILE_DIR")"
SITE_DIR="$(resolve_root_path "$SITE_DIR")"
S3CFG_REGISTRY="$(resolve_root_path "$S3CFG_REGISTRY")"
if [[ ! -d "$PROFILE_DIR" ]]; then
echo "Error: profile directory not found: $PROFILE_DIR" >&2
exit 2
fi
PROFILE_ENV_FILE="${PROFILE_DIR}/profile.env"
if [[ -f "$PROFILE_ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$PROFILE_ENV_FILE"
set +a
fi
# Re-apply defaults from profile if provided
REGISTRY_HOST="${REGISTRY_HOSTNAME:-${REGISTRY_HOST:-$REGISTRY_HOST}}"
NAMESPACE="${NAMESPACE:-$NAMESPACE}"
PROVIDER_NAME="${PROVIDER_NAME:-$PROVIDER_NAME}"
if [[ -n "${S3CFG_REGISTRY:-}" ]]; then
S3CFG_REGISTRY="$(resolve_root_path "${S3CFG_REGISTRY}")"
fi
if [[ ! -d "$SITE_DIR" ]]; then
echo "Error: site dir not found: $SITE_DIR" >&2
exit 2
fi
page="${PAGE_REL#/}"
if [[ "$page" == */ ]]; then
page="${page}index.html"
fi
if [[ "$page" != *.html && "$page" != *.xml && "$page" != *.txt && "$page" != *.json && "$page" != *.css && "$page" != *.js ]]; then
# convenience: if user passed folder-like path without trailing slash
if [[ -f "$SITE_DIR/${page}/index.html" ]]; then
page="${page}/index.html"
fi
fi
SOURCE_FILE="${SITE_DIR}/${page}"
if [[ ! -f "$SOURCE_FILE" ]]; then
echo "Error: page file not found: $SOURCE_FILE" >&2
exit 2
fi
load_s3cfg_registry "$S3CFG_REGISTRY"
MC_ALIAS="registry"
mc alias set "$MC_ALIAS" "$S3_ENDPOINT" "$S3_ACCESS_KEY" "$S3_SECRET_KEY" --api S3v4 >/dev/null
TARGET="${MC_ALIAS}/terraform-registry/docs/${NAMESPACE}/${PROVIDER_NAME}/${VERSION}/${page}"
mc cp "$SOURCE_FILE" "$TARGET"
PUBLIC_TARGET="${MC_ALIAS}/terraform-registry/docs/${NAMESPACE}/${PROVIDER_NAME}/${VERSION}/"
mc policy set public "$PUBLIC_TARGET" >/dev/null || true
echo "Published single page: https://${REGISTRY_HOST}/docs/${NAMESPACE}/${PROVIDER_NAME}/${VERSION}/${page}"
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
# Usage: publish-docs.sh <site-dir> <registry-host> <namespace> <name> <version>
SITE_DIR=${1:-site}
REGISTRY_HOST=${2:-terra.k8c.ru}
NAMESPACE=${3:-nubes}
NAME=${4:-nubes}
VERSION=${5:-dev}
# S3-only variables
ENDPOINT=${S3_ENDPOINT:-}
ACCESS_KEY=${S3_ACCESS_KEY:-}
SECRET_KEY=${S3_SECRET_KEY:-}
if [ -z "$ENDPOINT" ] || [ -z "$ACCESS_KEY" ] || [ -z "$SECRET_KEY" ]; then
echo "Error: S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY must be set"
exit 2
fi
MC_ALIAS=registry
mc alias set $MC_ALIAS "$ENDPOINT" "$ACCESS_KEY" "$SECRET_KEY" --api S3v4
TARGET="${MC_ALIAS}/terraform-registry/docs/${NAMESPACE}/${NAME}/${VERSION}/"
# Create target bucket path if needed (mc will create directories implicitly when copying)
mc cp --recursive "$SITE_DIR/" "$TARGET"
# Optionally set public policy
mc policy set public "$TARGET" || true
echo "Published docs to: https://${REGISTRY_HOST}/docs/${NAMESPACE}/${NAME}/${VERSION}/"
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -euo pipefail
# Пример настройки bucket notifications через mc (S3-compatible)
# Запускать НА УДАЛЁННОЙ машине.
#
# Пример:
# export S3CFG=/home/naeel/terra/terraform/secrets/.s3cfg_registry
# export BUCKET=buck
# export TARGET_ARN='arn:minio:sqs::primary:webhook'
# export EVENTS='put,delete'
# bash scripts/s3_notification_example.sh
#
# Где TARGET_ARN — уже существующий target в конфигурации S3/MinIO.
S3CFG="${S3CFG:-/home/naeel/terra/terraform/secrets/.s3cfg_registry}"
BUCKET="${BUCKET:-}"
TARGET_ARN="${TARGET_ARN:-}"
EVENTS="${EVENTS:-put}" # put | delete | get | put,delete
PREFIX="${PREFIX:-}" # опционально, напр. uploads/
SUFFIX="${SUFFIX:-}" # опционально, напр. .jpg
ALIAS_NAME="${ALIAS_NAME:-regnotif}"
if ! command -v mc >/dev/null 2>&1; then
echo "ERROR: mc не установлен"
exit 1
fi
if [[ ! -f "$S3CFG" ]]; then
echo "ERROR: не найден S3CFG: $S3CFG"
exit 1
fi
if [[ -z "$BUCKET" ]]; then
echo "ERROR: задайте BUCKET"
exit 1
fi
if [[ -z "$TARGET_ARN" ]]; then
echo "ERROR: задайте TARGET_ARN (например arn:minio:sqs::primary:webhook)"
exit 1
fi
# Читаем access_key/secret_key/host_base из .s3cfg
# shellcheck disable=SC2016
readarray -t CFG_LINES < <(python3 - <<PY
import configparser
cfg = configparser.RawConfigParser()
cfg.read("$S3CFG")
sec = cfg["default"]
print(sec.get("access_key", ""))
print(sec.get("secret_key", ""))
print(sec.get("host_base", ""))
PY
)
ACCESS_KEY="${CFG_LINES[0]:-}"
SECRET_KEY="${CFG_LINES[1]:-}"
HOST_BASE="${CFG_LINES[2]:-}"
if [[ -z "$ACCESS_KEY" || -z "$SECRET_KEY" || -z "$HOST_BASE" ]]; then
echo "ERROR: в $S3CFG нет access_key/secret_key/host_base"
exit 1
fi
mc alias rm "$ALIAS_NAME" >/dev/null 2>&1 || true
mc alias set "$ALIAS_NAME" "https://$HOST_BASE" "$ACCESS_KEY" "$SECRET_KEY" >/dev/null
echo "[1/3] Текущие notification-правила в бакете $BUCKET:"
mc event ls "$ALIAS_NAME/$BUCKET" || true
echo "[2/3] Добавление правила: events=$EVENTS target=$TARGET_ARN"
EVENT_FLAGS=()
IFS=',' read -r -a EVENT_ARRAY <<< "$EVENTS"
for ev in "${EVENT_ARRAY[@]}"; do
EVENT_FLAGS+=("--event" "$ev")
done
FILTER_FLAGS=()
if [[ -n "$PREFIX" ]]; then
FILTER_FLAGS+=("--prefix" "$PREFIX")
fi
if [[ -n "$SUFFIX" ]]; then
FILTER_FLAGS+=("--suffix" "$SUFFIX")
fi
mc event add "$ALIAS_NAME/$BUCKET" "$TARGET_ARN" "${EVENT_FLAGS[@]}" "${FILTER_FLAGS[@]}"
echo "[3/3] Проверка после добавления:"
mc event ls "$ALIAS_NAME/$BUCKET"
echo "OK: правило уведомлений настроено"