Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
406
devops/scripts/lib/ci-common.sh
Normal file
406
devops/scripts/lib/ci-common.sh
Normal file
@@ -0,0 +1,406 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# CI COMMON FUNCTIONS
|
||||
# =============================================================================
|
||||
# Shared utility functions for local CI testing scripts.
|
||||
#
|
||||
# Usage:
|
||||
# source "$SCRIPT_DIR/lib/ci-common.sh"
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Prevent multiple sourcing
|
||||
[[ -n "${_CI_COMMON_LOADED:-}" ]] && return
|
||||
_CI_COMMON_LOADED=1
|
||||
|
||||
# =============================================================================
|
||||
# COLOR DEFINITIONS
|
||||
# =============================================================================
|
||||
|
||||
if [[ -t 1 ]] && [[ -n "${TERM:-}" ]] && [[ "${TERM}" != "dumb" ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
MAGENTA='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
WHITE='\033[0;37m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
RED=''
|
||||
GREEN=''
|
||||
YELLOW=''
|
||||
BLUE=''
|
||||
MAGENTA=''
|
||||
CYAN=''
|
||||
WHITE=''
|
||||
BOLD=''
|
||||
DIM=''
|
||||
RESET=''
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# LOGGING FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# Log an info message
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${RESET} $*"
|
||||
}
|
||||
|
||||
# Log a success message
|
||||
log_success() {
|
||||
echo -e "${GREEN}[OK]${RESET} $*"
|
||||
}
|
||||
|
||||
# Log a warning message
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${RESET} $*" >&2
|
||||
}
|
||||
|
||||
# Log an error message
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${RESET} $*" >&2
|
||||
}
|
||||
|
||||
# Log a debug message (only if VERBOSE is true)
|
||||
log_debug() {
|
||||
if [[ "${VERBOSE:-false}" == "true" ]]; then
|
||||
echo -e "${DIM}[DEBUG]${RESET} $*"
|
||||
fi
|
||||
}
|
||||
|
||||
# Log a step in a process
|
||||
log_step() {
|
||||
local step_num="$1"
|
||||
local total_steps="$2"
|
||||
local message="$3"
|
||||
echo -e "${CYAN}[${step_num}/${total_steps}]${RESET} ${BOLD}${message}${RESET}"
|
||||
}
|
||||
|
||||
# Log a section header
|
||||
log_section() {
|
||||
echo ""
|
||||
echo -e "${BOLD}${MAGENTA}=== $* ===${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Log a subsection header
|
||||
log_subsection() {
|
||||
echo -e "${CYAN}--- $* ---${RESET}"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# ERROR HANDLING
|
||||
# =============================================================================
|
||||
|
||||
# Exit with error message
|
||||
die() {
|
||||
log_error "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if a command exists
|
||||
require_command() {
|
||||
local cmd="$1"
|
||||
local install_hint="${2:-}"
|
||||
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
log_error "Required command not found: $cmd"
|
||||
if [[ -n "$install_hint" ]]; then
|
||||
log_info "Install with: $install_hint"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if a file exists
|
||||
require_file() {
|
||||
local file="$1"
|
||||
if [[ ! -f "$file" ]]; then
|
||||
log_error "Required file not found: $file"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if a directory exists
|
||||
require_dir() {
|
||||
local dir="$1"
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
log_error "Required directory not found: $dir"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# TIMING FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# Get current timestamp in seconds
|
||||
get_timestamp() {
|
||||
date +%s
|
||||
}
|
||||
|
||||
# Format duration in human-readable format
|
||||
format_duration() {
|
||||
local seconds="$1"
|
||||
local minutes=$((seconds / 60))
|
||||
local remaining_seconds=$((seconds % 60))
|
||||
|
||||
if [[ $minutes -gt 0 ]]; then
|
||||
echo "${minutes}m ${remaining_seconds}s"
|
||||
else
|
||||
echo "${remaining_seconds}s"
|
||||
fi
|
||||
}
|
||||
|
||||
# Start a timer and return the start time
|
||||
start_timer() {
|
||||
get_timestamp
|
||||
}
|
||||
|
||||
# Stop a timer and print the duration
|
||||
stop_timer() {
|
||||
local start_time="$1"
|
||||
local label="${2:-Operation}"
|
||||
local end_time
|
||||
end_time=$(get_timestamp)
|
||||
local duration=$((end_time - start_time))
|
||||
|
||||
log_info "$label completed in $(format_duration $duration)"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# STRING FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# Convert string to lowercase
|
||||
to_lower() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
# Convert string to uppercase
|
||||
to_upper() {
|
||||
echo "$1" | tr '[:lower:]' '[:upper:]'
|
||||
}
|
||||
|
||||
# Trim whitespace from string
|
||||
trim() {
|
||||
local var="$*"
|
||||
var="${var#"${var%%[![:space:]]*}"}"
|
||||
var="${var%"${var##*[![:space:]]}"}"
|
||||
echo -n "$var"
|
||||
}
|
||||
|
||||
# Join array elements with delimiter
|
||||
join_by() {
|
||||
local delimiter="$1"
|
||||
shift
|
||||
local first="$1"
|
||||
shift
|
||||
printf '%s' "$first" "${@/#/$delimiter}"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# ARRAY FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# Check if array contains element
|
||||
array_contains() {
|
||||
local needle="$1"
|
||||
shift
|
||||
local element
|
||||
for element in "$@"; do
|
||||
[[ "$element" == "$needle" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# FILE FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
ensure_dir() {
|
||||
local dir="$1"
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
mkdir -p "$dir"
|
||||
log_debug "Created directory: $dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get absolute path
|
||||
get_absolute_path() {
|
||||
local path="$1"
|
||||
if [[ -d "$path" ]]; then
|
||||
(cd "$path" && pwd)
|
||||
elif [[ -f "$path" ]]; then
|
||||
local dir
|
||||
dir=$(dirname "$path")
|
||||
echo "$(cd "$dir" && pwd)/$(basename "$path")"
|
||||
else
|
||||
echo "$path"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# GIT FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# Get the repository root directory
|
||||
get_repo_root() {
|
||||
git rev-parse --show-toplevel 2>/dev/null
|
||||
}
|
||||
|
||||
# Get current branch name
|
||||
get_current_branch() {
|
||||
git rev-parse --abbrev-ref HEAD 2>/dev/null
|
||||
}
|
||||
|
||||
# Get current commit SHA
|
||||
get_current_sha() {
|
||||
git rev-parse HEAD 2>/dev/null
|
||||
}
|
||||
|
||||
# Get short commit SHA
|
||||
get_short_sha() {
|
||||
git rev-parse --short HEAD 2>/dev/null
|
||||
}
|
||||
|
||||
# Check if working directory is clean
|
||||
is_git_clean() {
|
||||
[[ -z "$(git status --porcelain 2>/dev/null)" ]]
|
||||
}
|
||||
|
||||
# Get list of changed files compared to main branch
|
||||
get_changed_files() {
|
||||
local base_branch="${1:-main}"
|
||||
git diff --name-only "$base_branch"...HEAD 2>/dev/null
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MODULE DETECTION
|
||||
# =============================================================================
|
||||
|
||||
# Map of module names to source paths
|
||||
declare -A MODULE_PATHS=(
|
||||
["Scanner"]="src/Scanner src/BinaryIndex"
|
||||
["Concelier"]="src/Concelier src/Excititor"
|
||||
["Authority"]="src/Authority"
|
||||
["Policy"]="src/Policy src/RiskEngine"
|
||||
["Attestor"]="src/Attestor src/Provenance"
|
||||
["EvidenceLocker"]="src/EvidenceLocker"
|
||||
["ExportCenter"]="src/ExportCenter"
|
||||
["Findings"]="src/Findings"
|
||||
["SbomService"]="src/SbomService"
|
||||
["Notify"]="src/Notify src/Notifier"
|
||||
["Router"]="src/Router src/Gateway"
|
||||
["Cryptography"]="src/Cryptography"
|
||||
["AirGap"]="src/AirGap"
|
||||
["Cli"]="src/Cli"
|
||||
["AdvisoryAI"]="src/AdvisoryAI"
|
||||
["ReachGraph"]="src/ReachGraph"
|
||||
["Orchestrator"]="src/Orchestrator"
|
||||
["PacksRegistry"]="src/PacksRegistry"
|
||||
["Replay"]="src/Replay"
|
||||
["Aoc"]="src/Aoc"
|
||||
["IssuerDirectory"]="src/IssuerDirectory"
|
||||
["Telemetry"]="src/Telemetry"
|
||||
["Signals"]="src/Signals"
|
||||
["Web"]="src/Web"
|
||||
["DevPortal"]="src/DevPortal"
|
||||
)
|
||||
|
||||
# Modules that use Node.js/npm instead of .NET
|
||||
declare -a NODE_MODULES=("Web" "DevPortal")
|
||||
|
||||
# Detect which modules have changed based on git diff
|
||||
detect_changed_modules() {
|
||||
local base_branch="${1:-main}"
|
||||
local changed_files
|
||||
changed_files=$(get_changed_files "$base_branch")
|
||||
|
||||
local changed_modules=()
|
||||
local module
|
||||
local paths
|
||||
|
||||
for module in "${!MODULE_PATHS[@]}"; do
|
||||
paths="${MODULE_PATHS[$module]}"
|
||||
for path in $paths; do
|
||||
if echo "$changed_files" | grep -q "^${path}/"; then
|
||||
if ! array_contains "$module" "${changed_modules[@]}"; then
|
||||
changed_modules+=("$module")
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Check for infrastructure changes that affect all modules
|
||||
if echo "$changed_files" | grep -qE "^(Directory\.Build\.props|Directory\.Packages\.props|nuget\.config)"; then
|
||||
echo "ALL"
|
||||
return
|
||||
fi
|
||||
|
||||
# Check for shared library changes
|
||||
if echo "$changed_files" | grep -q "^src/__Libraries/"; then
|
||||
echo "ALL"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ${#changed_modules[@]} -eq 0 ]]; then
|
||||
echo "NONE"
|
||||
else
|
||||
echo "${changed_modules[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# RESULT REPORTING
|
||||
# =============================================================================
|
||||
|
||||
# Print a summary table row
|
||||
print_table_row() {
|
||||
local col1="$1"
|
||||
local col2="$2"
|
||||
local col3="${3:-}"
|
||||
|
||||
printf " %-30s %-15s %s\n" "$col1" "$col2" "$col3"
|
||||
}
|
||||
|
||||
# Print pass/fail status
|
||||
print_status() {
|
||||
local name="$1"
|
||||
local passed="$2"
|
||||
local duration="${3:-}"
|
||||
|
||||
if [[ "$passed" == "true" ]]; then
|
||||
print_table_row "$name" "${GREEN}PASSED${RESET}" "$duration"
|
||||
else
|
||||
print_table_row "$name" "${RED}FAILED${RESET}" "$duration"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# ENVIRONMENT LOADING
|
||||
# =============================================================================
|
||||
|
||||
# Load environment file if it exists
|
||||
load_env_file() {
|
||||
local env_file="$1"
|
||||
|
||||
if [[ -f "$env_file" ]]; then
|
||||
log_debug "Loading environment from: $env_file"
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "$env_file"
|
||||
set +a
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
Reference in New Issue
Block a user