This migration updates the ScopesJson for the ApiClient with Id 91000000-0000-0000-0000-000000000001 to include additional authorization scopes for improved API access control. The Down method reverts the changes if necessary.
423 lines
20 KiB
PowerShell
423 lines
20 KiB
PowerShell
param(
|
|
[string]$ApiBaseUrl = 'http://localhost:5286/api',
|
|
|
|
[string]$ClientId = 'ssp-demo-worker',
|
|
|
|
[string]$ClientSecret = 'DemoOnly-DoNotUseInProduction!',
|
|
|
|
[int]$ApiTimeoutSec = 30
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Invoke-Api {
|
|
param(
|
|
[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
|
|
[string]$Method,
|
|
|
|
[string]$Path,
|
|
|
|
[object]$Body,
|
|
|
|
[string]$BearerToken,
|
|
|
|
[switch]$AllowNotFound
|
|
)
|
|
|
|
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
|
|
$parameters = @{
|
|
Method = $Method
|
|
Uri = $uri
|
|
TimeoutSec = $ApiTimeoutSec
|
|
ErrorAction = 'Stop'
|
|
}
|
|
|
|
if ($Body) {
|
|
$parameters.Body = ($Body | ConvertTo-Json -Depth 20)
|
|
$parameters.ContentType = 'application/json'
|
|
}
|
|
|
|
if ($BearerToken) {
|
|
$parameters.Headers = @{
|
|
Authorization = "Bearer $BearerToken"
|
|
}
|
|
}
|
|
|
|
try {
|
|
Invoke-RestMethod @parameters
|
|
}
|
|
catch {
|
|
$statusCode = $null
|
|
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
|
|
$statusCode = [int]$_.Exception.Response.StatusCode
|
|
}
|
|
|
|
if ($AllowNotFound -and $statusCode -eq 404) {
|
|
return $null
|
|
}
|
|
|
|
$responseBody = '<empty response body>'
|
|
if ($_.Exception.Response) {
|
|
try {
|
|
$stream = $_.Exception.Response.GetResponseStream()
|
|
if ($stream) {
|
|
$reader = [System.IO.StreamReader]::new($stream)
|
|
$text = $reader.ReadToEnd()
|
|
if (-not [string]::IsNullOrWhiteSpace($text)) {
|
|
$responseBody = $text
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
$responseBody = '<could not read response body>'
|
|
}
|
|
}
|
|
|
|
throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Get-AuthToken {
|
|
param([string]$Scope)
|
|
|
|
$token = Invoke-Api -Method POST -Path 'auth/token' -Body @{
|
|
clientId = $ClientId
|
|
clientSecret = $ClientSecret
|
|
scope = $Scope
|
|
}
|
|
|
|
return $token.accessToken
|
|
}
|
|
|
|
function Find-ByName {
|
|
param(
|
|
[object[]]$Items,
|
|
[string]$Name
|
|
)
|
|
|
|
return @($Items | Where-Object { $_.name -eq $Name })[0]
|
|
}
|
|
|
|
function ConvertTo-GuidValue {
|
|
param([object]$Value)
|
|
|
|
return [guid]@($Value)[0]
|
|
}
|
|
|
|
function ConvertTo-ApiItems {
|
|
param([object]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return @()
|
|
}
|
|
|
|
$items = @($Value)
|
|
if ($items.Count -eq 1 -and $items[0] -is [array]) {
|
|
return @($items[0])
|
|
}
|
|
|
|
if ($items.Count -eq 1 -and $items[0].PSObject.Properties['value']) {
|
|
return @($items[0].value)
|
|
}
|
|
|
|
return $items
|
|
}
|
|
|
|
function Write-TestSection {
|
|
param([string]$Name)
|
|
|
|
Write-Host ""
|
|
Write-Host ("=== {0} ===" -f $Name) -ForegroundColor Cyan
|
|
}
|
|
|
|
function Write-TestInfo {
|
|
param(
|
|
[string]$Label,
|
|
[object]$Value
|
|
)
|
|
|
|
Write-Host (" - {0}: {1}" -f $Label, $Value)
|
|
}
|
|
|
|
Describe 'API controller workflow smoke test' {
|
|
$script:token = $null
|
|
$script:environmentId = $null
|
|
$script:domainIds = @()
|
|
$script:targetIds = @()
|
|
$script:deploymentBatchId = $null
|
|
$script:templateSelectionIds = @()
|
|
$script:targetAssignmentIds = @()
|
|
$script:configurationValueId = $null
|
|
$script:credentialSecretId = $null
|
|
$script:testRunId = ([guid]::NewGuid().ToString('N')).Substring(0, 8)
|
|
$script:environmentName = "Pester Environment $script:testRunId"
|
|
$script:domainName1 = "Pester Domain A $script:testRunId"
|
|
$script:domainName2 = "Pester Domain B $script:testRunId"
|
|
$script:targetName1 = "PST-$script:testRunId-01"
|
|
$script:targetName2 = "PST-$script:testRunId-02"
|
|
$script:targetName3 = "PST-$script:testRunId-03"
|
|
$script:credentialSecretName = "Pester/Workflow/$script:testRunId"
|
|
|
|
try {
|
|
It 'creates an environment, links two domains, creates targets and composes a deployment batch' {
|
|
Write-TestSection -Name 'test run'
|
|
Write-TestInfo -Label 'API base URL' -Value $ApiBaseUrl
|
|
Write-TestInfo -Label 'Run id' -Value $script:testRunId
|
|
|
|
Write-TestSection -Name 'auth and inventory'
|
|
$script:token = Get-AuthToken -Scope 'configuration.read configuration.write credential.read credential.write credential.resolve deployment.read deployment.write queue.read template.read token.manage token.admin'
|
|
Write-TestInfo -Label 'Bearer token' -Value 'issued'
|
|
|
|
$allowedScopesResponse = Invoke-Api -Method GET -Path 'tokens/scopes' -BearerToken $script:token
|
|
$allowedScopes = @($allowedScopesResponse)
|
|
if ($allowedScopesResponse.PSObject.Properties['value']) {
|
|
$allowedScopes = @($allowedScopesResponse.value)
|
|
}
|
|
|
|
$allowedScopes -contains 'deployment.write' | Should Be $true
|
|
Write-TestInfo -Label 'Allowed scopes' -Value @($allowedScopes).Count
|
|
|
|
$myTokens = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'tokens/my' -BearerToken $script:token))
|
|
$myTokens.Count | Should BeGreaterThan 0
|
|
Write-TestInfo -Label 'Managed tokens visible' -Value $myTokens.Count
|
|
|
|
$services = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Service' -BearerToken $script:token))
|
|
$services.Count | Should BeGreaterThan 0
|
|
Write-TestInfo -Label 'Services' -Value $services.Count
|
|
$service = $services | Select-Object -First 1
|
|
Invoke-Api -Method GET -Path ("Service/{0}" -f $service.id) -BearerToken $script:token | Should Not BeNullOrEmpty
|
|
Invoke-Api -Method GET -Path ("Service/{0}/RoleDefinitions" -f $service.id) -BearerToken $script:token | Out-Null
|
|
|
|
$templateCategories = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'TemplateCategory' -BearerToken $script:token))
|
|
$templateCategories.Count | Should BeGreaterThan 0
|
|
Write-TestInfo -Label 'Template categories' -Value $templateCategories.Count
|
|
Invoke-Api -Method GET -Path ("TemplateCategory/{0}" -f $templateCategories[0].id) -BearerToken $script:token | Should Not BeNullOrEmpty
|
|
|
|
$deploymentRules = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'deployment-rules' -BearerToken $script:token))
|
|
$deploymentRules.Count | Should BeGreaterThan 0
|
|
Write-TestInfo -Label 'Deployment rules' -Value $deploymentRules.Count
|
|
Invoke-Api -Method GET -Path ("deployment-rules/{0}" -f $deploymentRules[0].id) -BearerToken $script:token | Should Not BeNullOrEmpty
|
|
|
|
$configurationDefinitions = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'configuration-definitions' -BearerToken $script:token))
|
|
$configurationDefinitions.Count | Should BeGreaterThan 0
|
|
Write-TestInfo -Label 'Configuration definitions' -Value $configurationDefinitions.Count
|
|
$configurationDefinition = $configurationDefinitions | Select-Object -First 1
|
|
Invoke-Api -Method GET -Path ("configuration-definitions/{0}" -f $configurationDefinition.id) -BearerToken $script:token | Should Not BeNullOrEmpty
|
|
Invoke-Api -Method GET -Path ("configuration-definitions/{0}/values" -f $configurationDefinition.id) -BearerToken $script:token | Out-Null
|
|
|
|
Write-TestSection -Name 'credential secret'
|
|
$script:credentialSecretId = ConvertTo-GuidValue -Value (Invoke-Api -Method POST -Path 'credential-secrets' -BearerToken $script:token -Body @{
|
|
name = $script:credentialSecretName
|
|
userName = "CONTOSO\svc-pester-$script:testRunId"
|
|
secretValue = 'PesterSecret!'
|
|
secretType = 'Credential'
|
|
metadataJson = (@{ source = 'Api.Workflow.Tests.ps1'; runId = $script:testRunId } | ConvertTo-Json -Compress)
|
|
isEnabled = $true
|
|
})
|
|
Write-TestInfo -Label 'Created credential secret' -Value $script:credentialSecretId
|
|
Invoke-Api -Method GET -Path ("credential-secrets/{0}" -f $script:credentialSecretId) -BearerToken $script:token | Should Not BeNullOrEmpty
|
|
$resolvedSecret = Invoke-Api -Method GET -Path ("credential-secrets/resolve?name={0}" -f [uri]::EscapeDataString($script:credentialSecretName)) -BearerToken $script:token
|
|
$resolvedSecret.userName | Should Be "CONTOSO\svc-pester-$script:testRunId"
|
|
Write-TestInfo -Label 'Resolved credential user' -Value $resolvedSecret.userName
|
|
|
|
Write-TestSection -Name 'environment and domains'
|
|
Invoke-Api -Method POST -Path 'Environment' -BearerToken $script:token -Body @{
|
|
name = $script:environmentName
|
|
environmentType = 'Test'
|
|
hostingType = 'OnPrem'
|
|
providerType = 'Pester'
|
|
metadataJson = (@{ source = 'Api.Workflow.Tests.ps1'; runId = $script:testRunId } | ConvertTo-Json -Compress)
|
|
} | Out-Null
|
|
|
|
$environment = Find-ByName -Items @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Environment' -BearerToken $script:token)) -Name $script:environmentName
|
|
$environment | Should Not BeNullOrEmpty
|
|
$script:environmentId = ConvertTo-GuidValue -Value $environment.id
|
|
Write-TestInfo -Label 'Created environment' -Value ("{0} ({1})" -f $script:environmentName, $script:environmentId)
|
|
|
|
$script:configurationValueId = ConvertTo-GuidValue -Value (Invoke-Api -Method POST -Path 'configuration-values' -BearerToken $script:token -Body @{
|
|
configurationDefinitionId = [string](ConvertTo-GuidValue -Value $configurationDefinition.id)
|
|
scopeType = 'Environment'
|
|
scopeId = [string]$script:environmentId
|
|
valueSourceType = 'Static'
|
|
valueJson = (@{ value = "Pester-$script:testRunId" } | ConvertTo-Json -Compress)
|
|
sortOrder = 999
|
|
})
|
|
@(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path ("configuration-values?scopeType=Environment&scopeId={0}" -f $script:environmentId) -BearerToken $script:token)).Count | Should BeGreaterThan 0
|
|
Write-TestInfo -Label 'Created configuration value' -Value $script:configurationValueId
|
|
|
|
$domainId1 = Invoke-Api -Method POST -Path 'Domain' -BearerToken $script:token -Body @{
|
|
name = $script:domainName1
|
|
fqdn = "pester-a-$script:testRunId.contoso.local"
|
|
netBIOS = "PSTA$($script:testRunId.Substring(0, 4))".ToUpper()
|
|
}
|
|
|
|
$domainId2 = Invoke-Api -Method POST -Path 'Domain' -BearerToken $script:token -Body @{
|
|
name = $script:domainName2
|
|
fqdn = "pester-b-$script:testRunId.contoso.local"
|
|
netBIOS = "PSTB$($script:testRunId.Substring(0, 4))".ToUpper()
|
|
}
|
|
|
|
$script:domainIds = @(
|
|
(ConvertTo-GuidValue -Value $domainId1),
|
|
(ConvertTo-GuidValue -Value $domainId2)
|
|
)
|
|
Write-TestInfo -Label 'Created domains' -Value ($script:domainIds -join ', ')
|
|
|
|
foreach ($domainId in $script:domainIds) {
|
|
Invoke-Api -Method POST -Path ("Domain/{0}/Environment/{1}" -f $domainId, $script:environmentId) -BearerToken $script:token | Out-Null
|
|
}
|
|
Write-TestInfo -Label 'Linked domains to environment' -Value $script:domainIds.Count
|
|
|
|
Write-TestSection -Name 'targets'
|
|
$targetDefinitions = @(
|
|
@{ name = $script:targetName1; domainId = $script:domainIds[0]; role = 'WebFrontEnd'; order = 10 },
|
|
@{ name = $script:targetName2; domainId = $script:domainIds[0]; role = 'Application'; order = 20 },
|
|
@{ name = $script:targetName3; domainId = $script:domainIds[1]; role = 'Search'; order = 30 }
|
|
)
|
|
|
|
foreach ($targetDefinition in $targetDefinitions) {
|
|
$targetId = Invoke-Api -Method POST -Path 'Target' -BearerToken $script:token -Body @{
|
|
domainID = $targetDefinition.domainId
|
|
name = $targetDefinition.name
|
|
targetType = 'VirtualMachine'
|
|
providerType = 'OnPrem'
|
|
externalId = $targetDefinition.name
|
|
metadataJson = (@{ source = 'Api.Workflow.Tests.ps1'; role = $targetDefinition.role } | ConvertTo-Json -Compress)
|
|
}
|
|
|
|
$script:targetIds += ConvertTo-GuidValue -Value $targetId
|
|
Write-TestInfo -Label 'Created target' -Value ("{0} / {1} ({2})" -f $targetDefinition.name, $targetDefinition.role, $targetId)
|
|
}
|
|
|
|
@($script:targetIds).Count | Should Be 3
|
|
|
|
Write-TestSection -Name 'template selection'
|
|
$templates = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Template' -BearerToken $script:token))
|
|
$onPremServiceIds = @(
|
|
$services |
|
|
Where-Object { -not $_.isCloudService } |
|
|
ForEach-Object { [string]$_.id }
|
|
)
|
|
$onPremCategoryIds = @(
|
|
$templateCategories |
|
|
Where-Object { $onPremServiceIds -contains [string]$_.serviceId } |
|
|
ForEach-Object { [string]$_.id }
|
|
)
|
|
$selectedTemplates = @(
|
|
$templates |
|
|
Where-Object { $onPremCategoryIds -contains [string]$_.templateCategoryId } |
|
|
Select-Object -First 2
|
|
)
|
|
@($selectedTemplates).Count | Should BeGreaterThan 0
|
|
Write-TestInfo -Label 'Available templates' -Value $templates.Count
|
|
Write-TestInfo -Label 'Selected templates' -Value (($selectedTemplates | ForEach-Object { $_.name }) -join ', ')
|
|
|
|
$templateSelections = @()
|
|
$sortOrder = 10
|
|
foreach ($template in $selectedTemplates) {
|
|
$versions = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path ("Template/{0}/Versions" -f $template.id) -BearerToken $script:token))
|
|
$version = $versions | Select-Object -First 1
|
|
$version | Should Not BeNullOrEmpty
|
|
|
|
$templateSelections += @{
|
|
templateVersionId = [string](ConvertTo-GuidValue -Value $version.id)
|
|
templateRole = 'Service'
|
|
sortOrder = $sortOrder
|
|
alias = "Pester-$sortOrder"
|
|
}
|
|
|
|
Write-TestInfo -Label 'Pinned template version' -Value ("{0} -> {1}" -f $template.name, $version.id)
|
|
$sortOrder += 10
|
|
}
|
|
|
|
$targetAssignments = @()
|
|
foreach ($targetDefinition in $targetDefinitions) {
|
|
$targetId = $script:targetIds[$targetDefinitions.IndexOf($targetDefinition)]
|
|
$targetAssignments += @{
|
|
targetId = [string]$targetId
|
|
roleKey = $targetDefinition.role
|
|
sortOrder = $targetDefinition.order
|
|
nodeDataJson = (@{ nodeName = $targetDefinition.name; pester = $true } | ConvertTo-Json -Compress)
|
|
}
|
|
}
|
|
|
|
Write-TestSection -Name 'deployment batch'
|
|
$script:deploymentBatchId = ConvertTo-GuidValue -Value (Invoke-Api -Method POST -Path 'deployment-batches' -BearerToken $script:token -Body @{
|
|
status = 'Pending'
|
|
targetIds = @()
|
|
templateSelections = $templateSelections
|
|
targetAssignments = $targetAssignments
|
|
})
|
|
|
|
$script:deploymentBatchId | Should Not BeNullOrEmpty
|
|
Write-TestInfo -Label 'Created deployment batch' -Value $script:deploymentBatchId
|
|
|
|
$composition = Invoke-Api -Method GET -Path ("deployment-batches/{0}/composition" -f $script:deploymentBatchId) -BearerToken $script:token
|
|
@($composition.templateSelections).Count | Should Be @($templateSelections).Count
|
|
|
|
$composition = Invoke-Api -Method GET -Path ("deployment-batches/{0}/composition" -f $script:deploymentBatchId) -BearerToken $script:token
|
|
@($composition.targetAssignments).Count | Should Be 3
|
|
$script:targetAssignmentIds = @($composition.targetAssignments | ForEach-Object { ConvertTo-GuidValue -Value $_.id })
|
|
Write-TestInfo -Label 'Composition template selections' -Value @($composition.templateSelections).Count
|
|
Write-TestInfo -Label 'Composition target assignments' -Value @($composition.targetAssignments).Count
|
|
|
|
$deployments = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Deployment' -BearerToken $script:token) | Where-Object { [string]$_.deploymentGroupId -eq [string]$script:deploymentBatchId })
|
|
$deployments.Count | Should Be 3
|
|
Write-TestInfo -Label 'Deployment rows' -Value $deployments.Count
|
|
Invoke-Api -Method GET -Path ("Deployment/{0}" -f $deployments[0].id) -BearerToken $script:token | Should Not BeNullOrEmpty
|
|
|
|
Invoke-Api -Method GET -Path ("deployment-artifacts?deploymentGroupId={0}" -f $script:deploymentBatchId) -BearerToken $script:token | Out-Null
|
|
Invoke-Api -Method GET -Path 'Deployment/QueueJobs' -BearerToken $script:token | Out-Null
|
|
Write-TestInfo -Label 'Artifact and queue endpoints' -Value 'reachable'
|
|
}
|
|
}
|
|
finally {
|
|
if ($script:token) {
|
|
Write-TestSection -Name 'cleanup'
|
|
if ($script:credentialSecretId) {
|
|
Invoke-Api -Method DELETE -Path ("credential-secrets/{0}" -f $script:credentialSecretId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
Write-TestInfo -Label 'Deleted credential secret' -Value $script:credentialSecretId
|
|
}
|
|
|
|
if ($script:configurationValueId) {
|
|
Invoke-Api -Method DELETE -Path ("configuration-values/{0}" -f $script:configurationValueId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
Write-TestInfo -Label 'Deleted configuration value' -Value $script:configurationValueId
|
|
}
|
|
|
|
foreach ($assignmentId in @($script:targetAssignmentIds)) {
|
|
if ($script:deploymentBatchId) {
|
|
Invoke-Api -Method DELETE -Path ("deployment-batches/{0}/target-assignments/{1}" -f $script:deploymentBatchId, $assignmentId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
}
|
|
}
|
|
|
|
if ($script:deploymentBatchId) {
|
|
Invoke-Api -Method DELETE -Path ("deployment-batches/{0}" -f $script:deploymentBatchId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
Write-TestInfo -Label 'Deleted deployment batch' -Value $script:deploymentBatchId
|
|
}
|
|
|
|
foreach ($targetId in @($script:targetIds)) {
|
|
Invoke-Api -Method DELETE -Path ("Target/{0}" -f $targetId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
}
|
|
if (@($script:targetIds).Count -gt 0) {
|
|
Write-TestInfo -Label 'Deleted targets' -Value @($script:targetIds).Count
|
|
}
|
|
|
|
foreach ($domainId in @($script:domainIds)) {
|
|
if ($script:environmentId) {
|
|
Invoke-Api -Method DELETE -Path ("Domain/{0}/Environment/{1}" -f $domainId, $script:environmentId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
}
|
|
|
|
Invoke-Api -Method DELETE -Path ("Domain/{0}" -f $domainId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
}
|
|
if (@($script:domainIds).Count -gt 0) {
|
|
Write-TestInfo -Label 'Deleted domains' -Value @($script:domainIds).Count
|
|
}
|
|
|
|
if ($script:environmentId) {
|
|
Invoke-Api -Method DELETE -Path ("Environment/{0}" -f $script:environmentId) -BearerToken $script:token -AllowNotFound | Out-Null
|
|
Write-TestInfo -Label 'Deleted environment' -Value $script:environmentId
|
|
}
|
|
}
|
|
}
|
|
}
|