Tests/AutomaticReadiness.Tests.ps1
|
$modulePath = Join-Path $PSScriptRoot '..\KubeBuddy.psm1' Import-Module $modulePath -Force Describe 'AKS Automatic readiness aggregation' { InModuleScope KubeBuddy { It 'marks readiness as not_ready when blockers exist' { $yamlChecks = @( [pscustomobject]@{ ID = 'SEC004' Name = 'Privileged Containers' Severity = 'critical' Category = 'Pod Security' AutomaticRelevance = 'blocker' AutomaticScope = 'workload' AutomaticReason = 'privileged' Total = 2 FailMessage = 'Privileged container found' Recommendation = 'Remove privileged mode.' URL = 'https://example.test/sec004' Items = @( [pscustomobject]@{ Resource = 'pod/ns1-a'; Issue = 'privileged=true' }, [pscustomobject]@{ Resource = 'pod/ns1-b'; Issue = 'privileged=true' } ) } ) $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'not_ready' $readiness.summary.blockerCount | Should -Be 1 $readiness.blockers.Count | Should -Be 1 $readiness.actionPlan.Count | Should -Be 1 $readiness.actionPlan[0].phase | Should -Be 'fix_before_migration' } It 'treats latest image tags as an AKS Automatic blocker with deny behavior' { $yamlChecks = @( [pscustomobject]@{ ID = 'POD007' Name = 'Container images do not use latest tag' Severity = 'critical' Category = 'Resource Management' AutomaticRelevance = 'blocker' AutomaticScope = 'workload' AutomaticReason = 'image_tag' AutomaticAdmissionBehavior = 'denies_on_enforce' AutomaticMutationOutcome = 'AKS Deployment Safeguards can deny workloads that use the latest tag or omit an explicit version tag.' Total = 1 FailMessage = 'Container image uses the latest tag' Recommendation = 'Specify an explicit image tag.' URL = 'https://example.test/pod007' Items = @( [pscustomobject]@{ Namespace = 'apps' Resource = 'pod/web-123' } ) } ) $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'not_ready' $readiness.blockers.Count | Should -Be 1 $readiness.blockers[0].admissionNote | Should -Match 'deny' $readiness.actionPlan[0].title | Should -Be 'Use explicit image tags' } It 'marks readiness as ready_with_changes when only warnings exist' { $yamlChecks = @( [pscustomobject]@{ ID = 'WRK005' Name = 'Missing Resource Requests or Limits' Severity = 'warning' Category = 'Workloads' AutomaticRelevance = 'warning' AutomaticScope = 'workload' AutomaticReason = 'resource_requests' Total = 1 FailMessage = 'Missing requests' Recommendation = 'Define requests.' URL = 'https://example.test/wrk005' Items = @([pscustomobject]@{ Resource = 'deployment/web'; Issue = 'CPU request missing' }) } ) $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'ready_with_changes' $readiness.summary.blockerCount | Should -Be 0 $readiness.summary.warningCount | Should -Be 1 $readiness.warnings.Count | Should -Be 1 } It 'treats missing resource requests as an AKS Automatic blocker' { $yamlChecks = @( [pscustomobject]@{ ID = 'WRK005' Name = 'Missing Resource Requests or Limits' Severity = 'warning' Category = 'Workloads' AutomaticRelevance = 'blocker' AutomaticScope = 'workload' AutomaticReason = 'resource_requests' AutomaticAdmissionBehavior = 'denies_on_enforce' AutomaticMutationOutcome = 'Observed on AKS Automatic: workloads with missing resource requests can be denied at admission.' Total = 1 FailMessage = 'Missing requests' Recommendation = 'Define requests.' URL = 'https://example.test/wrk005' Items = @([pscustomobject]@{ Resource = 'deployment/web'; Issue = 'CPU request missing' }) } ) $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'not_ready' $readiness.blockers[0].admissionNote | Should -Match 'deny' } It 'surfaces seccomp not configured as a warning' { $yamlChecks = @( [pscustomobject]@{ ID = 'SEC020' Name = 'Seccomp Profile Not Configured' Severity = 'warning' Category = 'Pod Security' AutomaticRelevance = 'warning' AutomaticScope = 'workload' AutomaticReason = 'seccomp' AutomaticAdmissionBehavior = 'warns_only' AutomaticMutationOutcome = 'Observed on AKS Automatic: workloads without an explicit seccomp profile can generate a warning.' Total = 1 FailMessage = 'Seccomp profile is not configured' Recommendation = 'Set seccompProfile.type to RuntimeDefault.' URL = 'https://example.test/sec020' Items = @([pscustomobject]@{ Resource = 'pod/web-123'; Issue = 'No explicit seccomp profile' }) } ) $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'ready_with_changes' $readiness.warnings[0].admissionNote | Should -Match 'warning' } It 'groups spread-constraint failures into an AKS Automatic blocker action' { $yamlChecks = @( [pscustomobject]@{ ID = 'WRK015' Name = 'Replicated Workloads Missing Spread Constraints' Severity = 'warning' Category = 'Workloads' AutomaticRelevance = 'blocker' AutomaticScope = 'workload' AutomaticReason = 'pod_spread' AutomaticAdmissionBehavior = 'denies_on_enforce' AutomaticMutationOutcome = 'Observed on AKS Automatic: replicated workloads without pod anti-affinity or topology spread constraints can be denied at admission.' Total = 1 FailMessage = 'Replicated workload has no pod spreading rules.' Recommendation = 'Add topology spread constraints.' URL = 'https://example.test/wrk015' Items = @([pscustomobject]@{ Resource = 'deployment/web'; Issue = 'No spread constraints' }) } ) $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'not_ready' $readiness.actionPlan[0].title | Should -Be 'Add workload spreading rules' } It 'groups duplicate service selectors into an AKS Automatic blocker action' { $yamlChecks = @( [pscustomobject]@{ ID = 'NET018' Name = 'Duplicate Service Selectors' Severity = 'warning' Category = 'Networking' AutomaticRelevance = 'blocker' AutomaticScope = 'workload' AutomaticReason = 'service_selector' AutomaticAdmissionBehavior = 'denies_on_enforce' AutomaticMutationOutcome = 'Observed on AKS Automatic: creating multiple Services with the same selector can be denied at admission.' Total = 2 FailMessage = 'Multiple Services share the same selector' Recommendation = 'Use unique selectors.' URL = 'https://example.test/net018' Items = @( [pscustomobject]@{ Resource = 'service/web'; Issue = 'Duplicate selector' }, [pscustomobject]@{ Resource = 'service/web-canary'; Issue = 'Duplicate selector' } ) } ) $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'not_ready' $readiness.actionPlan[0].title | Should -Be 'Use unique Service selectors' } It 'tracks AKS alignment separately from readiness' { $aksChecks = @( [pscustomobject]@{ ID = 'AKSBP015' Name = 'Deployment Safeguards Enabled' Severity = 'medium' Category = 'Best Practices' AutomaticRelevance = 'alignment' AutomaticScope = 'cluster' AutomaticReason = 'aks_platform' Total = 1 FailMessage = 'Deployment safeguards disabled' Recommendation = 'Enable safeguards.' URL = 'https://example.test/aksbp015' Items = @([pscustomobject]@{ Resource = 'cluster/aks-demo'; Issue = 'disabled' }) } ) $readiness = Get-KubeBuddyAutomaticReadiness -AksChecks $aksChecks -ClusterName 'aks-demo' $readiness.summary.status | Should -Be 'ready' $readiness.alignment.status | Should -Be 'not_aligned' $readiness.alignment.failed | Should -Be 1 $readiness.actionPlan.Count | Should -Be 0 $readiness.targetClusterBuildNotes.Count | Should -Be 1 } It 'resolves pod findings to owning workload and helm metadata' { $yamlChecks = @( [pscustomobject]@{ ID = 'SEC004' Name = 'Privileged Containers' Severity = 'critical' Category = 'Pod Security' AutomaticRelevance = 'blocker' AutomaticScope = 'workload' AutomaticReason = 'privileged' Total = 1 FailMessage = 'Privileged container found' Recommendation = 'Remove privileged mode.' URL = 'https://example.test/sec004' Items = @( [pscustomobject]@{ Namespace = 'apps' Pod = 'web-6d4cf56db6-abcde' Resource = 'pod/web-6d4cf56db6-abcde' } ) } ) $kubeData = [pscustomobject]@{ Pods = [pscustomobject]@{ items = @( [pscustomobject]@{ metadata = [pscustomobject]@{ namespace = 'apps' name = 'web-6d4cf56db6-abcde' ownerReferences = @( [pscustomobject]@{ kind = 'ReplicaSet' name = 'web-6d4cf56db6' controller = $true } ) } } ) } ReplicaSets = @( [pscustomobject]@{ metadata = [pscustomobject]@{ namespace = 'apps' name = 'web-6d4cf56db6' ownerReferences = @( [pscustomobject]@{ kind = 'Deployment' name = 'web' controller = $true } ) } } ) Deployments = @( [pscustomobject]@{ metadata = [pscustomobject]@{ namespace = 'apps' name = 'web' labels = [pscustomobject]@{ 'helm.sh/chart' = 'nginx-15.9.0' 'app.kubernetes.io/managed-by' = 'Helm' } annotations = [pscustomobject]@{ 'meta.helm.sh/release-name' = 'frontend' 'meta.helm.sh/release-namespace' = 'apps' } } spec = [pscustomobject]@{ template = [pscustomobject]@{ metadata = [pscustomobject]@{ labels = [pscustomobject]@{} annotations = [pscustomobject]@{} } } } } ) } $readiness = Get-KubeBuddyAutomaticReadiness -YamlChecks $yamlChecks -ClusterName 'aks-demo' -KubeData $kubeData $readiness.blockers[0].samples[0] | Should -Match 'Deployment/web via Pod/web-6d4cf56db6-abcde' $readiness.blockers[0].samples[0] | Should -Match 'Helm: release frontend, chart nginx@15.9.0' } It 'writes an action-plan HTML artifact' { $path = Join-Path $TestDrive 'aks-automatic-action-plan.html' $readiness = [pscustomobject]@{ summary = [pscustomobject]@{ status = 'ready_with_changes' statusLabel = 'Ready With Changes' blockerCount = 0 warningCount = 1 alignmentFailedCount = 0 } actionPlan = @( [pscustomobject]@{ phase = 'fix_before_migration' title = 'Define container resource requests' affectedCount = 3 recommendations = @('Add CPU and memory requests.') samples = @('deployment/web') } ) targetClusterBuildNotes = @( [pscustomobject]@{ title = 'Review AKS Automatic platform defaults' affectedCount = 1 recommendations = @('Enable deployment safeguards on the target cluster build.') steps = @('Create the destination cluster with the required platform defaults.') urls = @('https://example.test/aks-platform') samples = @('cluster/aks-demo') } ) } New-KubeBuddyAutomaticActionPlanHtml -OutputPath $path -Readiness $readiness -ClusterName 'aks-demo' Test-Path $path | Should -BeTrue (Get-Content -Raw $path) | Should -Match 'AKS Automatic Action Plan' (Get-Content -Raw $path) | Should -Match 'Define container resource requests' (Get-Content -Raw $path) | Should -Match 'Target Cluster Build Notes' } It 'skips readiness when the AKS cluster sku is Automatic' { $clusterInfo = [pscustomobject]@{ sku = [pscustomobject]@{ name = 'Automatic' } } $readiness = Get-KubeBuddyAutomaticReadiness -ClusterName 'aks-auto' -AksClusterInfo $clusterInfo $readiness.summary.status | Should -Be 'skipped' $readiness.summary.skipped | Should -BeTrue $readiness.actionPlan.Count | Should -Be 0 } } } |