tests/Test-EntroGcpIamGrants.ps1
|
#Requires -Version 7 $ErrorActionPreference = 'Stop' . (Join-Path $PSScriptRoot '_TestHelpers.ps1') $wizardRoot = Split-Path $PSScriptRoot -Parent Import-IscModule -Path (Join-Path $wizardRoot 'modules/ISC.OperatorConsole.psm1') -Force Import-IscModule -Path (Join-Path $wizardRoot 'modules/ISC.EntroGcp.psm1') -Force $org = '416188549418' $project = 'emea-tes-team' $sa = 'entro-automatic-onboard' $member = "serviceAccount:$sa@$project.iam.gserviceaccount.com" $env:ENTRO_GCP_IAM_STATE = Join-Path ([System.IO.Path]::GetTempPath()) "entro-iam-test-$([guid]::NewGuid()).state" $script:Bound = [System.Collections.Generic.HashSet[string]]::new() $script:Roles = @{} $script:Calls = [System.Collections.Generic.List[string]]::new() $script:FailOnRole = $null function global:Invoke-GCloud { param([string[]]$GcloudArgs) $line = $GcloudArgs -join ' ' $script:Calls.Add($line) $arg = { param($prefix) ($GcloudArgs | Where-Object { $_ -like "$prefix*" } | Select-Object -First 1) -replace "^$([regex]::Escape($prefix))", '' } switch -Regex ($line) { '^organizations get-iam-policy .*--filter=bindings\.role=' { $role = & $arg '--filter=bindings.role=' if ($script:Bound.Contains($role)) { return $member } else { return '' } } '^organizations get-iam-policy .*--filter=bindings\.members=' { return (@($script:Bound) -join "`n") } '^organizations add-iam-policy-binding' { $role = & $arg '--role=' if ($role -eq $script:FailOnRole) { throw "simulated failure binding $role" } $null = $script:Bound.Add($role); return '' } '^organizations remove-iam-policy-binding' { $null = $script:Bound.Remove((& $arg '--role=')); return '' } '^iam roles describe' { $id = $GcloudArgs[3] if (-not $script:Roles.ContainsKey($id)) { throw "NOT_FOUND $id" } return ($script:Roles[$id] -join ';') } '^iam roles create' { $script:Roles[$GcloudArgs[3]] = @((& $arg '--permissions=') -split ','); return '' } '^iam roles update' { $script:Roles[$GcloudArgs[3]] = @((& $arg '--permissions=') -split ','); return '' } '^iam roles delete' { $script:Roles.Remove($GcloudArgs[3]); return '' } default { throw "unexpected gcloud call: $line" } } } $iam = @{ OrganizationId = $org; ProjectId = $project; ServiceAccountName = $sa } function Get-CreatedRoleCount { @($script:Calls | Where-Object { $_ -like 'iam roles create*' }).Count } try { # An interrupted run keeps what it already did in the state file. $script:FailOnRole = 'roles/iam.supportUser' $failed = $false try { Invoke-EntroGcpIamGrants @iam -Mode apply 6>$null } catch { $failed = $true } Assert-True $failed 'simulated mid-run failure surfaces' Assert-True (Test-Path -LiteralPath $env:ENTRO_GCP_IAM_STATE) 'state file is written before the failure' $stateText = Get-Content -LiteralPath $env:ENTRO_GCP_IAM_STATE -Raw Assert-True ($stateText -match 'custom-role\tentroLoggingRole_\d+') 'state records the created custom role' Assert-True ($stateText -match 'roles/secretmanager\.viewer') 'state records bindings added before the failure' $roleId = ([regex]::Match($stateText, 'entroLoggingRole_\d+')).Value # Rerun resumes from the state file instead of refusing. $script:FailOnRole = $null Invoke-EntroGcpIamGrants @iam -Mode apply 6>$null Assert-Equal 1 (Get-CreatedRoleCount) 'resume reuses the recorded custom role' Assert-True (Test-EntroGcpIamGrantsComplete @iam) 'resume completes every grant' Assert-True ((Get-Content -LiteralPath $env:ENTRO_GCP_IAM_STATE -Raw) -match 'roles/iam\.supportUser') 'state records the bindings added on resume' # A complete setup is a no-op, even with a state file present. $before = @($script:Calls | Where-Object { $_ -match 'add-iam-policy-binding|roles (create|update)' }).Count Invoke-EntroGcpIamGrants @iam -Mode apply 6>$null $after = @($script:Calls | Where-Object { $_ -match 'add-iam-policy-binding|roles (create|update)' }).Count Assert-Equal $before $after 'complete setup makes no changes' # Lost state file: adopt the bound custom role instead of minting a second one. Remove-Item -LiteralPath $env:ENTRO_GCP_IAM_STATE -Force $null = $script:Bound.Remove('roles/cloudasset.viewer') Invoke-EntroGcpIamGrants @iam -Mode apply 6>$null Assert-Equal 1 (Get-CreatedRoleCount) 'lost state adopts the already-bound custom role' Assert-True (Test-EntroGcpIamGrantsComplete @iam) 'lost state still completes missing grants' # Rollback removes everything the state file recorded. Remove-Item -LiteralPath $env:ENTRO_GCP_IAM_STATE -Force $script:Bound.Clear(); $script:Roles.Clear(); $script:Calls.Clear() Invoke-EntroGcpIamGrants @iam -Mode apply 6>$null Invoke-EntroGcpIamGrants @iam -Mode rollback 6>$null Assert-Equal 0 $script:Bound.Count 'rollback removes every recorded binding' Assert-Equal 0 $script:Roles.Count 'rollback deletes the recorded custom role' Assert-True (-not (Test-Path -LiteralPath $env:ENTRO_GCP_IAM_STATE)) 'rollback removes the state file' Assert-True (-not [string]::IsNullOrEmpty($roleId)) 'custom role id was captured' } finally { if ($env:ENTRO_GCP_IAM_STATE -and (Test-Path -LiteralPath $env:ENTRO_GCP_IAM_STATE)) { Remove-Item -LiteralPath $env:ENTRO_GCP_IAM_STATE -Force } Remove-Item Env:ENTRO_GCP_IAM_STATE -ErrorAction SilentlyContinue Remove-Item Function:global:Invoke-GCloud -ErrorAction SilentlyContinue } $stateDir = Join-Path ([System.IO.Path]::GetTempPath()) ("entro-state-dir-{0}" -f [guid]::NewGuid().ToString('N')) $legacyRoot = if ($env:TEMP) { $env:TEMP } elseif ($env:TMPDIR) { $env:TMPDIR } else { '/tmp' } $legacyName = 'entro-gcp-iam-org-test-proj-test-sa-test.state' $legacy = Join-Path $legacyRoot $legacyName try { Set-Content -LiteralPath $legacy -Value "custom-role`tentroLoggingRole_9" -Encoding utf8 $resolved = & (Get-Module ISC.EntroGcp) { param($Directory) Get-EntroGcpIamStatePath -OrganizationId 'org-test' -ProjectId 'proj-test' -ServiceAccountName 'sa-test' -Directory $Directory } $stateDir $dest = Join-Path $stateDir $legacyName Assert-Equal $dest $resolved 'state file lives in the integration folder' Assert-True (Test-Path -LiteralPath $dest) 'legacy temp state is moved into that folder' Assert-True (-not (Test-Path -LiteralPath $legacy)) 'legacy temp state is removed' Assert-True ((Get-Content -LiteralPath $dest -Raw) -match 'entroLoggingRole_9') 'moved state keeps its contents' } finally { if (Test-Path -LiteralPath $legacy) { Remove-Item -LiteralPath $legacy -Force } if (Test-Path -LiteralPath $stateDir) { Remove-Item -LiteralPath $stateDir -Recurse -Force } } Write-Host "PASS ($script:AssertionCount assertions)" |