mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
335 lines
10 KiB
Bash
Executable file
335 lines
10 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# GitOps Migration Tool
|
|
# Moves self_service, categories, labels_exclude_any, labels_include_any keys
|
|
# from software YAML files to team YAML files
|
|
#
|
|
# Usage: ./migrate.sh <teams_directory_path>
|
|
# Example: ./migrate.sh it-and-security/teams
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Global counters
|
|
PROCESSED_TEAMS=0
|
|
PROCESSED_PACKAGES=0
|
|
ERRORS=0
|
|
|
|
# Array to track software files that have been processed
|
|
PROCESSED_SOFTWARE_FILES=()
|
|
|
|
# Check if yq is installed
|
|
check_dependencies() {
|
|
if ! command -v yq &> /dev/null; then
|
|
echo -e "${RED}Error: yq is required but not installed. Please install yq first.${NC}"
|
|
echo "Install with: brew install yq"
|
|
exit 1
|
|
fi
|
|
|
|
# Check yq version (we need v4+)
|
|
YQ_VERSION=$(yq --version | cut -d' ' -f4 | cut -d'v' -f2 | cut -d'.' -f1)
|
|
if [[ "$YQ_VERSION" == "" ]]; then
|
|
YQ_VERSION="0"
|
|
fi
|
|
if [ "$YQ_VERSION" -lt 4 ]; then
|
|
echo -e "${RED}Error: yq version 4 or higher is required${NC}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Show usage information
|
|
show_usage() {
|
|
echo "Usage: $0 <teams_directory_path>"
|
|
echo
|
|
echo "Process YAML files in the specified teams directory, moving keys from"
|
|
echo "referenced software files to the team files."
|
|
echo
|
|
echo "Arguments:"
|
|
echo " teams_directory_path Path to directory containing team YAML files"
|
|
echo
|
|
echo "Examples:"
|
|
echo " $0 it-and-security/teams"
|
|
echo " $0 /path/to/teams"
|
|
echo
|
|
echo "Keys moved: self_service, categories, labels_include_any, labels_exclude_any"
|
|
}
|
|
|
|
# Validate YAML syntax
|
|
validate_yaml() {
|
|
local file="$1"
|
|
if ! yq eval '.' "$file" >/dev/null 2>&1; then
|
|
echo -e "${RED}Error: Invalid YAML syntax in $file${NC}"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Extract target keys from software file
|
|
extract_keys_from_software() {
|
|
local software_file="$1"
|
|
local temp_file=$(mktemp -p .)
|
|
chmod 666 $temp_file
|
|
|
|
# Extract the keys we need
|
|
{
|
|
echo "# Extracted keys from $software_file"
|
|
yq eval 'pick(["self_service", "categories", "labels_include_any", "labels_exclude_any"])' "$software_file" 2>/dev/null || echo "{}"
|
|
} > "$temp_file"
|
|
|
|
echo "$temp_file"
|
|
}
|
|
|
|
# Remove target keys from software file
|
|
remove_keys_from_software() {
|
|
local software_file="$1"
|
|
|
|
echo -e "${BLUE} Removing keys from: $software_file${NC}"
|
|
|
|
# Create a temporary file with keys removed
|
|
local temp_file=$(mktemp -p .)
|
|
chmod 666 $temp_file
|
|
yq eval --output-format=yaml 'del(.self_service, .categories, .labels_include_any, .labels_exclude_any)' "$software_file" > "$temp_file"
|
|
|
|
# Replace the original file
|
|
mv "$temp_file" "$software_file"
|
|
}
|
|
|
|
# Add keys to team file at specific package index
|
|
add_keys_to_team_file() {
|
|
local team_file="$1"
|
|
local package_index="$2"
|
|
local keys_file="$3"
|
|
|
|
# Check if keys file has any meaningful content
|
|
if ! yq eval 'keys | length > 0' "$keys_file" >/dev/null 2>&1; then
|
|
echo -e "${YELLOW} No keys to move${NC}"
|
|
return 0
|
|
fi
|
|
|
|
echo -e "${BLUE} Adding keys to team file at package index $package_index${NC}"
|
|
|
|
|
|
# Process each key type directly on the team file to preserve formatting
|
|
for key in "self_service" "categories" "labels_include_any" "labels_exclude_any"; do
|
|
if yq eval "has(\"$key\")" "$keys_file" | grep -q "true"; then
|
|
# Use yq to properly extract and merge the value, preserving arrays and complex structures
|
|
if yq eval ".$key != null" "$keys_file" | grep -q "true"; then
|
|
yq eval -i ".software.packages[$package_index].$key = load(\"$keys_file\").$key" "$team_file"
|
|
echo -e "${GREEN} Added $key${NC}"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Process a single team file (Pass 1: Add keys to team files only)
|
|
process_team_file() {
|
|
local team_file="$1"
|
|
echo -e "${GREEN}Processing team file: $team_file${NC}"
|
|
|
|
# Check if file has software.packages section
|
|
if ! yq eval 'has("software") and .software | has("packages")' "$team_file" | grep -q "true"; then
|
|
echo -e "${YELLOW} No software.packages section found, skipping${NC}"
|
|
return 0
|
|
fi
|
|
|
|
# Get the number of packages
|
|
local package_count=$(yq eval '.software.packages | length' "$team_file")
|
|
echo -e "${BLUE} Found $package_count packages${NC}"
|
|
|
|
# Process each package
|
|
for ((i=0; i<package_count; i++)); do
|
|
echo -e "${BLUE} Processing package $((i+1))/$package_count${NC}"
|
|
|
|
# Get the path from the package
|
|
local package_path=$(yq eval ".software.packages[$i].path" "$team_file")
|
|
|
|
if [ "$package_path" = "null" ]; then
|
|
echo -e "${YELLOW} No path found, skipping${NC}"
|
|
continue
|
|
fi
|
|
|
|
echo -e "${BLUE} Package path: $package_path${NC}"
|
|
|
|
# Convert relative path to absolute path
|
|
local team_dir=$(dirname "$team_file")
|
|
local software_file="$team_dir/$package_path"
|
|
|
|
# Normalize the path
|
|
software_file=$(realpath "$software_file" 2>/dev/null || echo "$software_file")
|
|
|
|
if [ ! -f "$software_file" ]; then
|
|
echo -e "${RED} Error: Software file not found: $software_file${NC}"
|
|
ERRORS=$((ERRORS+1))
|
|
continue
|
|
fi
|
|
|
|
# Validate software file
|
|
if ! validate_yaml "$software_file"; then
|
|
echo -e "${RED} Error: Invalid YAML in software file${NC}"
|
|
ERRORS=$((ERRORS+1))
|
|
continue
|
|
fi
|
|
|
|
echo -e "${BLUE} Processing: $software_file${NC}"
|
|
|
|
# Extract keys from software file
|
|
local keys_temp_file=$(extract_keys_from_software "$software_file")
|
|
|
|
# Add keys to team file
|
|
add_keys_to_team_file "$team_file" "$i" "$keys_temp_file"
|
|
|
|
# Track this software file for cleanup in pass 2
|
|
PROCESSED_SOFTWARE_FILES+=("$software_file")
|
|
|
|
# Clean up temp file
|
|
rm -f "$keys_temp_file"
|
|
|
|
PROCESSED_PACKAGED=$((PROCESSED_PACKAGES+1))
|
|
echo -e "${GREEN} ✓ Package processed successfully${NC}"
|
|
done
|
|
|
|
# Validate the modified team file
|
|
if ! validate_yaml "$team_file"; then
|
|
echo -e "${RED} Error: Team file became invalid after processing${NC}"
|
|
ERRORS=$((ERRORS+1))
|
|
return 1
|
|
fi
|
|
|
|
PROCESSED_TEAMS=$((PROCESSED_TEAMS+1))
|
|
echo -e "${GREEN}✓ Team file processed successfully${NC}"
|
|
echo
|
|
}
|
|
|
|
# Pass 2: Remove keys from all processed software files
|
|
cleanup_software_files() {
|
|
echo -e "${GREEN}=== PASS 2: CLEANING UP SOFTWARE FILES ===${NC}"
|
|
|
|
# Remove duplicates from the array
|
|
local unique_files=($(printf "%s\n" "${PROCESSED_SOFTWARE_FILES[@]}" | sort -u))
|
|
|
|
echo -e "${BLUE}Removing keys from ${#unique_files[@]} unique software files${NC}"
|
|
|
|
for software_file in "${unique_files[@]}"; do
|
|
echo -e "${BLUE} Removing keys from: $software_file${NC}"
|
|
remove_keys_from_software "$software_file"
|
|
done
|
|
|
|
echo -e "${GREEN}✓ Software file cleanup complete${NC}"
|
|
echo
|
|
}
|
|
# Fix Unicode escape sequences back to emoji characters
|
|
fix_unicode_emojis() {
|
|
local file="$1"
|
|
echo -e "${BLUE} Restoring emoji characters in: $(basename "$file")${NC}"
|
|
|
|
# Use perl to convert Unicode escape sequences back to actual characters
|
|
if command -v perl &> /dev/null; then
|
|
perl -i -pe 's/\\U([0-9A-F]{8})/chr(hex($1))/ge' "$file"
|
|
else
|
|
# Fallback: convert specific known emojis manually
|
|
sed -i '' 's/\\U0001F4BB/💻/g' "$file" 2>/dev/null || sed -i 's/\\U0001F4BB/💻/g' "$file"
|
|
sed -i '' 's/\\U0001F423/🐣/g' "$file" 2>/dev/null || sed -i 's/\\U0001F423/🐣/g' "$file"
|
|
fi
|
|
}
|
|
|
|
# Fix emojis in all processed team files
|
|
restore_emojis_in_team_files() {
|
|
echo -e "${GREEN}=== RESTORING EMOJI CHARACTERS ===${NC}"
|
|
|
|
for team_file in "${team_files[@]}"; do
|
|
if [ -f "$team_file" ] && grep -q "\\\\U[0-9A-F]" "$team_file"; then
|
|
fix_unicode_emojis "$team_file"
|
|
fi
|
|
done
|
|
|
|
echo -e "${GREEN}✓ Emoji restoration complete${NC}"
|
|
echo
|
|
}
|
|
|
|
|
|
# Main function
|
|
main() {
|
|
# Check if directory path argument is provided
|
|
if [ $# -eq 0 ]; then
|
|
echo -e "${RED}Error: No teams directory path provided${NC}"
|
|
echo
|
|
show_usage
|
|
exit 1
|
|
fi
|
|
|
|
# Handle help flags
|
|
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
|
show_usage
|
|
exit 0
|
|
fi
|
|
|
|
local teams_dir="$1"
|
|
|
|
echo -e "${GREEN}GitOps Migration Tool${NC}"
|
|
echo -e "${BLUE}Moving keys from software files to team files${NC}"
|
|
echo -e "${BLUE}Teams directory: $teams_dir${NC}"
|
|
echo
|
|
|
|
# Check dependencies
|
|
check_dependencies
|
|
|
|
# Check if teams directory exists
|
|
if [ ! -d "$teams_dir" ]; then
|
|
echo -e "${RED}Error: Teams directory not found: $teams_dir${NC}"
|
|
echo "Please provide a valid directory path"
|
|
exit 1
|
|
fi
|
|
|
|
# Process all YAML files in teams directory
|
|
echo -e "${BLUE}Finding team files...${NC}"
|
|
|
|
# Use nullglob to handle case where no files match the pattern
|
|
shopt -s nullglob
|
|
local team_files=("$teams_dir"/*.yml)
|
|
shopt -u nullglob
|
|
|
|
if [ ${#team_files[@]} -eq 0 ]; then
|
|
echo -e "${RED}Error: No YAML files found in teams directory${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${BLUE}Found ${#team_files[@]} team files${NC}"
|
|
echo
|
|
|
|
# PASS 1: Process each team file (add keys to team files)
|
|
echo -e "${GREEN}=== PASS 1: UPDATING TEAM FILES ===${NC}"
|
|
for team_file in "${team_files[@]}"; do
|
|
if [ -f "$team_file" ]; then
|
|
process_team_file "$team_file"
|
|
fi
|
|
done
|
|
|
|
# PASS 2: Clean up software files (remove keys from software files)
|
|
if [ ${#PROCESSED_SOFTWARE_FILES[@]} -gt 0 ]; then
|
|
cleanup_software_files
|
|
fi
|
|
|
|
# PASS 3: Restore emoji characters that may have been converted to Unicode escape sequences
|
|
restore_emojis_in_team_files
|
|
|
|
# Summary
|
|
echo -e "${GREEN}=== PROCESSING COMPLETE ===${NC}"
|
|
echo -e "${GREEN}Teams processed: $PROCESSED_TEAMS${NC}"
|
|
echo -e "${GREEN}Packages processed: $PROCESSED_PACKAGES${NC}"
|
|
if [ $ERRORS -gt 0 ]; then
|
|
echo -e "${RED}Errors encountered: $ERRORS${NC}"
|
|
echo -e "${YELLOW}Check the output above for details${NC}"
|
|
else
|
|
echo -e "${GREEN}✓ All files processed successfully!${NC}"
|
|
fi
|
|
|
|
}
|
|
|
|
# Run main function with all script arguments
|
|
main "$@"
|