939 lines
30 KiB
Plaintext
939 lines
30 KiB
Plaintext
#!/usr/bin/env nu
|
|
|
|
# Package validation tool - validates package integrity and completeness
|
|
#
|
|
# Validates:
|
|
# - Package structure and required files
|
|
# - Binary execution and dependencies
|
|
# - Configuration completeness
|
|
# - Installation script functionality
|
|
# - Security and integrity checks
|
|
|
|
use std log
|
|
|
|
def main [
|
|
--package-path: string # Path to package file or directory to validate
|
|
--validation-type: string = "complete" # Validation type: quick, complete, security
|
|
--temp-workspace: string = "" # Temporary workspace (auto-generated if empty)
|
|
--cleanup: bool = true # Cleanup workspace after validation
|
|
--skip-execution: bool = false # Skip binary execution tests
|
|
--verbose: bool = false # Enable verbose logging
|
|
--output-format: string = "text" # Output format: text, json, table
|
|
] -> record {
|
|
|
|
let package_root = ($package_path | path expand)
|
|
let temp_workspace = if $temp_workspace == "" {
|
|
$env.TMPDIR | path join $"package-validation-(random uuid)"
|
|
} else {
|
|
$temp_workspace | path expand
|
|
}
|
|
|
|
let validation_config = {
|
|
package_path: $package_root
|
|
validation_type: $validation_type
|
|
temp_workspace: $temp_workspace
|
|
cleanup: $cleanup
|
|
skip_execution: $skip_execution
|
|
verbose: $verbose
|
|
output_format: $output_format
|
|
}
|
|
|
|
log info $"Starting package validation with config: ($validation_config)"
|
|
|
|
# Validate package exists
|
|
if not ($package_root | path exists) {
|
|
log error $"Package does not exist: ($package_root)"
|
|
exit 1
|
|
}
|
|
|
|
# Create temporary workspace
|
|
mkdir ($validation_config.temp_workspace)
|
|
|
|
let validation_results = []
|
|
|
|
try {
|
|
# Extract/prepare package for validation
|
|
let preparation_result = prepare_package_for_validation $validation_config
|
|
|
|
if $preparation_result.status != "success" {
|
|
log error $"Failed to prepare package for validation: ($preparation_result.reason)"
|
|
exit 1
|
|
}
|
|
|
|
let package_dir = $preparation_result.extracted_path
|
|
|
|
# Run validation tests based on type
|
|
let validation_categories = match $validation_config.validation_type {
|
|
"quick" => ["structure", "files"]
|
|
"complete" => ["structure", "files", "binaries", "configuration", "installation"]
|
|
"security" => ["structure", "files", "binaries", "configuration", "installation", "security"]
|
|
_ => ["structure", "files"]
|
|
}
|
|
|
|
for category in $validation_categories {
|
|
let category_result = match $category {
|
|
"structure" => { validate_package_structure $package_dir $validation_config }
|
|
"files" => { validate_required_files $package_dir $validation_config }
|
|
"binaries" => { validate_binaries $package_dir $validation_config }
|
|
"configuration" => { validate_configuration $package_dir $validation_config }
|
|
"installation" => { validate_installation_scripts $package_dir $validation_config }
|
|
"security" => { validate_security $package_dir $validation_config }
|
|
_ => {
|
|
log warning $"Unknown validation category: ($category)"
|
|
{ category: $category, status: "skipped", reason: "unknown category" }
|
|
}
|
|
}
|
|
let validation_results = ($validation_results | append $category_result)
|
|
}
|
|
|
|
# Generate validation report
|
|
let validation_report = generate_validation_report $validation_results $validation_config
|
|
|
|
# Cleanup if requested
|
|
if $validation_config.cleanup {
|
|
rm -rf ($validation_config.temp_workspace)
|
|
log info "Cleaned up validation workspace"
|
|
}
|
|
|
|
let summary = {
|
|
total_categories: ($validation_results | length)
|
|
passed_categories: ($validation_results | where status == "passed" | length)
|
|
failed_categories: ($validation_results | where status == "failed" | length)
|
|
warnings: ($validation_results | get warnings | flatten | length)
|
|
overall_status: (if ($validation_results | where status == "failed" | length) > 0 { "failed" } else { "passed" })
|
|
validation_config: $validation_config
|
|
results: $validation_results
|
|
report: $validation_report
|
|
}
|
|
|
|
# Output results in requested format
|
|
output_validation_results $summary $validation_config
|
|
|
|
if $summary.overall_status == "failed" {
|
|
log error $"Package validation failed - ($summary.failed_categories) categories failed"
|
|
exit 1
|
|
} else {
|
|
log info $"Package validation passed - all ($summary.passed_categories) categories validated successfully"
|
|
}
|
|
|
|
return $summary
|
|
|
|
} catch {|err|
|
|
# Ensure cleanup even on error
|
|
if $validation_config.cleanup and ($validation_config.temp_workspace | path exists) {
|
|
rm -rf ($validation_config.temp_workspace)
|
|
}
|
|
|
|
log error $"Package validation failed: ($err.msg)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Prepare package for validation (extract if needed)
|
|
def prepare_package_for_validation [validation_config: record] -> record {
|
|
let package_path = $validation_config.package_path
|
|
|
|
# Determine if package is an archive or directory
|
|
if ($package_path | path type) == "dir" {
|
|
# Package is already a directory
|
|
return {
|
|
status: "success"
|
|
extracted_path: $package_path
|
|
extraction_needed: false
|
|
}
|
|
}
|
|
|
|
# Package is an archive, need to extract
|
|
let extraction_dir = ($validation_config.temp_workspace | path join "extracted")
|
|
mkdir $extraction_dir
|
|
|
|
try {
|
|
let package_name = ($package_path | path basename)
|
|
|
|
if ($package_name | str ends-with ".tar.gz") or ($package_name | str ends-with ".tgz") {
|
|
# Extract tar.gz
|
|
cd $extraction_dir
|
|
tar -xzf $package_path
|
|
} else if ($package_name | str ends-with ".zip") {
|
|
# Extract zip
|
|
cd $extraction_dir
|
|
unzip $package_path
|
|
} else {
|
|
return {
|
|
status: "failed"
|
|
reason: $"unsupported package format: ($package_name)"
|
|
}
|
|
}
|
|
|
|
# Find the extracted directory (usually there's a single top-level directory)
|
|
let extracted_contents = (ls $extraction_dir)
|
|
let extracted_path = if ($extracted_contents | length) == 1 and (($extracted_contents | get 0.type) == "dir") {
|
|
($extracted_contents | get 0.name)
|
|
} else {
|
|
$extraction_dir
|
|
}
|
|
|
|
return {
|
|
status: "success"
|
|
extracted_path: $extracted_path
|
|
extraction_needed: true
|
|
}
|
|
|
|
} catch {|err|
|
|
return {
|
|
status: "failed"
|
|
reason: $"extraction failed: ($err.msg)"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Validate package structure
|
|
def validate_package_structure [
|
|
package_dir: string
|
|
validation_config: record
|
|
] -> record {
|
|
log info "Validating package structure..."
|
|
|
|
let start_time = (date now)
|
|
let mut structure_issues = []
|
|
let mut structure_warnings = []
|
|
|
|
# Define expected structure for provisioning packages
|
|
let expected_structure = [
|
|
{ path: "platform", type: "dir", required: true, description: "Platform binaries directory" },
|
|
{ path: "core", type: "dir", required: true, description: "Core libraries directory" },
|
|
{ path: "config", type: "dir", required: true, description: "Configuration directory" },
|
|
{ path: "README.md", type: "file", required: false, description: "Package documentation" },
|
|
{ path: "install.sh", type: "file", required: false, description: "Installation script (Unix)" },
|
|
{ path: "install.bat", type: "file", required: false, description: "Installation script (Windows)" }
|
|
]
|
|
|
|
# Check each expected component
|
|
for component in $expected_structure {
|
|
let component_path = ($package_dir | path join $component.path)
|
|
let exists = ($component_path | path exists)
|
|
let correct_type = if $exists {
|
|
($component_path | path type) == $component.type
|
|
} else {
|
|
false
|
|
}
|
|
|
|
if $component.required and not $exists {
|
|
$structure_issues = ($structure_issues | append {
|
|
type: "missing_required"
|
|
path: $component.path
|
|
description: $component.description
|
|
severity: "error"
|
|
})
|
|
} else if $exists and not $correct_type {
|
|
$structure_issues = ($structure_issues | append {
|
|
type: "incorrect_type"
|
|
path: $component.path
|
|
expected: $component.type
|
|
actual: ($component_path | path type)
|
|
severity: "error"
|
|
})
|
|
} else if not $component.required and not $exists {
|
|
$structure_warnings = ($structure_warnings | append {
|
|
type: "missing_optional"
|
|
path: $component.path
|
|
description: $component.description
|
|
severity: "warning"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Check for unexpected files at root level
|
|
let root_files = (ls $package_dir | get name | each { path basename })
|
|
let expected_files = ($expected_structure | get path)
|
|
let unexpected_files = ($root_files | where {|file| not ($file in $expected_files) })
|
|
|
|
for unexpected in $unexpected_files {
|
|
$structure_warnings = ($structure_warnings | append {
|
|
type: "unexpected_file"
|
|
path: $unexpected
|
|
severity: "info"
|
|
})
|
|
}
|
|
|
|
let status = if ($structure_issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
{
|
|
category: "structure"
|
|
status: $status
|
|
issues: $structure_issues
|
|
warnings: $structure_warnings
|
|
checks_performed: ($expected_structure | length)
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
# Validate required files
|
|
def validate_required_files [
|
|
package_dir: string
|
|
validation_config: record
|
|
] -> record {
|
|
log info "Validating required files..."
|
|
|
|
let start_time = (date now)
|
|
let mut file_issues = []
|
|
let mut file_warnings = []
|
|
|
|
# Check platform binaries
|
|
let platform_dir = ($package_dir | path join "platform")
|
|
if ($platform_dir | path exists) {
|
|
let binaries = (find $platform_dir -type f -executable)
|
|
if ($binaries | length) == 0 {
|
|
$file_issues = ($file_issues | append {
|
|
type: "no_executables"
|
|
path: "platform/"
|
|
description: "No executable binaries found in platform directory"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
# Check for common binaries
|
|
let expected_binaries = ["provisioning-orchestrator", "control-center"]
|
|
for binary in $expected_binaries {
|
|
let binary_files = ($binaries | where ($it =~ $binary))
|
|
if ($binary_files | length) == 0 {
|
|
$file_warnings = ($file_warnings | append {
|
|
type: "missing_binary"
|
|
path: $"platform/($binary)"
|
|
description: $"Expected binary not found: ($binary)"
|
|
severity: "warning"
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check core libraries
|
|
let core_dir = ($package_dir | path join "core")
|
|
if ($core_dir | path exists) {
|
|
let essential_core_paths = ["lib", "bin/provisioning"]
|
|
for core_path in $essential_core_paths {
|
|
let full_path = ($core_dir | path join $core_path)
|
|
if not ($full_path | path exists) {
|
|
$file_issues = ($file_issues | append {
|
|
type: "missing_core_component"
|
|
path: $"core/($core_path)"
|
|
description: $"Essential core component missing: ($core_path)"
|
|
severity: "error"
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check configuration files
|
|
let config_dir = ($package_dir | path join "config")
|
|
if ($config_dir | path exists) {
|
|
let config_files = (find $config_dir -name "*.toml" -type f)
|
|
if ($config_files | length) == 0 {
|
|
$file_warnings = ($file_warnings | append {
|
|
type: "no_config_files"
|
|
path: "config/"
|
|
description: "No TOML configuration files found"
|
|
severity: "warning"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Validate file permissions
|
|
let permission_issues = validate_file_permissions $package_dir $validation_config
|
|
$file_issues = ($file_issues | append $permission_issues.issues)
|
|
$file_warnings = ($file_warnings | append $permission_issues.warnings)
|
|
|
|
let status = if ($file_issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
{
|
|
category: "files"
|
|
status: $status
|
|
issues: $file_issues
|
|
warnings: $file_warnings
|
|
checks_performed: 4
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
# Validate file permissions
|
|
def validate_file_permissions [
|
|
package_dir: string
|
|
validation_config: record
|
|
] -> record {
|
|
let mut issues = []
|
|
let mut warnings = []
|
|
|
|
# Check that executables are actually executable
|
|
let platform_dir = ($package_dir | path join "platform")
|
|
if ($platform_dir | path exists) {
|
|
let files = (ls $platform_dir | where type == file)
|
|
for file in $files {
|
|
let is_executable = try {
|
|
# Check if file has execute permissions
|
|
let perms = (ls -l $file.name | get 0.permissions)
|
|
$perms =~ "x"
|
|
} catch {
|
|
false
|
|
}
|
|
|
|
if not $is_executable {
|
|
$issues = ($issues | append {
|
|
type: "not_executable"
|
|
path: ($file.name | str replace $package_dir "")
|
|
description: "Binary file is not executable"
|
|
severity: "error"
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return { issues: $issues, warnings: $warnings }
|
|
}
|
|
|
|
# Validate binaries
|
|
def validate_binaries [
|
|
package_dir: string
|
|
validation_config: record
|
|
] -> record {
|
|
log info "Validating binaries..."
|
|
|
|
let start_time = (date now)
|
|
let mut binary_issues = []
|
|
let mut binary_warnings = []
|
|
|
|
let platform_dir = ($package_dir | path join "platform")
|
|
if not ($platform_dir | path exists) {
|
|
return {
|
|
category: "binaries"
|
|
status: "skipped"
|
|
reason: "no platform directory found"
|
|
issues: []
|
|
warnings: []
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
let binaries = (find $platform_dir -type f -executable)
|
|
|
|
for binary in $binaries {
|
|
let binary_result = validate_single_binary $binary $validation_config
|
|
|
|
if $binary_result.status == "failed" {
|
|
$binary_issues = ($binary_issues | append $binary_result.issues)
|
|
}
|
|
|
|
if ($binary_result.warnings | length) > 0 {
|
|
$binary_warnings = ($binary_warnings | append $binary_result.warnings)
|
|
}
|
|
}
|
|
|
|
let status = if ($binary_issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
{
|
|
category: "binaries"
|
|
status: $status
|
|
issues: $binary_issues
|
|
warnings: $binary_warnings
|
|
binaries_tested: ($binaries | length)
|
|
checks_performed: ($binaries | length)
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
# Validate a single binary
|
|
def validate_single_binary [
|
|
binary_path: string
|
|
validation_config: record
|
|
] -> record {
|
|
let binary_name = ($binary_path | path basename)
|
|
let mut issues = []
|
|
let mut warnings = []
|
|
|
|
if $validation_config.verbose {
|
|
log info $"Validating binary: ($binary_name)"
|
|
}
|
|
|
|
# Check binary format
|
|
try {
|
|
let file_info = (file $binary_path)
|
|
|
|
if not (($file_info =~ "ELF") or ($file_info =~ "Mach-O") or ($file_info =~ "PE32")) {
|
|
$issues = ($issues | append {
|
|
type: "invalid_binary_format"
|
|
binary: $binary_name
|
|
description: $"Binary format not recognized: ($file_info)"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
# Check if binary is stripped (for release builds)
|
|
if not ($file_info =~ "stripped") {
|
|
$warnings = ($warnings | append {
|
|
type: "not_stripped"
|
|
binary: $binary_name
|
|
description: "Binary contains debug symbols (not stripped)"
|
|
severity: "info"
|
|
})
|
|
}
|
|
|
|
} catch {|err|
|
|
$issues = ($issues | append {
|
|
type: "file_analysis_failed"
|
|
binary: $binary_name
|
|
description: $"Failed to analyze binary: ($err.msg)"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
# Test binary execution (if not skipped)
|
|
if not $validation_config.skip_execution {
|
|
let execution_result = test_binary_execution $binary_path $validation_config
|
|
|
|
if $execution_result.status == "failed" {
|
|
$issues = ($issues | append $execution_result.issues)
|
|
}
|
|
|
|
if ($execution_result.warnings | length) > 0 {
|
|
$warnings = ($warnings | append $execution_result.warnings)
|
|
}
|
|
}
|
|
|
|
let status = if ($issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
return {
|
|
status: $status
|
|
binary: $binary_name
|
|
issues: $issues
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Test binary execution
|
|
def test_binary_execution [
|
|
binary_path: string
|
|
validation_config: record
|
|
] -> record {
|
|
let binary_name = ($binary_path | path basename)
|
|
let mut issues = []
|
|
let mut warnings = []
|
|
|
|
# Test basic execution (--help or --version)
|
|
let help_commands = ["--help", "-h", "--version", "-V"]
|
|
let mut execution_success = false
|
|
|
|
for cmd in $help_commands {
|
|
try {
|
|
let result = (run-external --redirect-combine $binary_path $cmd | complete)
|
|
if $result.exit_code == 0 {
|
|
$execution_success = true
|
|
break
|
|
}
|
|
} catch {
|
|
# Continue to next command
|
|
}
|
|
}
|
|
|
|
if not $execution_success {
|
|
$issues = ($issues | append {
|
|
type: "execution_failed"
|
|
binary: $binary_name
|
|
description: "Binary does not respond to common help commands"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
# Check for missing dynamic dependencies (Linux/macOS)
|
|
try {
|
|
let ldd_result = match $nu.os-info.name {
|
|
"linux" => { run-external --redirect-combine "ldd" $binary_path | complete }
|
|
"macos" => { run-external --redirect-combine "otool" "-L" $binary_path | complete }
|
|
_ => { { exit_code: 1, stdout: "", stderr: "" } }
|
|
}
|
|
|
|
if $ldd_result.exit_code == 0 and ($ldd_result.stdout =~ "not found") {
|
|
$warnings = ($warnings | append {
|
|
type: "missing_dependencies"
|
|
binary: $binary_name
|
|
description: "Binary has missing dynamic dependencies"
|
|
severity: "warning"
|
|
})
|
|
}
|
|
} catch {
|
|
# Dependency checking not available or failed
|
|
}
|
|
|
|
let status = if ($issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
return {
|
|
status: $status
|
|
issues: $issues
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Validate configuration
|
|
def validate_configuration [
|
|
package_dir: string
|
|
validation_config: record
|
|
] -> record {
|
|
log info "Validating configuration..."
|
|
|
|
let start_time = (date now)
|
|
let mut config_issues = []
|
|
let mut config_warnings = []
|
|
|
|
let config_dir = ($package_dir | path join "config")
|
|
if not ($config_dir | path exists) {
|
|
return {
|
|
category: "configuration"
|
|
status: "skipped"
|
|
reason: "no config directory found"
|
|
issues: []
|
|
warnings: []
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
# Validate TOML files
|
|
let toml_files = (find $config_dir -name "*.toml" -type f)
|
|
|
|
for toml_file in $toml_files {
|
|
let toml_result = validate_toml_file $toml_file $validation_config
|
|
|
|
if $toml_result.status == "failed" {
|
|
$config_issues = ($config_issues | append $toml_result.issues)
|
|
}
|
|
|
|
if ($toml_result.warnings | length) > 0 {
|
|
$config_warnings = ($config_warnings | append $toml_result.warnings)
|
|
}
|
|
}
|
|
|
|
let status = if ($config_issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
{
|
|
category: "configuration"
|
|
status: $status
|
|
issues: $config_issues
|
|
warnings: $config_warnings
|
|
config_files_tested: ($toml_files | length)
|
|
checks_performed: ($toml_files | length)
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
# Validate TOML file
|
|
def validate_toml_file [
|
|
toml_file: string
|
|
validation_config: record
|
|
] -> record {
|
|
let file_name = ($toml_file | path basename)
|
|
let mut issues = []
|
|
let mut warnings = []
|
|
|
|
try {
|
|
# Try to parse TOML file
|
|
let toml_content = (open $toml_file)
|
|
|
|
# Check for essential sections (if it's a main config file)
|
|
if ($file_name =~ "config") {
|
|
let expected_sections = ["paths", "providers", "general"]
|
|
for section in $expected_sections {
|
|
if not ($section in ($toml_content | columns)) {
|
|
$warnings = ($warnings | append {
|
|
type: "missing_config_section"
|
|
file: $file_name
|
|
section: $section
|
|
description: $"Configuration section missing: ($section)"
|
|
severity: "warning"
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch {|err|
|
|
$issues = ($issues | append {
|
|
type: "toml_parse_error"
|
|
file: $file_name
|
|
description: $"Failed to parse TOML file: ($err.msg)"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
let status = if ($issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
return {
|
|
status: $status
|
|
file: $file_name
|
|
issues: $issues
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Validate installation scripts
|
|
def validate_installation_scripts [
|
|
package_dir: string
|
|
validation_config: record
|
|
] -> record {
|
|
log info "Validating installation scripts..."
|
|
|
|
let start_time = (date now)
|
|
let mut script_issues = []
|
|
let mut script_warnings = []
|
|
|
|
# Check for installation scripts
|
|
let install_scripts = [
|
|
{ name: "install.sh", platform: "unix" },
|
|
{ name: "install.bat", platform: "windows" }
|
|
]
|
|
|
|
let mut found_scripts = 0
|
|
|
|
for script in $install_scripts {
|
|
let script_path = ($package_dir | path join $script.name)
|
|
|
|
if ($script_path | path exists) {
|
|
$found_scripts = $found_scripts + 1
|
|
let script_result = validate_install_script $script_path $script.platform $validation_config
|
|
|
|
if $script_result.status == "failed" {
|
|
$script_issues = ($script_issues | append $script_result.issues)
|
|
}
|
|
|
|
if ($script_result.warnings | length) > 0 {
|
|
$script_warnings = ($script_warnings | append $script_result.warnings)
|
|
}
|
|
}
|
|
}
|
|
|
|
if $found_scripts == 0 {
|
|
$script_warnings = ($script_warnings | append {
|
|
type: "no_install_scripts"
|
|
description: "No installation scripts found"
|
|
severity: "warning"
|
|
})
|
|
}
|
|
|
|
let status = if ($script_issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
{
|
|
category: "installation"
|
|
status: $status
|
|
issues: $script_issues
|
|
warnings: $script_warnings
|
|
scripts_found: $found_scripts
|
|
checks_performed: $found_scripts
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
# Validate install script
|
|
def validate_install_script [
|
|
script_path: string
|
|
platform: string
|
|
validation_config: record
|
|
] -> record {
|
|
let script_name = ($script_path | path basename)
|
|
let mut issues = []
|
|
let mut warnings = []
|
|
|
|
# Check script is executable
|
|
let is_executable = try {
|
|
let perms = (ls -l $script_path | get 0.permissions)
|
|
$perms =~ "x"
|
|
} catch {
|
|
false
|
|
}
|
|
|
|
if not $is_executable and $platform == "unix" {
|
|
$issues = ($issues | append {
|
|
type: "script_not_executable"
|
|
script: $script_name
|
|
description: "Install script is not executable"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
# Basic syntax check
|
|
try {
|
|
let content = (open $script_path --raw)
|
|
|
|
if $platform == "unix" {
|
|
# Check for shebang
|
|
if not ($content | str starts-with "#!") {
|
|
$warnings = ($warnings | append {
|
|
type: "missing_shebang"
|
|
script: $script_name
|
|
description: "Script missing shebang line"
|
|
severity: "warning"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Check for dangerous commands
|
|
let dangerous_patterns = ["rm -rf /", "sudo rm -rf", "format c:"]
|
|
for pattern in $dangerous_patterns {
|
|
if ($content =~ $pattern) {
|
|
$issues = ($issues | append {
|
|
type: "dangerous_command"
|
|
script: $script_name
|
|
pattern: $pattern
|
|
description: $"Script contains potentially dangerous command: ($pattern)"
|
|
severity: "error"
|
|
})
|
|
}
|
|
}
|
|
|
|
} catch {|err|
|
|
$issues = ($issues | append {
|
|
type: "script_read_error"
|
|
script: $script_name
|
|
description: $"Failed to read script: ($err.msg)"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
let status = if ($issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
return {
|
|
status: $status
|
|
script: $script_name
|
|
issues: $issues
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Validate security aspects
|
|
def validate_security [
|
|
package_dir: string
|
|
validation_config: record
|
|
] -> record {
|
|
log info "Validating security..."
|
|
|
|
let start_time = (date now)
|
|
let mut security_issues = []
|
|
let mut security_warnings = []
|
|
|
|
# Check for potentially unsafe files
|
|
let unsafe_patterns = ["*.tmp", "*.log", "*password*", "*secret*", "*.key", "*.pem"]
|
|
for pattern in $unsafe_patterns {
|
|
let found_files = (find $package_dir -name $pattern -type f)
|
|
for file in $found_files {
|
|
$security_warnings = ($security_warnings | append {
|
|
type: "potentially_unsafe_file"
|
|
path: ($file | str replace $package_dir "")
|
|
description: $"File may contain sensitive information: ($file | path basename)"
|
|
severity: "warning"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Check file permissions for security
|
|
let world_writable = (find $package_dir -perm -o+w -type f)
|
|
for file in $world_writable {
|
|
$security_issues = ($security_issues | append {
|
|
type: "world_writable_file"
|
|
path: ($file | str replace $package_dir "")
|
|
description: "File is world-writable"
|
|
severity: "error"
|
|
})
|
|
}
|
|
|
|
let status = if ($security_issues | length) > 0 { "failed" } else { "passed" }
|
|
|
|
{
|
|
category: "security"
|
|
status: $status
|
|
issues: $security_issues
|
|
warnings: $security_warnings
|
|
checks_performed: 2
|
|
duration: ((date now) - $start_time)
|
|
}
|
|
}
|
|
|
|
# Generate validation report
|
|
def generate_validation_report [
|
|
validation_results: list
|
|
validation_config: record
|
|
] -> record {
|
|
let total_issues = ($validation_results | get issues | flatten | length)
|
|
let total_warnings = ($validation_results | get warnings | flatten | length)
|
|
|
|
let report = {
|
|
timestamp: (date now)
|
|
package_path: $validation_config.package_path
|
|
validation_type: $validation_config.validation_type
|
|
overall_status: (if ($validation_results | where status == "failed" | length) > 0 { "failed" } else { "passed" })
|
|
summary: {
|
|
total_categories: ($validation_results | length)
|
|
passed: ($validation_results | where status == "passed" | length)
|
|
failed: ($validation_results | where status == "failed" | length)
|
|
skipped: ($validation_results | where status == "skipped" | length)
|
|
total_issues: $total_issues
|
|
total_warnings: $total_warnings
|
|
}
|
|
categories: $validation_results
|
|
all_issues: ($validation_results | get issues | flatten)
|
|
all_warnings: ($validation_results | get warnings | flatten)
|
|
}
|
|
|
|
# Save report to file
|
|
let report_file = ($validation_config.temp_workspace | path join "validation-report.json")
|
|
$report | to json | save $report_file
|
|
log info $"Validation report saved to: ($report_file)"
|
|
|
|
return $report
|
|
}
|
|
|
|
# Output validation results in requested format
|
|
def output_validation_results [
|
|
summary: record
|
|
validation_config: record
|
|
] {
|
|
match $validation_config.output_format {
|
|
"json" => {
|
|
$summary | to json
|
|
}
|
|
"table" => {
|
|
print $"Package Validation Results for ($validation_config.package_path)"
|
|
print $"Overall Status: ($summary.overall_status)"
|
|
print ""
|
|
|
|
$summary.results | select category status issues warnings | table
|
|
|
|
if ($summary.results | get issues | flatten | length) > 0 {
|
|
print "\nIssues Found:"
|
|
$summary.results | get issues | flatten | table
|
|
}
|
|
}
|
|
_ => {
|
|
# Default text format
|
|
print $"Package Validation Results"
|
|
print $"========================="
|
|
print $"Package: ($validation_config.package_path)"
|
|
print $"Validation Type: ($validation_config.validation_type)"
|
|
print $"Overall Status: ($summary.overall_status)"
|
|
print ""
|
|
|
|
for result in $summary.results {
|
|
print $"($result.category | str title): ($result.status | str upcase)"
|
|
if ($result.issues | length) > 0 {
|
|
print $" Issues: ($result.issues | length)"
|
|
}
|
|
if ($result.warnings | length) > 0 {
|
|
print $" Warnings: ($result.warnings | length)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Quick validation command
|
|
def "main quick" [package_path: string] {
|
|
main $package_path --validation-type quick
|
|
}
|
|
|
|
# Security validation command
|
|
def "main security" [package_path: string] {
|
|
main $package_path --validation-type security
|
|
} |