diff --git a/PSSwagger/GeneratedHelpers.ps1 b/PSSwagger/GeneratedHelpers.ps1 index 30552cb..27cc989 100644 --- a/PSSwagger/GeneratedHelpers.ps1 +++ b/PSSwagger/GeneratedHelpers.ps1 @@ -50,13 +50,16 @@ function Get-AzSubscriptionId param() $AzureContext = & "Get-AzureRmContext" -ErrorAction Stop - if(Get-Member -InputObject $AzureContext.Subscription -Name SubscriptionId) + if($AzureContext) { - return $AzureContext.Subscription.SubscriptionId - } - else - { - return $AzureContext.Subscription.Id + if(Get-Member -InputObject $AzureContext.Subscription -Name SubscriptionId) + { + return $AzureContext.Subscription.SubscriptionId + } + else + { + return $AzureContext.Subscription.Id + } } } diff --git a/PSSwagger/New-ServiceClient.ps1 b/PSSwagger/New-ServiceClient.ps1 new file mode 100644 index 0000000..1b84c90 --- /dev/null +++ b/PSSwagger/New-ServiceClient.ps1 @@ -0,0 +1,110 @@ +Microsoft.PowerShell.Core\Set-StrictMode -Version Latest + +<# +.DESCRIPTION + Creates Service Client object. + +.PARAMETER FullClientTypeName + Client type full name. + +.PARAMETER AddHttpClientHandler + Switch to determine whether the client type constructor expects the HttpClientHandler object. + +.PARAMETER Credential + Credential is required for for creating the HttpClientHandler object. + +.PARAMETER AuthenticationCommand + Command that should return a Microsoft.Rest.ServiceClientCredentials object that implements custom authentication logic. + +.PARAMETER AuthenticationCommandArgumentList + Arguments to the AuthenticationCommand, if any. + +.PARAMETER HostOverrideCommand + Command should return a custom hostname string. + Overrides the default host in the specification. + +.PARAMETER SubscriptionIdCommand + Custom command get SubscriptionId value. + +.PARAMETER GlobalParameterHashtable + Global parameters to be set on client object. +#> +function New-ServiceClient { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $FullClientTypeName, + + [Parameter(Mandatory = $false)] + [switch] + $AddHttpClientHandler, + + [Parameter(Mandatory = $false)] + [pscredential] + $Credential, + + [Parameter(Mandatory = $true)] + [string] + $AuthenticationCommand, + + [Parameter(Mandatory = $false)] + [Object[]] + $AuthenticationCommandArgumentList, + + [Parameter(Mandatory = $false)] + [string] + $HostOverrideCommand, + + [Parameter(Mandatory = $false)] + [string] + $SubscriptionIdCommand, + + [Parameter(Mandatory = $false)] + [PSCustomObject] + $GlobalParameterHashtable + ) + + $ClientArgumentList = @() + $InvokeCommand_parameters = @{ + ScriptBlock = [scriptblock]::Create($AuthenticationCommand) + } + if ($AuthenticationCommandArgumentList) { + $InvokeCommand_parameters['ArgumentList'] = $AuthenticationCommandArgumentList + } + $ClientArgumentList += Invoke-Command @InvokeCommand_parameters + + if ($AddHttpClientHandler) { + $httpClientHandler = New-HttpClientHandler -Credential $Credential + $ClientArgumentList += $httpClientHandler + } + + $delegatingHandler = New-Object -TypeName System.Net.Http.DelegatingHandler[] -ArgumentList 0 + $ClientArgumentList += $delegatingHandler + + $Client = New-Object -TypeName $FullClientTypeName -ArgumentList $ClientArgumentList + + if ($HostOverrideCommand) { + $Client.BaseUri = Invoke-Command -ScriptBlock [scriptblock]::Create($HostOverrideCommand) + } + + if ($GlobalParameterHashtable) { + $GlobalParameterHashtable.GetEnumerator() | ForEach-Object { + if (Get-Member -InputObject $Client -Name $_.Key -MemberType Property) { + if ((-not $_.Value) -and ($_.Key -eq 'SubscriptionId')) { + if($SubscriptionIdCommand) { + $Client.SubscriptionId = Invoke-Command -ScriptBlock [scriptblock]::Create($SubscriptionIdCommand) + } + else { + $Client.SubscriptionId = Get-AzSubscriptionId + } + } + else { + $Client."$($_.Key)" = $_.Value + } + } + } + } + + return $Client +} diff --git a/PSSwagger/PSSwagger.Constants.ps1 b/PSSwagger/PSSwagger.Constants.ps1 index 06b0d5c..1a4094e 100644 --- a/PSSwagger/PSSwagger.Constants.ps1 +++ b/PSSwagger/PSSwagger.Constants.ps1 @@ -53,6 +53,7 @@ $DynamicAssemblyGenerationCode `$allDllsPath = Join-Path -Path `$ClrPath -ChildPath '*.dll' Get-ChildItem -Path `$allDllsPath -File | ForEach-Object { Add-Type -Path `$_.FullName -ErrorAction SilentlyContinue } +. (Join-Path -Path `$PSScriptRoot -ChildPath 'New-ServiceClient.ps1') . (Join-Path -Path `$PSScriptRoot -ChildPath 'GeneratedHelpers.ps1') `$allPs1FilesPath = Join-Path -Path `$PSScriptRoot -ChildPath '$GeneratedCommandsName' | Join-Path -ChildPath '*.ps1' @@ -157,11 +158,49 @@ $constructFlattenedParameter = @' $functionBodyStr = @' `$ErrorActionPreference = 'Stop' - $securityBlock - $clientName = New-Object -TypeName $FullClientTypeName -ArgumentList $clientArgumentList$apiVersion - $overrideBaseUriBlock - $GlobalParameterBlock + `$NewServiceClient_params = @{ + FullClientTypeName = '$FullClientTypeName' + } +$( +if($AuthenticationCommand){ +" + `$NewServiceClient_params['AuthenticationCommand'] = @' + $AuthenticationCommand +`'@ " + if($AuthenticationCommandArgumentName){ +" + `$NewServiceClient_params['AuthenticationCommandArgumentList'] = `$$AuthenticationCommandArgumentName" + } +} +if($AddHttpClientHandler){ +" + `$NewServiceClient_params['AddHttpClientHandler'] = `$true + `$NewServiceClient_params['Credential'] = `$Credential" +} +if($hostOverrideCommand){ +" + `$NewServiceClient_params['HostOverrideCommand'] = @' + $hostOverrideCommand +`'@" +} +if($GlobalParameters) { +' + $GlobalParameterHashtable = @{} ' + + foreach($parameter in $GlobalParameters) { +" + `$GlobalParameterHashtable['$parameter'] = `$null + if(`$PSBoundParameters.ContainsKey('$parameter')) { + `$GlobalParameterHashtable['$parameter'] = `$PSBoundParameters['$parameter'] + } +" + } +" + `$NewServiceClient_params['GlobalParameterHashtable'] = `$GlobalParameterHashtable " +} +) + $clientName = New-ServiceClient @NewServiceClient_params $oDataExpressionBlock $parameterGroupsExpressionBlock $flattenedParametersBlock @@ -174,16 +213,6 @@ $functionBodyStr = @' } '@ -$clientArgumentListNoHandler = "`$serviceCredentials,`$delegatingHandler" -$clientArgumentListHttpClientHandler = "`$serviceCredentials,`$httpClientHandler,`$delegatingHandler" - -$securityBlockStr = @' -`$serviceCredentials = $authFunctionCall - $azSubscriptionIdBlock - $httpClientHandlerCall - `$delegatingHandler = New-Object -TypeName System.Net.Http.DelegatingHandler[] 0 -'@ - $parameterSetBasedMethodStrIfCase = @' if ('$operationId' -eq `$PsCmdlet.ParameterSetName) { $additionalConditionStart$methodBlock$additionalConditionEnd @@ -428,23 +457,6 @@ $createObjectStr = @' return `$Object '@ -$ApiVersionStr = @' - - if(Get-Member -InputObject $clientName -Name 'ApiVersion' -MemberType Property) - { - $clientName.ApiVersion = "$infoVersion" - } -'@ - -$GlobalParameterBlockStr = @' - if(Get-Member -InputObject `$clientName -Name '$globalParameterName' -MemberType Property) - { - `$clientName.$globalParameterName = $globalParameterValue - } -'@ - -$HostOverrideBlock = '`$ResourceManagerUrl = $hostOverrideCommand`n $clientName.BaseUri = `$ResourceManagerUrl' - $GeneratedCommandsName = 'Generated.PowerShell.Commands' $FormatViewDefinitionStr = @' diff --git a/PSSwagger/PSSwagger.psm1 b/PSSwagger/PSSwagger.psm1 index 0178318..81bdc62 100644 --- a/PSSwagger/PSSwagger.psm1 +++ b/PSSwagger/PSSwagger.psm1 @@ -612,9 +612,10 @@ function New-PSSwaggerModule -PSHeaderComment $PSHeaderComment $CopyFilesMap = [ordered]@{ - 'GeneratedHelpers.ps1' = 'GeneratedHelpers.ps1' + 'GeneratedHelpers.ps1' = 'GeneratedHelpers.ps1' 'Test-CoreRequirements.ps1' = 'Test-CoreRequirements.ps1' 'Test-FullRequirements.ps1' = 'Test-FullRequirements.ps1' + 'New-ServiceClient.ps1' = 'New-ServiceClient.ps1' } if (-not $AssemblyFileName) { diff --git a/PSSwagger/Paths.psm1 b/PSSwagger/Paths.psm1 index d0d52cf..4806794 100644 --- a/PSSwagger/Paths.psm1 +++ b/PSSwagger/Paths.psm1 @@ -425,7 +425,7 @@ function New-SwaggerPath $parametersToAdd = @{} $flattenedParametersOnPSCmdlet = @{} $parameterHitCount = @{} - $globalParameterBlock = '' + $globalParameters = @() $x_ms_pageableObject = $null foreach ($parameterSetDetail in $parameterSetDetails) { if ($parameterSetDetail.ContainsKey('x-ms-pageable') -and $parameterSetDetail.'x-ms-pageable' -and (-not $isNextPageOperation)) { @@ -505,7 +505,7 @@ function New-SwaggerPath $globalParameterValue = $parameterDetails.ConstantValue } - $globalParameterBlock += [Environment]::NewLine + $executionContext.InvokeCommand.ExpandString($GlobalParameterBlockStr) + $globalParameters += $globalParameterName } } @@ -609,24 +609,24 @@ function New-SwaggerPath } # Process security section - $azSubscriptionIdBlock = "" - $authFunctionCall = "" - $overrideBaseUriBlock = "" - $httpClientHandlerCall = "" + $SubscriptionIdCommand = "" + $AuthenticationCommand = "" + $AuthenticationCommandArgumentName = '' + $hostOverrideCommand = '' + $AddHttpClientHandler = $false $securityParametersToAdd = @() $PowerShellCodeGen = $SwaggerMetaDict['PowerShellCodeGen'] if (($PowerShellCodeGen['ServiceType'] -eq 'azure') -or ($PowerShellCodeGen['ServiceType'] -eq 'azure_stack')) { - $azSubscriptionIdBlock = "`$subscriptionId = Get-AzSubscriptionId" + $SubscriptionIdCommand = 'Get-AzSubscriptionId' } if ($PowerShellCodeGen['CustomAuthCommand']) { - $authFunctionCall = $PowerShellCodeGen['CustomAuthCommand'] + $AuthenticationCommand = $PowerShellCodeGen['CustomAuthCommand'] } if ($PowerShellCodeGen['HostOverrideCommand']) { $hostOverrideCommand = $PowerShellCodeGen['HostOverrideCommand'] - $overrideBaseUriBlock = $executionContext.InvokeCommand.ExpandString($HostOverrideBlock) } # If the auth function hasn't been set by metadata, try to discover it from the security and securityDefinition objects in the spec - if (-not $authFunctionCall) { + if (-not $AuthenticationCommand) { if ($FunctionDetails.ContainsKey('Security')) { # For now, just take the first security object if ($FunctionDetails.Security.Count -gt 1) { @@ -674,11 +674,12 @@ function New-SwaggerPath } # If the service is specified to not issue authentication challenges, we can't rely on HttpClientHandler if ($PowerShellCodeGen['NoAuthChallenge'] -and ($PowerShellCodeGen['NoAuthChallenge'] -eq $true)) { - $authFunctionCall = 'Get-AutoRestCredential -Credential $Credential' + $AuthenticationCommand = 'param([pscredential]$Credential) Get-AutoRestCredential -Credential $Credential' + $AuthenticationCommandArgumentName = 'Credential' } else { # Use an empty service client credentials object because we're using HttpClientHandler instead - $authFunctionCall = 'Get-AutoRestCredential' - $httpClientHandlerCall = '$httpClientHandler = New-HttpClientHandler -Credential $Credential' + $AuthenticationCommand = 'Get-AutoRestCredential' + $AddHttpClientHandler = $true } } elseif ($type -eq 'apiKey') { if (-not (Get-Member -InputObject $securityDefinition -Name 'name')) { @@ -712,7 +713,8 @@ function New-SwaggerPath Parameter = $credentialParameter IsConflictingWithOperationParameter = $false } - $authFunctionCall = "Get-AutoRestCredential -APIKey `$APIKey -Location '$in' -Name '$name'" + $AuthenticationCommand = "param([string]`$APIKey) Get-AutoRestCredential -APIKey `$APIKey -Location '$in' -Name '$name'" + $AuthenticationCommandArgumentName = 'APIKey' } else { Write-Warning -Message ($LocalizedData.UnsupportedAuthenticationType -f ($type)) } @@ -720,14 +722,9 @@ function New-SwaggerPath } } - if (-not $authFunctionCall) { + if (-not $AuthenticationCommand) { # At this point, there was no supported security object or overridden auth function, so assume no auth - $authFunctionCall = 'Get-AutoRestCredential' - } - - $clientArgumentList = $clientArgumentListNoHandler - if ($httpClientHandlerCall) { - $clientArgumentList = $clientArgumentListHttpClientHandler + $AuthenticationCommand = 'Get-AutoRestCredential' } $nonUniqueParameterSets = @() @@ -961,18 +958,22 @@ function New-SwaggerPath } $functionBodyParams = @{ - ParameterSetDetails = $parameterSetDetails - ODataExpressionBlock = $oDataExpressionBlock - ParameterGroupsExpressionBlock = $parameterGroupsExpressionBlock - GlobalParameterBlock = $GlobalParameterBlock - SwaggerDict = $SwaggerDict - SwaggerMetaDict = $SwaggerMetaDict - SecurityBlock = $executionContext.InvokeCommand.ExpandString($securityBlockStr) - OverrideBaseUriBlock = $overrideBaseUriBlock - ClientArgumentList = $clientArgumentList - FlattenedParametersOnPSCmdlet = $flattenedParametersOnPSCmdlet - } - + ParameterSetDetails = $parameterSetDetails + ODataExpressionBlock = $oDataExpressionBlock + ParameterGroupsExpressionBlock = $parameterGroupsExpressionBlock + SwaggerDict = $SwaggerDict + SwaggerMetaDict = $SwaggerMetaDict + AddHttpClientHandler = $AddHttpClientHandler + HostOverrideCommand = $hostOverrideCommand + AuthenticationCommand = $AuthenticationCommand + AuthenticationCommandArgumentName = $AuthenticationCommandArgumentName + SubscriptionIdCommand = $SubscriptionIdCommand + FlattenedParametersOnPSCmdlet = $flattenedParametersOnPSCmdlet + } + if($globalParameters) { + $functionBodyParams['GlobalParameters'] = $globalParameters + } + $pathGenerationPhaseResult = Get-PathFunctionBody @functionBodyParams $bodyObject = $pathGenerationPhaseResult.BodyObject diff --git a/PSSwagger/SwaggerUtils.psm1 b/PSSwagger/SwaggerUtils.psm1 index dc4396d..1b01a69 100644 --- a/PSSwagger/SwaggerUtils.psm1 +++ b/PSSwagger/SwaggerUtils.psm1 @@ -1373,10 +1373,9 @@ function Get-PathFunctionBody [AllowEmptyString()] $ParameterGroupsExpressionBlock, - [Parameter(Mandatory=$true)] - [string] - [AllowEmptyString()] - $GlobalParameterBlock, + [Parameter(Mandatory=$false)] + [string[]] + $GlobalParameters, [Parameter(Mandatory=$true)] [PSCustomObject] @@ -1386,22 +1385,29 @@ function Get-PathFunctionBody [PSCustomObject] $SwaggerMetaDict, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] + [switch] + $AddHttpClientHandler, + + [Parameter(Mandatory=$false)] [string] - $SecurityBlock, + $HostOverrideCommand, [Parameter(Mandatory=$true)] [string] - [AllowEmptyString()] - $OverrideBaseUriBlock, - - [Parameter(Mandatory=$true)] - [PSCustomObject] - $FlattenedParametersOnPSCmdlet, + $AuthenticationCommand, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory=$false)] [string] - $ClientArgumentList + $AuthenticationCommandArgumentName, + + [Parameter(Mandatory=$false)] + [string] + $SubscriptionIdCommand, + + [Parameter(Mandatory=$true)] + [PSCustomObject] + $FlattenedParametersOnPSCmdlet ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState @@ -1414,16 +1420,12 @@ function Get-PathFunctionBody $clientName = '$' + $Info['ClientTypeName'] $NameSpace = $info.namespace $FullClientTypeName = $Namespace + '.' + $Info['ClientTypeName'] - $apiVersion = $null $SubscriptionId = $null $BaseUri = $null $GetServiceCredentialStr = '' $AdvancedFunctionEndCodeBlock = '' $GetServiceCredentialStr = 'Get-AzServiceCredential' - # Expanding again expands $clientName - $GlobalParameterBlock = $executionContext.InvokeCommand.ExpandString($GlobalParameterBlock) - $parameterSetBasedMethodStr = '' foreach ($parameterSetDetail in $ParameterSetDetails) { # Responses isn't actually used right now, but keeping this when we need to handle responses per parameter set diff --git a/README.md b/README.md index 85d8412..ca889a0 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ A PowerShell module with commands to generate the PowerShell commands for a give ## Customizing PowerShell Metadata - [PowerShell Extensions](/docs/extensions/readme.md) +## Testing generated module +- [Mocking New-ServiceClient utility](/docs/testing/NewServiceClient.md) + ## Supported Platforms | Usage | Platforms | | ----------------| ------------------------------------- | diff --git a/docs/testing/NewServiceClient.md b/docs/testing/NewServiceClient.md new file mode 100644 index 0000000..4a37cb9 --- /dev/null +++ b/docs/testing/NewServiceClient.md @@ -0,0 +1,6 @@ +# Mocking New-ServiceClient utility function +- Implement your own New-ServiceClient function with mock/customized functionality in MockedNewServiceClient.ps1 +- Either rename or backup the New-ServiceClient.ps1 file under generated module base folder. +- Copy MockedNewServiceClient.ps1 to module base folder as New-ServiceClient.ps1 file. +- Invoke your tests to validate the cmdlets from generated module. +- Restore the original New-ServiceClient.ps1 under module base folder.