CD/CD consolidation
This commit is contained in:
183
devops/scripts/test-local.sh
Normal file
183
devops/scripts/test-local.sh
Normal file
@@ -0,0 +1,183 @@
|
||||
#!/bin/bash
|
||||
# test-local.sh - Run full CI test suite locally using Docker
|
||||
# Sprint: SPRINT_20251226_006_CICD
|
||||
#
|
||||
# Usage:
|
||||
# ./devops/scripts/test-local.sh # Run all PR-gating tests
|
||||
# ./devops/scripts/test-local.sh --category Unit # Run specific category
|
||||
# ./devops/scripts/test-local.sh --build-only # Only build, skip tests
|
||||
# ./devops/scripts/test-local.sh --no-docker # Run directly without Docker
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Configuration
|
||||
CI_IMAGE="stellaops-ci:local"
|
||||
DOCKERFILE="$REPO_ROOT/devops/docker/Dockerfile.ci"
|
||||
RESULTS_DIR="$REPO_ROOT/TestResults"
|
||||
|
||||
# Default options
|
||||
USE_DOCKER=true
|
||||
BUILD_ONLY=false
|
||||
SPECIFIC_CATEGORY=""
|
||||
REBUILD_IMAGE=false
|
||||
|
||||
# PR-gating test categories
|
||||
PR_GATING_CATEGORIES=(Unit Architecture Contract Integration Security Golden)
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--category)
|
||||
SPECIFIC_CATEGORY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--build-only)
|
||||
BUILD_ONLY=true
|
||||
shift
|
||||
;;
|
||||
--no-docker)
|
||||
USE_DOCKER=false
|
||||
shift
|
||||
;;
|
||||
--rebuild)
|
||||
REBUILD_IMAGE=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --category CATEGORY Run only specific test category"
|
||||
echo " --build-only Only build, skip tests"
|
||||
echo " --no-docker Run directly without Docker container"
|
||||
echo " --rebuild Force rebuild of CI Docker image"
|
||||
echo " --help Show this help message"
|
||||
echo ""
|
||||
echo "Available categories: ${PR_GATING_CATEGORIES[*]}"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "=== StellaOps Local CI Test Runner ==="
|
||||
echo "Repository: $REPO_ROOT"
|
||||
echo "Use Docker: $USE_DOCKER"
|
||||
echo "Build Only: $BUILD_ONLY"
|
||||
echo "Category: ${SPECIFIC_CATEGORY:-All PR-gating}"
|
||||
|
||||
# Create results directory
|
||||
mkdir -p "$RESULTS_DIR"
|
||||
|
||||
run_tests() {
|
||||
local category=$1
|
||||
echo ""
|
||||
echo "=== Running $category tests ==="
|
||||
|
||||
dotnet test "$REPO_ROOT/src/StellaOps.sln" \
|
||||
--filter "Category=$category" \
|
||||
--configuration Release \
|
||||
--no-build \
|
||||
--logger "trx;LogFileName=${category}-tests.trx" \
|
||||
--results-directory "$RESULTS_DIR/$category" \
|
||||
--verbosity minimal || true
|
||||
}
|
||||
|
||||
run_build() {
|
||||
echo ""
|
||||
echo "=== Restoring dependencies ==="
|
||||
dotnet restore "$REPO_ROOT/src/StellaOps.sln"
|
||||
|
||||
echo ""
|
||||
echo "=== Building solution ==="
|
||||
dotnet build "$REPO_ROOT/src/StellaOps.sln" \
|
||||
--configuration Release \
|
||||
--no-restore
|
||||
}
|
||||
|
||||
run_all_tests() {
|
||||
run_build
|
||||
|
||||
if [[ "$BUILD_ONLY" == "true" ]]; then
|
||||
echo ""
|
||||
echo "=== Build completed (tests skipped) ==="
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -n "$SPECIFIC_CATEGORY" ]]; then
|
||||
run_tests "$SPECIFIC_CATEGORY"
|
||||
else
|
||||
for category in "${PR_GATING_CATEGORIES[@]}"; do
|
||||
run_tests "$category"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Test Summary ==="
|
||||
find "$RESULTS_DIR" -name "*.trx" -exec echo " Found: {}" \;
|
||||
|
||||
# Convert TRX to JUnit if trx2junit is available
|
||||
if command -v trx2junit &>/dev/null; then
|
||||
echo ""
|
||||
echo "=== Converting TRX to JUnit ==="
|
||||
find "$RESULTS_DIR" -name "*.trx" -exec trx2junit {} \; 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$USE_DOCKER" == "true" ]]; then
|
||||
# Check if Docker is available
|
||||
if ! command -v docker &>/dev/null; then
|
||||
echo "Error: Docker is not installed or not in PATH"
|
||||
echo "Use --no-docker to run tests directly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build CI image if needed
|
||||
if [[ "$REBUILD_IMAGE" == "true" ]] || ! docker image inspect "$CI_IMAGE" &>/dev/null; then
|
||||
echo ""
|
||||
echo "=== Building CI Docker image ==="
|
||||
docker build -t "$CI_IMAGE" -f "$DOCKERFILE" "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
# Run in Docker container
|
||||
echo ""
|
||||
echo "=== Running in Docker container ==="
|
||||
|
||||
DOCKER_ARGS=(
|
||||
--rm
|
||||
-v "$REPO_ROOT:/src"
|
||||
-v "$RESULTS_DIR:/src/TestResults"
|
||||
-e DOTNET_NOLOGO=1
|
||||
-e DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
-w /src
|
||||
)
|
||||
|
||||
# Mount Docker socket if available (for Testcontainers)
|
||||
if [[ -S /var/run/docker.sock ]]; then
|
||||
DOCKER_ARGS+=(-v /var/run/docker.sock:/var/run/docker.sock)
|
||||
fi
|
||||
|
||||
# Build test command
|
||||
TEST_CMD="./devops/scripts/test-local.sh --no-docker"
|
||||
if [[ -n "$SPECIFIC_CATEGORY" ]]; then
|
||||
TEST_CMD="$TEST_CMD --category $SPECIFIC_CATEGORY"
|
||||
fi
|
||||
if [[ "$BUILD_ONLY" == "true" ]]; then
|
||||
TEST_CMD="$TEST_CMD --build-only"
|
||||
fi
|
||||
|
||||
docker run "${DOCKER_ARGS[@]}" "$CI_IMAGE" bash -c "$TEST_CMD"
|
||||
else
|
||||
# Run directly
|
||||
run_all_tests
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Done ==="
|
||||
echo "Results saved to: $RESULTS_DIR"
|
||||
141
devops/scripts/validate-compose.sh
Normal file
141
devops/scripts/validate-compose.sh
Normal file
@@ -0,0 +1,141 @@
|
||||
#!/bin/bash
|
||||
# validate-compose.sh - Validate all Docker Compose profiles
|
||||
# Sprint: SPRINT_20251226_006_CICD
|
||||
#
|
||||
# Usage:
|
||||
# ./devops/scripts/validate-compose.sh # Validate all profiles
|
||||
# ./devops/scripts/validate-compose.sh dev stage # Validate specific profiles
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
COMPOSE_DIR="$REPO_ROOT/devops/compose"
|
||||
|
||||
# Default profiles to validate
|
||||
DEFAULT_PROFILES=(
|
||||
dev
|
||||
stage
|
||||
prod
|
||||
airgap
|
||||
mirror
|
||||
crypto-fips
|
||||
crypto-gost
|
||||
monitoring
|
||||
)
|
||||
|
||||
echo "=== Docker Compose Profile Validation ==="
|
||||
echo "Compose directory: $COMPOSE_DIR"
|
||||
|
||||
# Check if docker compose is available
|
||||
if ! command -v docker &>/dev/null; then
|
||||
echo "Error: Docker is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check compose directory exists
|
||||
if [[ ! -d "$COMPOSE_DIR" ]]; then
|
||||
echo "Error: Compose directory not found: $COMPOSE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine profiles to validate
|
||||
if [[ $# -gt 0 ]]; then
|
||||
PROFILES=("$@")
|
||||
else
|
||||
PROFILES=("${DEFAULT_PROFILES[@]}")
|
||||
fi
|
||||
|
||||
FAILED=0
|
||||
PASSED=0
|
||||
SKIPPED=0
|
||||
|
||||
# Validate base compose file first
|
||||
BASE_COMPOSE="$COMPOSE_DIR/docker-compose.yml"
|
||||
if [[ -f "$BASE_COMPOSE" ]]; then
|
||||
echo ""
|
||||
echo "=== Validating base: docker-compose.yml ==="
|
||||
if docker compose -f "$BASE_COMPOSE" config --quiet 2>/dev/null; then
|
||||
echo " [PASS] docker-compose.yml"
|
||||
((PASSED++))
|
||||
else
|
||||
echo " [FAIL] docker-compose.yml"
|
||||
docker compose -f "$BASE_COMPOSE" config 2>&1 | head -20
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "Warning: Base compose file not found: $BASE_COMPOSE"
|
||||
fi
|
||||
|
||||
# Validate each profile
|
||||
for profile in "${PROFILES[@]}"; do
|
||||
PROFILE_FILE="$COMPOSE_DIR/docker-compose.${profile}.yml"
|
||||
|
||||
echo ""
|
||||
echo "=== Validating profile: $profile ==="
|
||||
|
||||
if [[ ! -f "$PROFILE_FILE" ]]; then
|
||||
echo " [SKIP] Profile file not found: docker-compose.${profile}.yml"
|
||||
((SKIPPED++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Validate profile alone
|
||||
if docker compose -f "$PROFILE_FILE" config --quiet 2>/dev/null; then
|
||||
echo " [PASS] docker-compose.${profile}.yml (standalone)"
|
||||
else
|
||||
echo " [FAIL] docker-compose.${profile}.yml (standalone)"
|
||||
docker compose -f "$PROFILE_FILE" config 2>&1 | head -10
|
||||
((FAILED++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Validate profile with base
|
||||
if [[ -f "$BASE_COMPOSE" ]]; then
|
||||
if docker compose -f "$BASE_COMPOSE" -f "$PROFILE_FILE" config --quiet 2>/dev/null; then
|
||||
echo " [PASS] docker-compose.yml + docker-compose.${profile}.yml (merged)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo " [FAIL] Merged validation failed"
|
||||
docker compose -f "$BASE_COMPOSE" -f "$PROFILE_FILE" config 2>&1 | head -10
|
||||
((FAILED++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate Helm chart if present
|
||||
HELM_DIR="$REPO_ROOT/devops/helm/stellaops"
|
||||
if [[ -d "$HELM_DIR" ]]; then
|
||||
echo ""
|
||||
echo "=== Validating Helm chart ==="
|
||||
if command -v helm &>/dev/null; then
|
||||
if helm lint "$HELM_DIR" --quiet 2>/dev/null; then
|
||||
echo " [PASS] Helm chart: stellaops"
|
||||
((PASSED++))
|
||||
else
|
||||
echo " [FAIL] Helm chart: stellaops"
|
||||
helm lint "$HELM_DIR" 2>&1 | head -20
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo " [SKIP] Helm not installed"
|
||||
((SKIPPED++))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "=== Validation Summary ==="
|
||||
echo " Passed: $PASSED"
|
||||
echo " Failed: $FAILED"
|
||||
echo " Skipped: $SKIPPED"
|
||||
|
||||
if [[ $FAILED -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "ERROR: $FAILED validation(s) failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "All validations passed!"
|
||||
Reference in New Issue
Block a user