Add Oracle Aggregator and CCIP Integration

- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
This commit is contained in:
defiQUG
2025-12-12 14:57:48 -08:00
parent a1466e4005
commit 1fb7266469
1720 changed files with 241279 additions and 16 deletions

132
scripts/lib/README.md Normal file
View File

@@ -0,0 +1,132 @@
# Scripts Library
Common libraries for all scripts in the project. This modularizes and consolidates shared functionality.
## Structure
```
lib/
├── common/ # Common utilities
│ ├── colors.sh # Color definitions and functions
│ ├── logging.sh # Logging functions (info, warn, error, etc.)
│ ├── paths.sh # Path definitions (SCRIPT_DIR, PROJECT_ROOT, etc.)
│ └── utils.sh # Utility functions (confirm, require_command, etc.)
├── config/ # Configuration
│ ├── env.sh # Environment variable loading
│ └── regions.sh # Region code mapping (single source of truth)
├── azure/ # Azure-specific functions
│ └── cli.sh # Azure CLI wrapper functions
└── init.sh # Initialize all libraries (main entry point)
```
## Usage
### Quick Start
Most scripts should source `init.sh` to load all libraries:
```bash
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/init.sh"
# Now you can use all functions from the libraries
log_info "Starting script"
ensure_azure_cli || exit 1
```
### Individual Libraries
You can also source individual libraries if you only need specific functionality:
```bash
source "$SCRIPT_DIR/lib/common/colors.sh"
source "$SCRIPT_DIR/lib/common/logging.sh"
```
### Examples
#### Using Logging
```bash
source "$SCRIPT_DIR/lib/init.sh"
log_info "Processing regions..."
log_warn "This might take a while"
log_error "Failed to connect"
log_success "Completed successfully"
log_section "Deployment Status"
```
#### Using Colors
```bash
source "$SCRIPT_DIR/lib/init.sh"
color_red "Error message"
color_green "Success message"
color_yellow "Warning message"
```
#### Using Region Codes
```bash
source "$SCRIPT_DIR/lib/init.sh"
# Get all regions
get_all_regions
# Get region code
CODE=$(get_region_code "westeurope") # Returns "wst"
# Get region name from code
REGION=$(get_region_name "wst") # Returns "westeurope"
```
#### Using Azure CLI Functions
```bash
source "$SCRIPT_DIR/lib/init.sh"
# Ensure Azure CLI is ready
ensure_azure_cli || exit 1
# Get subscription info
SUB_ID=$(get_current_subscription)
```
## Migration Guide
To migrate existing scripts to use the library:
1. **Remove duplicate code**:
- Remove color definitions (use `lib/common/colors.sh`)
- Remove SCRIPT_DIR/PROJECT_ROOT definitions (use `lib/common/paths.sh`)
- Remove region code mappings (use `lib/config/regions.sh`)
2. **Replace with library calls**:
```bash
# Old
RED='\033[0;31m'
echo -e "${RED}Error${NC}"
# New
source "$SCRIPT_DIR/lib/init.sh"
log_error "Error"
```
3. **Use standardized functions**:
- Replace `az account show` checks with `ensure_azure_cli`
- Replace manual color codes with logging functions
- Replace duplicate region mappings with `get_region_code()`
## Benefits
1. **Single Source of Truth**: Region codes defined once
2. **Consistency**: All scripts use the same colors, logging, and utilities
3. **Maintainability**: Update once, applies everywhere
4. **Reduced Duplication**: Eliminates repetitive code
5. **Better Error Handling**: Standardized error messages and checks

55
scripts/lib/azure/cli.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Azure CLI wrapper functions
# Usage: source "$SCRIPT_DIR/lib/azure/cli.sh"
# Source logging if not already sourced
[ -z "${log_error:-}" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../common/logging.sh"
# Check if Azure CLI is installed
check_azure_cli() {
if ! command -v az &> /dev/null; then
log_error "Azure CLI not found. Please install Azure CLI."
log_info "Visit: https://docs.microsoft.com/cli/azure/install-azure-cli"
return 1
fi
return 0
}
# Check if logged in to Azure
check_azure_login() {
if ! az account show &> /dev/null; then
log_error "Not logged in to Azure. Run 'az login' first."
return 1
fi
return 0
}
# Ensure Azure CLI is ready (checks installation and login)
ensure_azure_cli() {
check_azure_cli || return 1
check_azure_login || return 1
# Set subscription if configured
local subscription_id="${AZURE_SUBSCRIPTION_ID:-}"
if [ -n "$subscription_id" ]; then
az account set --subscription "$subscription_id" &> /dev/null || true
fi
return 0
}
# Get current subscription ID
get_current_subscription() {
az account show --query id -o tsv 2>/dev/null
}
# Get current subscription name
get_current_subscription_name() {
az account show --query name -o tsv 2>/dev/null
}
# List all subscriptions
list_subscriptions() {
az account list --query "[].{Name:name, ID:id, IsDefault:isDefault}" -o table
}

23
scripts/lib/common/colors.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Common color definitions for scripts
# Usage: source "$SCRIPT_DIR/lib/common/colors.sh"
# Color codes
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly MAGENTA='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly WHITE='\033[1;37m'
readonly BOLD='\033[1m'
readonly NC='\033[0m' # No Color
# Color functions for output
color_red() { echo -e "${RED}$*${NC}"; }
color_green() { echo -e "${GREEN}$*${NC}"; }
color_yellow() { echo -e "${YELLOW}$*${NC}"; }
color_blue() { echo -e "${BLUE}$*${NC}"; }
color_cyan() { echo -e "${CYAN}$*${NC}"; }
color_bold() { echo -e "${BOLD}$*${NC}"; }

45
scripts/lib/common/env.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Environment loader and profile support
# Provides: load_env [--profile <name>] [--file <path>] and helpers
set -euo pipefail
DEFAULT_ENV_FILE="${DEFAULT_ENV_FILE:-.env}"
load_env() {
local profile=""; local file="$DEFAULT_ENV_FILE"; local opt
while [[ $# -gt 0 ]]; do
opt="$1"
case "$opt" in
--profile)
profile="$2"; shift 2;;
--file)
file="$2"; shift 2;;
*)
break;;
esac
done
if [ -f "$file" ]; then
# shellcheck disable=SC2046
set -a; . "$file"; set +a
fi
if [ -n "$profile" ]; then
local pf="${file}.${profile}"
if [ -f "$pf" ]; then
# shellcheck disable=SC2046
set -a; . "$pf"; set +a
fi
fi
}
require_env() {
local name="$1"
if [ -z "${!name:-}" ]; then
echo "Missing required env: $name" >&2
return 1
fi
}
current_profile() {
echo "${ENV_PROFILE:-}"
}

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Error handling functions
# Usage: source "$SCRIPT_DIR/lib/common/error-handling.sh"
# Source logging if not already sourced
[ -z "$(type -t log_error)" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/logging.sh"
# Error exit function
error_exit() {
local message="$1"
local exit_code="${2:-1}"
log_error "$message"
exit "$exit_code"
}
# Cleanup function registry
declare -a CLEANUP_FUNCTIONS=()
# Register cleanup function
register_cleanup() {
local func="$1"
CLEANUP_FUNCTIONS+=("$func")
}
# Execute cleanup functions
cleanup_on_exit() {
local exit_code=$?
for func in "${CLEANUP_FUNCTIONS[@]}"; do
if type -t "$func" >/dev/null; then
"$func" || true
fi
done
exit $exit_code
}
# Setup error traps
setup_error_traps() {
# Trap ERR - command failures
trap 'error_exit "Line $LINENO: Command failed: $BASH_COMMAND" $?' ERR
# Trap EXIT - cleanup
trap 'cleanup_on_exit' EXIT
# Trap INT - Ctrl+C
trap 'log_warn "Interrupted by user"; exit 130' INT
# Trap TERM - termination
trap 'log_warn "Terminated"; exit 143' TERM
}
# Check if error handling is enabled
is_error_handling_enabled() {
[[ $- == *e* ]]
}

71
scripts/lib/common/logging.sh Executable file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Common logging functions for scripts
# Usage: source "$SCRIPT_DIR/lib/common/logging.sh"
# Source colors if not already sourced
[ -z "${RED:-}" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/colors.sh"
# Logging levels (idempotent & safe to re-source)
: "${LOG_LEVEL_DEBUG:=0}"
: "${LOG_LEVEL_INFO:=1}"
: "${LOG_LEVEL_WARN:=2}"
: "${LOG_LEVEL_ERROR:=0}"
# Mark as readonly if not already
readonly LOG_LEVEL_DEBUG 2>/dev/null || true
readonly LOG_LEVEL_INFO 2>/dev/null || true
readonly LOG_LEVEL_WARN 2>/dev/null || true
readonly LOG_LEVEL_ERROR 2>/dev/null || true
# Default log level
: "${LOG_LEVEL:=$LOG_LEVEL_INFO}"
# Logging functions
log_debug() {
[ "$LOG_LEVEL" -le "$LOG_LEVEL_DEBUG" ] && echo -e "${CYAN}[DEBUG]${NC} $*" >&2
}
log_info() {
[ "$LOG_LEVEL" -le "$LOG_LEVEL_INFO" ] && echo -e "${GREEN}[INFO]${NC} $*"
}
log_warn() {
[ "$LOG_LEVEL" -le "$LOG_LEVEL_WARN" ] && echo -e "${YELLOW}[WARN]${NC} $*" >&2
}
log_error() {
[ "$LOG_LEVEL" -le "$LOG_LEVEL_ERROR" ] && echo -e "${RED}[ERROR]${NC} $*" >&2
}
log_success() {
echo -e "${GREEN}${NC} $*"
}
log_failure() {
echo -e "${RED}${NC} $*"
}
log_section() {
local title="$1"
echo ""
echo -e "${BOLD}${BLUE}════════════════════════════════════════════════════════════════${NC}"
echo -e "${BOLD}${BLUE} ${title}${NC}"
echo -e "${BOLD}${BLUE}════════════════════════════════════════════════════════════════${NC}"
echo ""
}
log_subsection() {
local title="$1"
echo ""
echo -e "${CYAN}${title}${NC}"
echo "$(printf '─%.0s' {1..60})"
echo ""
}
# Print separator line
print_separator() {
local char="${1:-=}"
local width="${2:-80}"
printf "%.0s${char}" $(seq 1 "$width")
echo
}

48
scripts/lib/common/metadata.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Metadata and help utilities for scripts
# Provides simple helpers to print script usage and header information.
# Print a standardized usage/help message using optional variables:
# SCRIPT_NAME, SCRIPT_DESC, SCRIPT_USAGE, SCRIPT_OPTIONS, SCRIPT_ENVVARS, SCRIPT_REQUIREMENTS, SCRIPT_EXAMPLE
script_usage() {
local name="${SCRIPT_NAME:-${0##*/}}"
echo "${name}"
[ -n "${SCRIPT_DESC:-}" ] && echo "Description: ${SCRIPT_DESC}"
if [ -n "${SCRIPT_USAGE:-}" ]; then
echo "Usage: ${SCRIPT_USAGE}"
else
echo "Usage: ${name} [options]"
fi
if [ -n "${SCRIPT_OPTIONS:-}" ]; then
echo
echo "Options:"
# shellcheck disable=SC2001
echo "${SCRIPT_OPTIONS}" | sed 's/^/ /'
fi
if [ -n "${SCRIPT_ENVVARS:-}" ]; then
echo
echo "Environment:"
# shellcheck disable=SC2001
echo "${SCRIPT_ENVVARS}" | sed 's/^/ /'
fi
if [ -n "${SCRIPT_REQUIREMENTS:-}" ]; then
echo
echo "Requires: ${SCRIPT_REQUIREMENTS}"
fi
if [ -n "${SCRIPT_EXAMPLE:-}" ]; then
echo
echo "Example:"
# shellcheck disable=SC2001
echo "${SCRIPT_EXAMPLE}" | sed 's/^/ /'
fi
}
# Handle -h/--help; call after sourcing init and setting metadata vars
handle_help() {
case "${1:-}" in
-h|--help)
script_usage
exit 0
;;
esac
}

25
scripts/lib/common/paths.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Common path definitions for scripts
# Usage: source "$SCRIPT_DIR/lib/common/paths.sh"
# Get script directory (works even when sourced)
if [ -z "${SCRIPT_DIR:-}" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-}")" && pwd)"
# If sourced from another script, try to get the calling script's directory
if [ "${BASH_SOURCE[0]:-}" = "${BASH_SOURCE[1]:-}" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]:-}")" && pwd)"
fi
fi
# Project root (two levels up from scripts/)
if [ -z "${PROJECT_ROOT:-}" ]; then
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
fi
# Common directories
readonly CONFIG_DIR="${PROJECT_ROOT}/config"
readonly KEYS_DIR="${PROJECT_ROOT}/keys"
readonly SCRIPTS_DIR="${PROJECT_ROOT}/scripts"
readonly TERRAFORM_DIR="${PROJECT_ROOT}/terraform"
readonly DOCS_DIR="${PROJECT_ROOT}/docs"

113
scripts/lib/common/retry.sh Executable file
View File

@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# Retry functions with exponential backoff
# Usage: source "$SCRIPT_DIR/lib/common/retry.sh"
# Source logging if not already sourced
[ -z "$(type -t log_info)" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/logging.sh"
# Retry a command with exponential backoff
# Usage: retry_command <max_attempts> <delay> <command> [args...]
retry_command() {
local max_attempts="$1"
local base_delay="$2"
shift 2
local command=("$@")
local attempt=1
local delay="$base_delay"
while [ $attempt -le $max_attempts ]; do
log_info "Attempt $attempt/$max_attempts: ${command[*]}"
if "${command[@]}"; then
return 0
fi
if [ $attempt -lt $max_attempts ]; then
log_warn "Command failed, retrying in ${delay}s..."
sleep "$delay"
delay=$((delay * 2)) # Exponential backoff
fi
((attempt++)) || true
done
log_error "Command failed after $max_attempts attempts: ${command[*]}"
return 1
}
# Retry a function with exponential backoff
# Usage: retry_function <max_attempts> <delay> <function_name> [args...]
retry_function() {
local max_attempts="$1"
local base_delay="$2"
local func_name="$3"
shift 3
local args=("$@")
local attempt=1
local delay="$base_delay"
while [ $attempt -le $max_attempts ]; do
log_info "Attempt $attempt/$max_attempts: $func_name"
if "$func_name" "${args[@]}"; then
return 0
fi
if [ $attempt -lt $max_attempts ]; then
log_warn "Function failed, retrying in ${delay}s..."
sleep "$delay"
delay=$((delay * 2)) # Exponential backoff
fi
((attempt++)) || true
done
log_error "Function failed after $max_attempts attempts: $func_name"
return 1
}
# Wait for a condition to be true
# Usage: wait_for_condition <timeout> <interval> <condition_command>
wait_for_condition() {
local timeout="$1"
local interval="$2"
shift 2
local condition=("$@")
local elapsed=0
while [ $elapsed -lt $timeout ]; do
if "${condition[@]}"; then
return 0
fi
sleep "$interval"
elapsed=$((elapsed + interval))
done
log_error "Condition not met within ${timeout}s: ${condition[*]}"
return 1
}
# Wait for a service to be ready
# Usage: wait_for_service <url> <timeout> <interval>
wait_for_service() {
local url="$1"
local timeout="${2:-60}"
local interval="${3:-5}"
wait_for_condition "$timeout" "$interval" curl -sf "$url" >/dev/null
}
# Wait for a file to exist
# Usage: wait_for_file <file_path> <timeout> <interval>
wait_for_file() {
local file_path="$1"
local timeout="${2:-60}"
local interval="${3:-5}"
wait_for_condition "$timeout" "$interval" test -f "$file_path"
}

81
scripts/lib/common/utils.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# Common utility functions
# Usage: source "$SCRIPT_DIR/lib/common/utils.sh"
# Source colors if not already sourced
[ -z "${RED:-}" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/colors.sh"
# Check if command exists
command_exists() {
command -v "$1" &> /dev/null
}
# Require command (exits if not found)
require_command() {
local cmd="$1"
local install_hint="${2:-}"
if ! command_exists "$cmd"; then
echo -e "${RED}Error: $cmd not found${NC}" >&2
[ -n "$install_hint" ] && echo -e "${YELLOW}Hint: $install_hint${NC}" >&2
exit 1
fi
}
# Confirm action
confirm() {
local prompt="${1:-Are you sure?}"
local default="${2:-n}"
if [ "$default" = "y" ]; then
local options="[Y/n]"
else
local options="[y/N]"
fi
read -p "$(echo -e "${YELLOW}${prompt} ${options}: ${NC}")" -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]] || ([ "$default" = "y" ] && [[ -z $REPLY ]]); then
return 0
else
return 1
fi
}
# Wait for user input
press_enter_to_continue() {
read -p "$(echo -e "${CYAN}Press Enter to continue...${NC}")"
}
# Print header box
print_header() {
local title="$1"
local width="${2:-80}"
echo "$(printf '═%.0s' $(seq 1 $((width-2))))"
printf "║ %-${width-4}s ║\n" "$title"
echo "$(printf '═%.0s' $(seq 1 $((width-2))))"
echo
}
# Print centered text
print_centered() {
local text="$1"
local width="${2:-80}"
printf "%*s\n" $(((${#text} + width) / 2)) "$text"
}
# Trim whitespace
trim() {
local var="$*"
var="${var#"${var%%[![:space:]]*}"}" # Remove leading whitespace
var="${var%"${var##*[![:space:]]}"}" # Remove trailing whitespace
echo "$var"
}
# Check if running in dry-run mode
is_dry_run() {
[ "${DRY_RUN:-0}" = "1" ] || [ "${DRY_RUN:-0}" = "true" ]
}

211
scripts/lib/common/validation.sh Executable file
View File

@@ -0,0 +1,211 @@
#!/usr/bin/env bash
# Validation functions for scripts
# Usage: source "$SCRIPT_DIR/lib/common/validation.sh"
# Source colors and logging if not already sourced
[ -z "${RED:-}" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/colors.sh"
[ -z "$(type -t log_error)" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/logging.sh"
# Validate required environment variable
validate_required() {
local var_name="$1"
local var_value="${!var_name:-}"
if [ -z "$var_value" ]; then
log_error "$var_name is required but not set"
return 1
fi
return 0
}
# Validate file exists
validate_file_exists() {
local file_path="$1"
local description="${2:-File}"
if [ ! -f "$file_path" ]; then
log_error "$description not found: $file_path"
return 1
fi
return 0
}
# Validate directory exists
validate_directory_exists() {
local dir_path="$1"
local description="${2:-Directory}"
if [ ! -d "$dir_path" ]; then
log_error "$description not found: $dir_path"
return 1
fi
return 0
}
# Validate JSON file
validate_json() {
local json_file="$1"
if ! python3 -m json.tool "$json_file" >/dev/null 2>&1; then
log_error "Invalid JSON file: $json_file"
return 1
fi
return 0
}
# Validate YAML file
validate_yaml() {
local yaml_file="$1"
if ! command -v yamllint &>/dev/null; then
log_warn "yamllint not installed, skipping YAML validation"
return 0
fi
if ! yamllint "$yaml_file" >/dev/null 2>&1; then
log_error "Invalid YAML file: $yaml_file"
return 1
fi
return 0
}
# Validate TOML file
validate_toml() {
local toml_file="$1"
if ! python3 -c "import tomllib; open('$toml_file', 'rb').read()" 2>/dev/null; then
log_error "Invalid TOML file: $toml_file"
return 1
fi
return 0
}
# Validate URL format
validate_url() {
local url="$1"
if [[ ! "$url" =~ ^https?:// ]]; then
log_error "Invalid URL format: $url"
return 1
fi
return 0
}
# Validate IP address
validate_ip() {
local ip="$1"
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
log_error "Invalid IP address format: $ip"
return 1
fi
# Check each octet is 0-255
IFS='.' read -ra ADDR <<< "$ip"
for i in "${ADDR[@]}"; do
if [ "$i" -gt 255 ] || [ "$i" -lt 0 ]; then
log_error "Invalid IP address: $ip (octet out of range)"
return 1
fi
done
return 0
}
# Validate port number
validate_port() {
local port="$1"
if [[ ! "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
log_error "Invalid port number: $port (must be 1-65535)"
return 1
fi
return 0
}
# Validate Ethereum address
validate_eth_address() {
local address="$1"
if [[ ! "$address" =~ ^0x[0-9a-fA-F]{40}$ ]]; then
log_error "Invalid Ethereum address format: $address"
return 1
fi
return 0
}
# Validate chain ID
validate_chain_id() {
local chain_id="$1"
if [[ ! "$chain_id" =~ ^[0-9]+$ ]] || [ "$chain_id" -lt 1 ]; then
log_error "Invalid chain ID: $chain_id"
return 1
fi
return 0
}
# Validate non-empty string
validate_non_empty() {
local value="$1"
local name="${2:-Value}"
if [ -z "$value" ]; then
log_error "$name cannot be empty"
return 1
fi
return 0
}
# Validate numeric value
validate_numeric() {
local value="$1"
local name="${2:-Value}"
if [[ ! "$value" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
log_error "$name must be numeric: $value"
return 1
fi
return 0
}
# Validate positive number
validate_positive() {
local value="$1"
local name="${2:-Value}"
if ! validate_numeric "$value" "$name"; then
return 1
fi
if (( $(echo "$value <= 0" | bc -l) )); then
log_error "$name must be positive: $value"
return 1
fi
return 0
}
# Validate command exists
validate_command() {
local cmd="$1"
if ! command -v "$cmd" &>/dev/null; then
log_error "Command not found: $cmd"
return 1
fi
return 0
}
# Validate multiple requirements
validate_all() {
local failed=0
while [ $# -gt 0 ]; do
if ! "$@"; then
failed=1
fi
shift
done
return $failed
}

36
scripts/lib/config/env.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Environment configuration loader
# Usage: source "$SCRIPT_DIR/lib/config/env.sh"
# Source paths if not already sourced
[ -z "${PROJECT_ROOT:-}" ] && source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../common/paths.sh"
# Default subscription ID
DEFAULT_SUBSCRIPTION_ID="${DEFAULT_SUBSCRIPTION_ID:-fc08d829-4f14-413d-ab27-ce024425db0b}"
# Load environment variables from .env file
load_env() {
local env_file="${1:-${PROJECT_ROOT}/.env}"
if [ -f "$env_file" ]; then
# Export variables, ignoring comments and empty lines
set -a
source <(grep -v '^#' "$env_file" | grep -v '^$' | sed 's/^/export /')
set +a
fi
}
# Get Azure subscription ID
get_subscription_id() {
echo "${AZURE_SUBSCRIPTION_ID:-${DEFAULT_SUBSCRIPTION_ID}}"
}
# Set Azure subscription
set_subscription() {
local subscription_id="${1:-$(get_subscription_id)}"
az account set --subscription "$subscription_id" &> /dev/null || return 1
}
# Auto-load env if PROJECT_ROOT is set
[ -n "${PROJECT_ROOT:-}" ] && load_env

121
scripts/lib/config/regions.sh Executable file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env bash
# Region code mapping - Single source of truth
# All region codes are standardized to exactly 3 characters
# Usage: source "$SCRIPT_DIR/lib/config/regions.sh"
# Region codes mapping (standardized to exactly 3 characters)
declare -A REGION_CODES=(
["northeurope"]="nor"
["uksouth"]="uks"
["ukwest"]="ukw"
["westeurope"]="wst"
["francecentral"]="frc"
["germanywestcentral"]="gwc"
["switzerlandnorth"]="swn"
["switzerlandwest"]="swt"
["swedencentral"]="swc"
["norwayeast"]="noe"
["polandcentral"]="pol"
["spaincentral"]="spa"
["italynorth"]="ita"
["southindia"]="sin"
["centralindia"]="cin"
["westindia"]="win"
["belgiumcentral"]="bel"
["eastasia"]="eas"
["southeastasia"]="sea"
["japaneast"]="jpe"
["japanwest"]="jpw"
["koreacentral"]="kor"
["koreasouth"]="kos"
["australiaeast"]="aus"
["australiasoutheast"]="ase"
["newzealandnorth"]="nzl"
["indonesiacentral"]="idn"
["malaysiawest"]="mys"
["uaenorth"]="uae"
["qatarcentral"]="qat"
["israelcentral"]="ilc"
["canadacentral"]="can"
["canadaeast"]="cae"
["brazilsouth"]="bra"
["chilecentral"]="chl"
["mexicocentral"]="mex"
["southafricanorth"]="zaf"
["austriaeast"]="aut"
)
# Reverse mapping (code -> region name)
declare -A REGION_CODE_TO_NAME=()
for region_name in "${!REGION_CODES[@]}"; do
REGION_CODE_TO_NAME["${REGION_CODES[$region_name]}"]="$region_name"
done
# Old code mappings (for backward compatibility with existing resources)
declare -A OLD_CODE_TO_REGION=(
["ne"]="northeurope"
["we"]="westeurope"
["fc"]="francecentral"
["sn"]="switzerlandnorth"
["sw"]="switzerlandwest"
["in"]="italynorth"
["pc"]="polandcentral"
["sc"]="spaincentral"
["bc"]="belgiumcentral"
["ae"]="australiaeast" # Note: conflicts with austriaeast (old), prefer australiaeast
["ea"]="eastasia"
["ci"]="centralindia"
["si"]="southindia"
["wi"]="westindia"
["je"]="japaneast"
["jw"]="japanwest"
["kc"]="koreacentral"
["ks"]="koreasouth"
["cc"]="canadacentral"
["ce"]="canadaeast"
["bs"]="brazilsouth"
["mc"]="mexicocentral"
["qc"]="qatarcentral"
["ic"]="indonesiacentral"
["mw"]="malaysiawest"
["nzn"]="newzealandnorth"
["san"]="southafricanorth"
["uan"]="uaenorth"
["chc"]="chilecentral"
)
# Get all regions as array (region_name:code format)
get_all_regions() {
for region_name in "${!REGION_CODES[@]}"; do
echo "${region_name}:${REGION_CODES[$region_name]}"
done | sort
}
# Get region code by region name
get_region_code() {
local region_name="$1"
echo "${REGION_CODES[$region_name]:-}"
}
# Get region name by code (tries new codes first, then old codes)
get_region_name() {
local code="$1"
# Try new codes first
if [ -n "${REGION_CODE_TO_NAME[$code]:-}" ]; then
echo "${REGION_CODE_TO_NAME[$code]}"
return 0
fi
# Try old codes for backward compatibility
if [ -n "${OLD_CODE_TO_REGION[$code]:-}" ]; then
echo "${OLD_CODE_TO_REGION[$code]}"
return 0
fi
return 1
}
# Validate region code (must be 3 characters)
is_valid_region_code() {
local code="$1"
[[ "$code" =~ ^[a-z]{3}$ ]] && [ -n "${REGION_CODE_TO_NAME[$code]:-}" ]
}

150
scripts/lib/deployment/costs.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# Cost calculation library
# Usage: source "$SCRIPT_DIR/lib/deployment/costs.sh"
# Source libraries (use absolute path resolution)
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ -z "${PROJECT_ROOT:-}" ] && source "${LIB_DIR}/../common/paths.sh"
[ -z "${log_info:-}" ] && source "${LIB_DIR}/../common/logging.sh"
# Check for required commands
command_exists() {
command -v "$1" &> /dev/null
}
# Default gas requirements (in wei units, can be overridden)
DEFAULT_CCIPWETH9_BRIDGE_GAS="${CCIPWETH9_BRIDGE_GAS:-263000}"
DEFAULT_CCIPWETH10_BRIDGE_GAS="${CCIPWETH10_BRIDGE_GAS:-263000}"
DEFAULT_CONFIGURATION_GAS="${CONFIGURATION_GAS:-200000}"
# Load environment variables
load_env_file() {
local env_file="${PROJECT_ROOT}/.env"
if [ -f "$env_file" ]; then
set -a
source "$env_file"
set +a
fi
}
# Get gas price from multiple sources
get_gas_price() {
local source_type="${1:-auto}" # auto, rpc, infura, default, conservative
load_env_file
local gas_price_wei=""
local gas_price_gwei=""
local source_name=""
case "$source_type" in
rpc|auto)
# Try ETHEREUM_MAINNET_RPC
if [ -n "${ETHEREUM_MAINNET_RPC:-}" ]; then
if command_exists cast; then
gas_price_wei=$(cast gas-price --rpc-url "$ETHEREUM_MAINNET_RPC" 2>/dev/null || echo "")
if [ -n "$gas_price_wei" ] && [ "$gas_price_wei" != "0" ]; then
gas_price_gwei=$(cast --to-unit "$gas_price_wei" gwei 2>/dev/null || echo "")
source_name="ETHEREUM_MAINNET_RPC"
fi
fi
fi
;;
infura|auto)
# Try Infura Gas API
if [ -z "$gas_price_wei" ] && [ -n "${INFURA_GAS_API:-}" ]; then
local api_key="$INFURA_GAS_API"
# Extract API key from URL if needed
if [[ "$INFURA_GAS_API" == *"infura.io"* ]]; then
api_key=$(echo "$INFURA_GAS_API" | sed -n 's|.*/v3/\(.*\)|\1|p')
fi
if [ -n "$api_key" ] && command_exists curl && command_exists jq; then
local response=$(curl -s "https://gas.api.infura.io/networks/1/suggestedGasFees" \
-H "Authorization: Basic $(echo -n :$api_key | base64)" 2>/dev/null || echo "")
if [ -n "$response" ]; then
local standard_gas=$(echo "$response" | jq -r '.standard.maxFeePerGas' 2>/dev/null || echo "")
if [ -n "$standard_gas" ] && [ "$standard_gas" != "null" ] && [ "$standard_gas" != "" ]; then
gas_price_wei="$standard_gas"
if command_exists cast; then
gas_price_gwei=$(cast --to-unit "$gas_price_wei" gwei 2>/dev/null || echo "")
else
gas_price_gwei=$(echo "scale=2; $gas_price_wei / 1000000000" | bc 2>/dev/null || echo "")
fi
source_name="Infura Gas API"
fi
fi
fi
fi
;;
default|auto)
# Fallback to default RPC
if [ -z "$gas_price_wei" ] && command_exists cast; then
gas_price_wei=$(cast gas-price --rpc-url "https://eth.llamarpc.com" 2>/dev/null || echo "")
if [ -n "$gas_price_wei" ] && [ "$gas_price_wei" != "0" ]; then
gas_price_gwei=$(cast --to-unit "$gas_price_wei" gwei 2>/dev/null || echo "")
source_name="Default RPC"
fi
fi
;;
conservative)
# Use conservative estimate (50% above current)
gas_price_wei=$(get_gas_price "auto")
if [ -n "$gas_price_wei" ] && [ "$gas_price_wei" != "0" ]; then
gas_price_wei=$(echo "$gas_price_wei * 1.5" | bc 2>/dev/null || echo "$gas_price_wei")
if command_exists cast; then
gas_price_gwei=$(cast --to-unit "$gas_price_wei" gwei 2>/dev/null || echo "")
else
gas_price_gwei=$(echo "scale=2; $gas_price_wei / 1000000000" | bc 2>/dev/null || echo "")
fi
source_name="Conservative Estimate"
fi
;;
esac
# Final fallback
if [ -z "$gas_price_wei" ] || [ "$gas_price_wei" = "0" ]; then
gas_price_gwei="${DEFAULT_GAS_PRICE_GWEI:-30}"
gas_price_wei=$(echo "$gas_price_gwei * 1000000000" | bc 2>/dev/null || echo "30000000000")
source_name="Fallback Default"
fi
echo "$gas_price_wei|$gas_price_gwei|$source_name"
}
# Calculate cost in ETH
calculate_cost_eth() {
local gas=$1
local gas_price_wei=$2
local cost_wei=$(echo "$gas * $gas_price_wei" | bc 2>/dev/null || echo "0")
local cost_eth=$(echo "scale=10; $cost_wei / 1000000000000000000" | bc 2>/dev/null || echo "0")
echo "$cost_eth"
}
# Calculate cost in USD
calculate_cost_usd() {
local cost_eth=$1
local eth_price_usd="${2:-${ETH_PRICE_USD:-2500}}"
local cost_usd=$(echo "scale=2; $cost_eth * $eth_price_usd" | bc 2>/dev/null || echo "0")
echo "$cost_usd"
}
# Format cost output
format_cost_output() {
local item_name="$1"
local cost_eth="$2"
local cost_usd="$3"
printf "%-30s %18.8f ETH $%15.2f\n" "$item_name" "$cost_eth" "$cost_usd"
}
# Get ETH price (simple version, can be enhanced)
get_eth_price() {
local eth_price="${ETH_PRICE_USD:-2500}"
echo "$eth_price"
}

37
scripts/lib/init.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Initialize common libraries
# Usage: source "$SCRIPT_DIR/lib/init.sh"
#
# This script loads all common libraries in the correct order.
# Individual scripts can source this instead of sourcing each library separately.
# Get lib directory (this file's directory)
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Get script directory if not set (try to get from calling script)
if [ -z "${SCRIPT_DIR:-}" ]; then
# Try to get from calling script (BASH_SOURCE[2] because init.sh adds one level)
if [ -n "${BASH_SOURCE[2]:-}" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[2]}")" && pwd)"
elif [ -n "${BASH_SOURCE[1]:-}" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)"
fi
fi
# Source libraries in order
source "${LIB_DIR}/common/paths.sh"
source "${LIB_DIR}/common/colors.sh"
source "${LIB_DIR}/common/logging.sh"
source "${LIB_DIR}/common/utils.sh"
source "${LIB_DIR}/common/env.sh"
source "${LIB_DIR}/common/metadata.sh"
source "${LIB_DIR}/common/validation.sh" 2>/dev/null || true
source "${LIB_DIR}/common/retry.sh" 2>/dev/null || true
source "${LIB_DIR}/common/error-handling.sh" 2>/dev/null || true
source "${LIB_DIR}/config/env.sh"
source "${LIB_DIR}/config/regions.sh"
source "${LIB_DIR}/azure/cli.sh" 2>/dev/null || true
# Log that libraries are loaded (only in debug mode)
[ "${LOG_LEVEL:-1}" -le 0 ] && log_debug "Common libraries loaded from ${LIB_DIR}"