diff --git a/.azuredevops/pipelineTemplates/jobs.publishModule.yml b/.azuredevops/pipelineTemplates/jobs.publishModule.yml index df9df5bc86..5087e1244f 100644 --- a/.azuredevops/pipelineTemplates/jobs.publishModule.yml +++ b/.azuredevops/pipelineTemplates/jobs.publishModule.yml @@ -27,7 +27,8 @@ ## | vmImage | '$(vmImage)' | You can provide either a [poolname] or [vmImage] to run the job on | 'ubuntu20.04' | ## | defaultJobTimeoutInMinutes | 120 | The timeout for the job in this pipeline | 120 | ## | modulePath | '$(modulePath)' | The path to the module to deploy. E.g. [c:/KeyVault] | 'c:/KeyVault' | -## | publishLatest | '$(publishLatest)' | Flag to indicate whether or not to publish a "latest" version to Bicep Registry and Template Specs | true | +## | publishLatest | '$(publishLatest)' | Flag to indicate whether or not to publish a "latest" version to Bicep Registry and Template Specs | true | +## | useApiSpecsAlignedName | '$(useApiSpecsAlignedName)' | Flag to indicate whether or not to publish module using their REST API, or their folder path name | true | ## | templateSpecsRGName | '$(templateSpecsRGName)' | Required to publish to template spec. ResourceGroup of the template spec to publish to | 'mgmt-rg' | ## | templateSpecsRGLocation | '$(templateSpecsRGLocation)' | Required to publish to template spec. Location of the template spec resource group | 'West Europe' | ## | templateSpecsDescription | '$(templateSpecsDescription)' | Required to publish to template spec. Description of the template spec to publish to | 'IaCs module' | @@ -60,6 +61,7 @@ parameters: # Shared publishLatest: '$(publishLatest)' + useApiSpecsAlignedName: '$(useApiSpecsAlignedName)' ## TemplateSpec-related templateSpecsDoPublish: '$(templateSpecsDoPublish)' @@ -164,10 +166,11 @@ jobs: ############################# # Add all modules that don't exist in the target location $missingInputObject = @{ - TemplateFilePath = $TemplateFilePath - VstsOrganizationUri = '${{ parameters.vstsOrganizationUri }}' - VstsFeedProject = '${{ parameters.vstsFeedProject }}' - VstsFeedName = '${{ parameters.vstsFeedName }}' + TemplateFilePath = $TemplateFilePath + VstsOrganizationUri = '${{ parameters.vstsOrganizationUri }}' + VstsFeedProject = '${{ parameters.vstsFeedProject }}' + VstsFeedName = '${{ parameters.vstsFeedName }}' + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ parameters.useApiSpecsAlignedName }}') } Write-Verbose "Invoke Get-ModulesMissingFromUniversalArtifactsFeed with" -Verbose @@ -189,11 +192,12 @@ jobs: Write-Host "##[group]$(' - [{0}] [{1}]' -f $RelPath, $moduleToPublish.Version)" $functionInput = @{ - TemplateFilePath = $moduleToPublish.TemplateFilePath - VstsOrganizationUri = '${{ parameters.vstsOrganizationUri }}' - VstsFeedProject = '${{ parameters.vstsFeedProject }}' - VstsFeedName = '${{ parameters.vstsFeedName }}' - ModuleVersion = $moduleToPublish.Version + TemplateFilePath = $moduleToPublish.TemplateFilePath + VstsOrganizationUri = '${{ parameters.vstsOrganizationUri }}' + VstsFeedProject = '${{ parameters.vstsFeedProject }}' + VstsFeedName = '${{ parameters.vstsFeedName }}' + ModuleVersion = $moduleToPublish.Version + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ parameters.useApiSpecsAlignedName }}') } Write-Verbose "Invoke Publish-ModuleToUniversalArtifactsFeed with" -Verbose @@ -241,7 +245,7 @@ jobs: ################################ $functionInput = @{ TemplateFilePath = $TemplateFilePath - PublishLatest = [bool] '${{ parameters.publishLatest }}' + PublishLatest = [System.Convert]::ToBoolean('${{ parameters.publishLatest }}') } Write-Verbose "Invoke Get-ModulesToPublish with" -Verbose @@ -258,9 +262,10 @@ jobs: # Add all modules that don't exist in the target location $missingInputObject = @{ - TemplateFilePath = $TemplateFilePath - TemplateSpecsRGName = '${{ parameters.templateSpecsRgName }}' - PublishLatest = [bool] '${{ parameters.bicepRegistryRgName }}' + TemplateFilePath = $TemplateFilePath + TemplateSpecsRGName = '${{ parameters.templateSpecsRgName }}' + PublishLatest = [System.Convert]::ToBoolean('${{ parameters.publishLatest }}') + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ parameters.useApiSpecsAlignedName }}') } Write-Verbose "Invoke Get-ModulesMissingFromTemplateSpecsRG with" -Verbose @@ -287,6 +292,7 @@ jobs: TemplateSpecsRgLocation = '${{ parameters.templateSpecsRgLocation }}' TemplateSpecsDescription = '${{ parameters.templateSpecsDescription }}' ModuleVersion = $moduleToPublish.Version + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ parameters.useApiSpecsAlignedName }}') } Write-Verbose "Invoke Publish-ModuleToTemplateSpecsRG with" -Verbose @@ -336,7 +342,7 @@ jobs: ################################ $functionInput = @{ TemplateFilePath = $TemplateFilePath - PublishLatest = [bool] '${{ parameters.publishLatest }}' + PublishLatest = [System.Convert]::ToBoolean('${{ parameters.publishLatest }}') } Write-Verbose "Invoke Get-ModulesToPublish with" -Verbose @@ -352,10 +358,11 @@ jobs: ############################# # Add all modules that don't exist in the target location $missingInputObject = @{ - TemplateFilePath = $TemplateFilePath - BicepRegistryName = '${{ parameters.bicepRegistryName }}' - BicepRegistryRgName = '${{ parameters.bicepRegistryRgName }}' - PublishLatest = [bool] '${{ parameters.bicepRegistryRgName }}' + TemplateFilePath = $TemplateFilePath + BicepRegistryName = '${{ parameters.bicepRegistryName }}' + BicepRegistryRgName = '${{ parameters.bicepRegistryRgName }}' + PublishLatest = [System.Convert]::ToBoolean('${{ parameters.publishLatest }}') + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ parameters.useApiSpecsAlignedName }}') } Write-Verbose "Invoke Get-ModulesMissingFromPrivateBicepRegistry with" -Verbose @@ -382,6 +389,7 @@ jobs: BicepRegistryRgName = '${{ parameters.bicepRegistryRgName }}' BicepRegistryRgLocation = '${{ parameters.bicepRegistryRgLocation }}' ModuleVersion = $moduleToPublish.Version + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ parameters.useApiSpecsAlignedName }}') } Write-Verbose "Invoke Publish-ModuleToPrivateBicepRegistry with" -Verbose diff --git a/.github/actions/templates/publishModule/action.yml b/.github/actions/templates/publishModule/action.yml index 49d8241f57..53f1415b7e 100644 --- a/.github/actions/templates/publishModule/action.yml +++ b/.github/actions/templates/publishModule/action.yml @@ -12,21 +12,22 @@ ## ACTION PARAMETERS ## ##-------------------------------------------## ## -## |===========================================================================================================================================================================================================| -## | Parameter | Required | Default | Description | Example | -## |--------------------------|----------|---------|--------------------------------------------------------------------------------------------------|--------------------------------------------------------| -## | templateFilePath | true | '' | The path to the template file to publish | 'modules/api-management/service/main.bicep' | -## | subscriptionId | false | '' | The ID of the subscription to publish to | '11111111-1111-1111-1111-111111111111' | -## | templateSpecsRgName | false | '' | Required to publish to template spec. ResourceGroup of the template spec to publish to | 'artifacts-rg' | -## | templateSpecsRgLocation | false | '' | Required to publish to template spec. Location of the template spec resource group | 'WestEurope' | -## | templateSpecsDescription | false | '' | Required to publish to template spec. Description of the template spec to publish to | 'This is an API-Management service template' | -## | templateSpecsDoPublish | false | 'false' | Flag to indicate whether or not to publish to template specs | 'true' | -## | bicepRegistryName | false | '' | Required to publish to private bicep registry. Name of the container registry to publish to | 'myacr' | -## | bicepRegistryRgName | false | '' | Required to publish to private bicep registry. Name of the container registry resource group | 'artifacts-rg' | -## | bicepRegistryRgLocation | false | '' | Required to publish to private bicep registry. Location of the container registry resource group | 'WestEurope' | -## | bicepRegistryDoPublish | false | 'false' | Flag to indicate whether or not to publish to the private bicep registry | 'true' | -## | publishLatest | false | 'true' | Flag to indicate whether or not to publish a "latest" version | 'true' | -## |===========================================================================================================================================================================================================| +## |==============================================================================================================================================================================================================| +## | Parameter | Required | Default | Description | Example | +## |----------------------------|----------|---------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------| +## | templateFilePath | true | '' | The path to the template file to publish | 'modules/api-management/service/main.bicep' | +## | subscriptionId | false | '' | The ID of the subscription to publish to | '11111111-1111-1111-1111-111111111111' | +## | templateSpecsRgName | false | '' | Required to publish to template spec. ResourceGroup of the template spec to publish to | 'artifacts-rg' | +## | templateSpecsRgLocation | false | '' | Required to publish to template spec. Location of the template spec resource group | 'WestEurope' | +## | templateSpecsDescription | false | '' | Required to publish to template spec. Description of the template spec to publish to | 'This is an API-Management service template' | +## | templateSpecsDoPublish | false | 'false' | Flag to indicate whether or not to publish to template specs | 'true' | +## | bicepRegistryName | false | '' | Required to publish to private bicep registry. Name of the container registry to publish to | 'myacr' | +## | bicepRegistryRgName | false | '' | Required to publish to private bicep registry. Name of the container registry resource group | 'artifacts-rg' | +## | bicepRegistryRgLocation | false | '' | Required to publish to private bicep registry. Location of the container registry resource group | 'WestEurope' | +## | bicepRegistryDoPublish | false | 'false' | Flag to indicate whether or not to publish to the private bicep registry | 'true' | +## | publishLatest | false | 'true' | Flag to indicate whether or not to publish a "latest" version | 'true' | +## | useApiSpecsAlignedName | false | 'false' | Flag to indicate whether or not to publish module using their REST API, or their folder path name | 'true' | +## |==============================================================================================================================================================================================================| ## ##---------------------------------------------## name: 'Publishing' @@ -69,6 +70,10 @@ inputs: description: 'Flag to indicate whether or not to publish a "latest" version' default: 'true' required: false + useApiSpecsAlignedName: + description: 'Flag to indicate whether or not to publish module using their REST API, or their folder path name' + default: 'false' + required: false runs: using: 'composite' @@ -105,7 +110,7 @@ runs: ################################ $functionInput = @{ TemplateFilePath = Join-Path $env:GITHUB_WORKSPACE "${{ inputs.templateFilePath }}" - PublishLatest = [bool] "${{ inputs.publishLatest }}" + PublishLatest = [System.Convert]::ToBoolean("${{ inputs.publishLatest }}") } Write-Verbose "Invoke task with" -Verbose @@ -122,7 +127,7 @@ runs: $missingInputObject = @{ TemplateFilePath = Join-Path $env:GITHUB_WORKSPACE "${{ inputs.templateFilePath }}" TemplateSpecsRGName = '${{ inputs.templateSpecsRgName }}' - PublishLatest = [bool] "${{ inputs.publishLatest }}" + PublishLatest = [System.Convert]::ToBoolean("${{ inputs.publishLatest }}") } Write-Verbose "Invoke Get-ModulesMissingFromTemplateSpecsRG with" -Verbose @@ -149,6 +154,7 @@ runs: TemplateSpecsRgLocation = '${{ inputs.templateSpecsRgLocation }}' TemplateSpecsDescription = '${{ inputs.templateSpecsDescription }}' ModuleVersion = $moduleToPublish.Version + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ inputs.useApiSpecsAlignedName }}') } Write-Verbose "Invoke task with" -Verbose @@ -185,7 +191,7 @@ runs: ################################ $functionInput = @{ TemplateFilePath = Join-Path $env:GITHUB_WORKSPACE "${{ inputs.templateFilePath }}" - PublishLatest = [bool] "${{ inputs.publishLatest }}" + PublishLatest = [System.Convert]::ToBoolean("${{ inputs.publishLatest }}") } Write-Verbose "Invoke task with" -Verbose @@ -202,7 +208,7 @@ runs: TemplateFilePath = Join-Path $env:GITHUB_WORKSPACE "${{ inputs.templateFilePath }}" BicepRegistryName = '${{ inputs.bicepRegistryName }}' BicepRegistryRgName = '${{ inputs.bicepRegistryRgName }}' - PublishLatest = [bool] "${{ inputs.publishLatest }}" + PublishLatest = [System.Convert]::ToBoolean("${{ inputs.publishLatest }}") } Write-Verbose "Invoke Get-ModulesMissingFromPrivateBicepRegistry with" -Verbose @@ -229,6 +235,7 @@ runs: BicepRegistryRgName = '${{ inputs.bicepRegistryRgName }}' BicepRegistryRgLocation = '${{ inputs.bicepRegistryRgLocation }}' ModuleVersion = $moduleToPublish.Version + UseApiSpecsAlignedName = [System.Convert]::ToBoolean('${{ inputs.useApiSpecsAlignedName }}') } Write-Verbose "Invoke task with" -Verbose diff --git a/.github/workflows/template.module.yml b/.github/workflows/template.module.yml index 180935b1b8..d1dd1d4c4f 100644 --- a/.github/workflows/template.module.yml +++ b/.github/workflows/template.module.yml @@ -140,3 +140,4 @@ jobs: bicepRegistryRgLocation: '${{ env.bicepRegistryRgLocation }}' bicepRegistryDoPublish: '${{ env.bicepRegistryDoPublish }}' publishLatest: '${{ env.publishLatest }}' + useApiSpecsAlignedName: '${{ env.useApiSpecsAlignedName }}' diff --git a/settings.yml b/settings.yml index 04bdbab53d..c96dbe1dc2 100644 --- a/settings.yml +++ b/settings.yml @@ -54,6 +54,7 @@ variables: # --------------- # publishLatest: true # [Only for Template-Specs & Bicep Registry] Publish an absolute latest version. Note: This version may include breaking changes and is not recommended for production environments + useApiSpecsAlignedName: false # Publish a module not using its folder path, but the matching name in the REST API (i.e., the classic naming). For example: `bicep/modules/microsoft.keyvault.vaults.secrets` instead of `bicep/modules/key-vault.vault.secret` # Template-Spec settings # # ---------------------- # diff --git a/utilities/pipelines/resourcePublish/Get-ModulesMissingFromPrivateBicepRegistry.ps1 b/utilities/pipelines/resourcePublish/Get-ModulesMissingFromPrivateBicepRegistry.ps1 index 124da5f51e..c91319fa94 100644 --- a/utilities/pipelines/resourcePublish/Get-ModulesMissingFromPrivateBicepRegistry.ps1 +++ b/utilities/pipelines/resourcePublish/Get-ModulesMissingFromPrivateBicepRegistry.ps1 @@ -18,6 +18,11 @@ Mandatory. The name of Resource Group the Container Registry is located it. Optional. Publish an absolute latest version. Note: This version may include breaking changes and is not recommended for production environments +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the module name looked for is aligned with the Azure API naming. If not, it's one aligned with the module's folder path. See the following examples: +- True: microsoft.keyvault.vaults.secrets +- False: key-vault.vault.secret + .EXAMPLE Get-ModulesMissingFromPrivateBicepRegistry -TemplateFilePath 'C:\ResourceModules\modules\compute\virtual-machine\main.bicep' -BicepRegistryName 'adpsxxazacrx001' -BicepRegistryRgName 'artifacts-rg' @@ -57,7 +62,10 @@ function Get-ModulesMissingFromPrivateBicepRegistry { [string] $BicepRegistryRgName, [Parameter(Mandatory = $false)] - [bool] $PublishLatest = $true + [bool] $PublishLatest = $true, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) begin { @@ -89,7 +97,7 @@ function Get-ModulesMissingFromPrivateBicepRegistry { foreach ($templatePath in $availableModuleTemplatePaths) { # Get a valid Container Registry name - $moduleRegistryIdentifier = Get-PrivateRegistryRepositoryName -TemplateFilePath $templatePath + $moduleRegistryIdentifier = Get-PrivateRegistryRepositoryName -TemplateFilePath $templatePath -UseApiSpecsAlignedName $UseApiSpecsAlignedName $null = Get-AzContainerRegistryTag -RepositoryName $moduleRegistryIdentifier -RegistryName $BicepRegistryName -ErrorAction 'SilentlyContinue' -ErrorVariable 'result' diff --git a/utilities/pipelines/resourcePublish/Get-ModulesMissingFromTemplateSpecsRG.ps1 b/utilities/pipelines/resourcePublish/Get-ModulesMissingFromTemplateSpecsRG.ps1 index 6cdfc1f879..a71c7fe258 100644 --- a/utilities/pipelines/resourcePublish/Get-ModulesMissingFromTemplateSpecsRG.ps1 +++ b/utilities/pipelines/resourcePublish/Get-ModulesMissingFromTemplateSpecsRG.ps1 @@ -15,6 +15,11 @@ Mandatory. The Resource Group to search in Optional. Publish an absolute latest version. Note: This version may include breaking changes and is not recommended for production environments +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the module name looked for is aligned with the Azure API naming. If not, it's one aligned with the module's folder path. See the following examples: +- True: microsoft.keyvault.vaults.secrets +- False: key-vault.vault.secret + .EXAMPLE Get-ModulesMissingFromTemplateSpecsRG -TemplateFilePath 'C:\ResourceModules\modules\key-vault\vault\main.bicep' -TemplateSpecsRGName 'artifacts-rg' @@ -67,7 +72,10 @@ function Get-ModulesMissingFromTemplateSpecsRG { [string] $TemplateSpecsRGName, [Parameter(Mandatory = $false)] - [bool] $PublishLatest = $true + [bool] $PublishLatest = $true, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) begin { @@ -99,7 +107,7 @@ function Get-ModulesMissingFromTemplateSpecsRG { foreach ($templatePath in $availableModuleTemplatePaths) { # Get a valid Template Spec name - $templateSpecsIdentifier = Get-TemplateSpecsName -TemplateFilePath $templatePath + $templateSpecsIdentifier = Get-TemplateSpecsName -TemplateFilePath $templatePath -UseApiSpecsAlignedName $UseApiSpecsAlignedName $null = Get-AzTemplateSpec -ResourceGroupName $TemplateSpecsRGName -Name $templateSpecsIdentifier -ErrorAction 'SilentlyContinue' -ErrorVariable 'result' diff --git a/utilities/pipelines/resourcePublish/Get-ModulesMissingFromUniversalArtifactsFeed.ps1 b/utilities/pipelines/resourcePublish/Get-ModulesMissingFromUniversalArtifactsFeed.ps1 index df7b43e33d..1654e16d2c 100644 --- a/utilities/pipelines/resourcePublish/Get-ModulesMissingFromUniversalArtifactsFeed.ps1 +++ b/utilities/pipelines/resourcePublish/Get-ModulesMissingFromUniversalArtifactsFeed.ps1 @@ -20,6 +20,11 @@ Example: 'IaC'. Mandatory. Name to the feed to publish to. Example: 'Artifacts'. +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the module name looked for is aligned with the Azure API naming. If not, it's one aligned with the module's folder path. See the following examples: +- True: microsoft.keyvault.vaults.secrets +- False: key-vault.vault.secret + .PARAMETER BearerToken Optional. The bearer token to use to authenticate the request. If not provided it MUST be existing in your environment as `$env:TOKEN` @@ -56,6 +61,9 @@ function Get-ModulesMissingFromUniversalArtifactsFeed { [Parameter(Mandatory = $false)] [string] $VstsFeedProject = '', + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false, + [Parameter(Mandatory = $false)] [string] $BearerToken = $env:TOKEN ) @@ -104,7 +112,7 @@ function Get-ModulesMissingFromUniversalArtifactsFeed { foreach ($templatePath in $availableModuleTemplatePaths) { # Get a valid Universal Artifact name - $artifactsIdentifier = Get-UniversalArtifactsName -TemplateFilePath $templatePath + $artifactsIdentifier = Get-UniversalArtifactsName -TemplateFilePath $templatePath -UseApiSpecsAlignedName $UseApiSpecsAlignedName if ($publishedModules -notcontains $artifactsIdentifier) { $missingTemplatePaths += $templatePath diff --git a/utilities/pipelines/resourcePublish/Get-PrivateRegistryRepositoryName.ps1 b/utilities/pipelines/resourcePublish/Get-PrivateRegistryRepositoryName.ps1 index 7b93cb8ca0..1b4070c6a6 100644 --- a/utilities/pipelines/resourcePublish/Get-PrivateRegistryRepositoryName.ps1 +++ b/utilities/pipelines/resourcePublish/Get-PrivateRegistryRepositoryName.ps1 @@ -8,6 +8,11 @@ Convert the given template file path into a valid Container Registry repository .PARAMETER TemplateFilePath Mandatory. The template file path to convert +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the returned name will be aligned with the Azure API naming. If not, the one aligned with the module's folder path. See the following examples: +- True: bicep/modules/microsoft.keyvault.vaults.secrets +- False: bicep/modules/key-vault.vault.secret + .EXAMPLE Get-PrivateRegistryRepositoryName -TemplateFilePath 'C:\modules\key-vault\vault\main.bicep' @@ -17,11 +22,21 @@ function Get-PrivateRegistryRepositoryName { [CmdletBinding()] param ( - [Parameter(Mandatory)] - [string] $TemplateFilePath + [Parameter(Mandatory = $true)] + [string] $TemplateFilePath, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) $moduleIdentifier = (Split-Path $TemplateFilePath -Parent).Replace('\', '/').Split('/modules/')[1] + + if ($UseApiSpecsAlignedName) { + # Load helper script + . (Join-Path (Get-Item -Path $PSScriptRoot).Parent.Parent 'tools' 'helper' 'Get-SpecsAlignedResourceName.ps1') + $moduleIdentifier = Get-SpecsAlignedResourceName -ResourceIdentifier $moduleIdentifier + } + $moduleRegistryIdentifier = 'bicep/modules/{0}' -f $moduleIdentifier.Replace('\', '/').Replace('/', '.').ToLower() return $moduleRegistryIdentifier diff --git a/utilities/pipelines/resourcePublish/Get-TemplateSpecsName.ps1 b/utilities/pipelines/resourcePublish/Get-TemplateSpecsName.ps1 index e69d30cd3e..89777d6308 100644 --- a/utilities/pipelines/resourcePublish/Get-TemplateSpecsName.ps1 +++ b/utilities/pipelines/resourcePublish/Get-TemplateSpecsName.ps1 @@ -8,6 +8,11 @@ Convert the given template file path into a valid Template Specs repository name .PARAMETER TemplateFilePath Mandatory. The template file path to convert +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the returned name will be aligned with the Azure API naming. If not, the one aligned with the module's folder path. See the following examples: +- True: microsoft.keyvault.vaults.secrets +- False: key-vault.vault.secret + .EXAMPLE Get-TemplateSpecsName -TemplateFilePath 'C:\modules\key-vault\vault\main.bicep' @@ -18,10 +23,21 @@ function Get-TemplateSpecsName { [CmdletBinding()] param ( [Parameter(Mandatory)] - [string] $TemplateFilePath + [string] $TemplateFilePath, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) $moduleIdentifier = (Split-Path $TemplateFilePath -Parent).Replace('\', '/').Split('/modules/')[1] + + if ($UseApiSpecsAlignedName) { + # Load helper script + . (Join-Path (Get-Item -Path $PSScriptRoot).Parent.Parent 'tools' 'helper' 'Get-SpecsAlignedResourceName.ps1') + $moduleIdentifier = Get-SpecsAlignedResourceName -ResourceIdentifier $moduleIdentifier + $moduleIdentifier = $moduleIdentifier -replace 'microsoft', 'ms' + } + $templateSpecIdentifier = $moduleIdentifier.Replace('\', '/').Replace('/', '.').ToLower() # Shorten the name @@ -36,8 +52,19 @@ function Get-TemplateSpecsName { $stringToCheck = $nameElems[($index + 1)] # If a name is replicated in a path, it is usually plural in the parent, and singular in the child path. - if ($stringToCheck.StartsWith($stringToRemove)) { - $nameElems[($index + 1)] = $stringToCheck -replace "$stringToRemove-" + # For example: /virtualNetworks/ (plural) & /virtualNetworks/virtualNetworkPeerings/ (singular) + # In this case we want to remove the singular version from the subsequent string & format it accordingly + if ($stringToRemove.EndsWith('s') -and $stringToCheck.StartsWith($stringToRemove.Substring(0, $stringToRemove.length - 1))) { + $singularString = $stringToRemove.Substring(0, $stringToRemove.length - 1) # Would be 'virtualNetwork' from the example above + $rest = $stringToCheck.length - $singularString.Length # Would be 8 from the example above + $shortenedString = $stringToCheck.Substring($singularString.length, $rest) # Would be 'peerings' from the example above + $camelCaseString = [Regex]::Replace($shortenedString , '\b.', { $args[0].Value.Tolower() }) # Would be 'peerings' from the example above + $nameElems[($index + 1)] = $camelCaseString # Would overwrite 'virtualnetworkpeerings' with 'peerings' from the example above + } elseif ($stringToCheck.StartsWith($stringToRemove)) { + # If the subsequent string starts with the current string, we want to remove the current string from the subsequent string. + # So we take the index of the end of the current string, caculate the length until the end of the string and reduce. If a `-` was in between the 2 elements, we also want to trim it from the front. + # For example 'replication-protection-container' & 'replication-protection-container-mapping' should become 'mapping' + $nameElems[($index + 1)] = $stringToCheck.Substring($stringToRemove.length, $stringToCheck.length - $stringToRemove.length).TrimStart('-') } } } diff --git a/utilities/pipelines/resourcePublish/Get-UniversalArtifactsName.ps1 b/utilities/pipelines/resourcePublish/Get-UniversalArtifactsName.ps1 index 95dc5a6a31..9cc29a5091 100644 --- a/utilities/pipelines/resourcePublish/Get-UniversalArtifactsName.ps1 +++ b/utilities/pipelines/resourcePublish/Get-UniversalArtifactsName.ps1 @@ -9,6 +9,11 @@ Must be lowercase alphanumerics, dashes, dots or underscores, under 256 characte .PARAMETER TemplateFilePath Mandatory. The template file path to convert +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the returned name will be aligned with the Azure API naming. If not, the one aligned with the module's folder path. See the following examples: +- True: microsoft.keyvault.vaults.secrets +- False: key-vault.vault.secret + .EXAMPLE Get-UniversalArtifactsName -TemplateFilePath 'C:\modules\key-vault\vault\main.bicep' @@ -19,11 +24,21 @@ function Get-UniversalArtifactsName { [CmdletBinding()] param ( [Parameter(Mandatory)] - [string] $TemplateFilePath + [string] $TemplateFilePath, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) $ModuleFolderPath = Split-Path $TemplateFilePath -Parent $universalPackageModuleName = $ModuleFolderPath.Replace('\', '/').Split('/modules/')[1] + + if ($UseApiSpecsAlignedName) { + # Load helper script + . (Join-Path (Get-Item -Path $PSScriptRoot).Parent.Parent 'tools' 'helper' 'Get-SpecsAlignedResourceName.ps1') + $universalPackageModuleName = Get-SpecsAlignedResourceName -ResourceIdentifier $universalPackageModuleName + } + $universalPackageModuleName = $universalPackageModuleName.Replace('\', '.').Replace('/', '.').toLower() return $universalPackageModuleName diff --git a/utilities/pipelines/resourcePublish/Publish-ModuleToPrivateBicepRegistry.ps1 b/utilities/pipelines/resourcePublish/Publish-ModuleToPrivateBicepRegistry.ps1 index cc5b29b846..220c245767 100644 --- a/utilities/pipelines/resourcePublish/Publish-ModuleToPrivateBicepRegistry.ps1 +++ b/utilities/pipelines/resourcePublish/Publish-ModuleToPrivateBicepRegistry.ps1 @@ -25,6 +25,11 @@ Example: 'artifacts-rg' Optional. The location of the resourceGroup the private bicep registry is deployed to. Required if the resource group is not yet existing. Example: 'West Europe' +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the module will be published with a name that is aligned with the Azure API naming. If not, one aligned with the module's folder path. See the following examples: +- True: bicep/modules/microsoft.keyvault.vaults.secrets +- False: bicep/modules/key-vault.vault.secret + .EXAMPLE Publish-ModuleToPrivateBicepRegistry -TemplateFilePath 'C:\modules\key-vault\vault\main.bicep' -ModuleVersion '3.0.0-alpha' -BicepRegistryName 'adpsxxazacrx001' -BicepRegistryRgName 'artifacts-rg' @@ -47,7 +52,10 @@ function Publish-ModuleToPrivateBicepRegistry { [string] $BicepRegistryRgName, [Parameter(Mandatory = $false)] - [string] $BicepRegistryRgLocation + [string] $BicepRegistryRgLocation, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) begin { @@ -80,7 +88,7 @@ function Publish-ModuleToPrivateBicepRegistry { } # Get a valid Container Registry name - $moduleRegistryIdentifier = Get-PrivateRegistryRepositoryName -TemplateFilePath $TemplateFilePath + $moduleRegistryIdentifier = Get-PrivateRegistryRepositoryName -TemplateFilePath $TemplateFilePath -UseApiSpecsAlignedName $UseApiSpecsAlignedName ############################################# ## Publish to private bicep registry ## diff --git a/utilities/pipelines/resourcePublish/Publish-ModuleToTemplateSpecsRG.ps1 b/utilities/pipelines/resourcePublish/Publish-ModuleToTemplateSpecsRG.ps1 index f3a2c1191d..209033b84e 100644 --- a/utilities/pipelines/resourcePublish/Publish-ModuleToTemplateSpecsRG.ps1 +++ b/utilities/pipelines/resourcePublish/Publish-ModuleToTemplateSpecsRG.ps1 @@ -26,6 +26,11 @@ Example: 'West Europe' Mandatory. The description of the parent template spec. Example: 'iacs key vault' +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the module will be published with a name that is aligned with the Azure API naming. If not, one aligned with the module's folder path. See the following examples: +- True: microsoft.keyvault.vaults.secrets +- False: key-vault.vault.secret + .EXAMPLE Publish-ModuleToTemplateSpecsRG -TemplateFilePath 'C:\modules\key-vault\vault\main.bicep' -ModuleVersion '3.0.0-alpha' -TemplateSpecsRgName 'artifacts-rg' -TemplateSpecsRgLocation 'West Europe' -TemplateSpecsDescription 'iacs key vault' @@ -48,7 +53,10 @@ function Publish-ModuleToTemplateSpecsRG { [string] $TemplateSpecsRgLocation, [Parameter(Mandatory)] - [string] $TemplateSpecsDescription + [string] $TemplateSpecsDescription, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) begin { @@ -69,7 +77,7 @@ function Publish-ModuleToTemplateSpecsRG { } # Get a valid Template Specs name - $templateSpecIdentifier = Get-TemplateSpecsName -TemplateFilePath $TemplateFilePath + $templateSpecIdentifier = Get-TemplateSpecsName -TemplateFilePath $TemplateFilePath -UseApiSpecsAlignedName $UseApiSpecsAlignedName ################################ ## Create template spec ## diff --git a/utilities/pipelines/resourcePublish/Publish-ModuleToUniversalArtifactsFeed.ps1 b/utilities/pipelines/resourcePublish/Publish-ModuleToUniversalArtifactsFeed.ps1 index a2e79cf3b7..c2705690fe 100644 --- a/utilities/pipelines/resourcePublish/Publish-ModuleToUniversalArtifactsFeed.ps1 +++ b/utilities/pipelines/resourcePublish/Publish-ModuleToUniversalArtifactsFeed.ps1 @@ -59,6 +59,11 @@ Example: 'Artifacts'. .PARAMETER BearerToken Optional. The bearer token to use to authenticate the request. If not provided it MUST be existing in your environment as `$env:TOKEN` +.PARAMETER UseApiSpecsAlignedName +Optional. If set to true, the module will be published with a name that is aligned with the Azure API naming. If not, one aligned with the module's folder path. See the following examples: +- True: microsoft.keyvault.vaults.secrets +- False: key-vault.vault.secret + .EXAMPLE Publish-ModuleToUniversalArtifactsFeed -TemplateFilePath 'C:\modules\key-vault\vault\main.bicep' -ModuleVersion '3.0.0-alpha' -vstsOrganizationUri 'https://dev.azure.com/fabrikam' -VstsProject 'IaC' -VstsFeedName 'Artifacts' @@ -84,7 +89,10 @@ function Publish-ModuleToUniversalArtifactsFeed { [string] $BearerToken = $env:TOKEN, [Parameter(Mandatory)] - [string] $ModuleVersion + [string] $ModuleVersion, + + [Parameter(Mandatory = $false)] + [bool] $UseApiSpecsAlignedName = $false ) begin { @@ -103,7 +111,7 @@ function Publish-ModuleToUniversalArtifactsFeed { ################################# ## Generate package name ## ################################# - $universalPackageModuleName = Get-UniversalArtifactsName -TemplateFilePath $TemplateFilePath + $universalPackageModuleName = Get-UniversalArtifactsName -TemplateFilePath $TemplateFilePath -UseApiSpecsAlignedName $UseApiSpecsAlignedName ########################### ## Find feed scope ## diff --git a/utilities/tools/Set-ModuleReadMe.ps1 b/utilities/tools/Set-ModuleReadMe.ps1 index 9f55b4cca6..ca0f04a51b 100644 --- a/utilities/tools/Set-ModuleReadMe.ps1 +++ b/utilities/tools/Set-ModuleReadMe.ps1 @@ -1400,7 +1400,7 @@ Initialize the readme of the 'sql/managed-instance/administrator' module #> function Initialize-ReadMe { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ReadMeFilePath, @@ -1412,11 +1412,11 @@ function Initialize-ReadMe { [hashtable] $TemplateFileContent ) - . (Join-Path $PSScriptRoot 'helper' 'ConvertTo-ModuleResourceType.ps1') + . (Join-Path $PSScriptRoot 'helper' 'Get-SpecsAlignedResourceName.ps1') $moduleName = $TemplateFileContent.metadata.name $moduleDescription = $TemplateFileContent.metadata.description - $formattedResourceType = ConvertTo-ModuleResourceType -ResourceIdentifier $FullModuleIdentifier + $formattedResourceType = Get-SpecsAlignedResourceName -ResourceIdentifier $FullModuleIdentifier if (-not (Test-Path $ReadMeFilePath) -or ([String]::IsNullOrEmpty((Get-Content $ReadMeFilePath -Raw)))) { diff --git a/utilities/tools/helper/ConvertTo-ModuleResourceType.ps1 b/utilities/tools/helper/ConvertTo-ModuleResourceType.ps1 deleted file mode 100644 index e18892fc21..0000000000 --- a/utilities/tools/helper/ConvertTo-ModuleResourceType.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -<# -.SYNOPSIS -Converts a parent or child module folder path to the corresponding resource type. - -.DESCRIPTION -Converts a parent or child module folder path to the corresponding resource type. - -.PARAMETER ResourceIdentifier -Mandatory. The resource identifier to search for, i.e. the relative module file path starting from the resource provider folder. - -.EXAMPLE -ConvertTo-ModuleResourceType -ResourceIdentifier 'storage/storage-account'. - -Returns 'Microsoft.Storage/storageAccounts'. - -.EXAMPLE -ConvertTo-ModuleResourceType -ResourceIdentifier 'storage/storage-account/blob-service/container/immutability-policy'. - -Returns 'Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies'. -#> -function ConvertTo-ModuleResourceType { - - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [string] $ResourceIdentifier - ) - - . (Join-Path $PSScriptRoot 'Get-SpecsAlignedResourceName.ps1') - - $provider, $parentType, $childTypeString = $ResourceIdentifier -Split '[\/|\\]', 3 - $parentResourceIdentifier = $provider, $parentType -join '/' - - $fullParentResourceType = Get-SpecsAlignedResourceName -ResourceIdentifier $parentResourceIdentifier - - if (-not $childTypeString) { - $fullResourceType = $fullParentResourceType - } else { - $childTypeArray = $childTypeString -split '\/' - - $innerResourceType = $fullParentResourceType - foreach ($childType in $childTypeArray) { - # Additional check for child types non existing on their own (e.g. sites/hybridConnectionNamespaces does not exist, sites/hybridConnectionNamespaces/relays does) - $innerResourceTypeLeafReduced = Get-ReducedWordString -StringToReduce ($innerResourceType -Split '[\/|\\]')[-1] - $childTypeReduced = Get-ReducedWordString -StringToReduce $childType - if ($innerResourceTypeLeafReduced -eq $childTypeReduced) { - break - } - - $innerResourceType = $innerResourceType.Replace('Microsoft.', '', 'OrdinalIgnoreCase'), $childType -join '/' - $fullResourceType = Get-SpecsAlignedResourceName -ResourceIdentifier $innerResourceType - $innerResourceType = $fullResourceType - } - } - - return $fullResourceType -} diff --git a/utilities/tools/helper/Get-SpecsAlignedResourceName.ps1 b/utilities/tools/helper/Get-SpecsAlignedResourceName.ps1 index 820a478fa6..7cfab64e1d 100644 --- a/utilities/tools/helper/Get-SpecsAlignedResourceName.ps1 +++ b/utilities/tools/helper/Get-SpecsAlignedResourceName.ps1 @@ -69,6 +69,7 @@ function Get-SpecsAlignedResourceName { $rawProviderNamespace, $rawResourceType = $reducedResourceIdentifier -Split '[\/|\\]', 2 # e.g. 'keyvault' & 'vaults/keys' + # Find provider namespace $foundProviderNamespaceMatches = ($specs.Keys | Sort-Object) | Where-Object { $_ -like "Microsoft.$rawProviderNamespace*" } if (-not $foundProviderNamespaceMatches) { @@ -78,44 +79,44 @@ function Get-SpecsAlignedResourceName { $providerNamespace = ($foundProviderNamespaceMatches.Count -eq 1) ? $foundProviderNamespaceMatches : $foundProviderNamespaceMatches[0] } + # Find resource type $innerResourceTypes = $specs[$providerNamespace].Keys | Sort-Object - $rawResourceTypeReduced = Get-ReducedWordString -StringToReduce $rawResourceType - $foundResourceTypeMatches = $innerResourceTypes | Where-Object { $_ -like "$rawResourceTypeReduced*" } - - if (-not $foundResourceTypeMatches) { - $resourceType = $reducedResourceIdentifier.Split('/')[1] - Write-Warning "Failed to identify resource type [$rawResourceType] in provider namespace [$providerNamespace]. Fallback to [$resourceType]." - } elseif ($foundResourceTypeMatches.Count -eq 1) { - $resourceType = $foundResourceTypeMatches - } else { - # If more than one specs resource type matches the input resource type core string, get all specs core strings and check exact match - # This is to avoid that e.g. web/connection falls to Microsoft.Web/connectionGateways instead of Microsoft.Web/connections - foreach ($foundResourceTypeMatch in $foundResourceTypeMatches) { - $foundResourceTypeMatchReduced = Get-ReducedWordString -StringToReduce $foundResourceTypeMatch - if ($rawResourceTypeReduced -eq $foundResourceTypeMatchReduced) { - $resourceType = $foundResourceTypeMatch - break + + $rawResourceTypeElem = $rawResourceType -split '[\/|\\]' + $reducedResourceTypeElements = $rawResourceTypeElem | ForEach-Object { Get-ReducedWordString -StringToReduce $_ } + + ## We built a regex that matches the resource type, but also the plural and singular form of it along its entire path. For example ^vault(y|ii|e|ys|ies|es|s|)(\/|$)key(y|ii|e|ys|ies|es|s|)(\/|$)$ + ### (y|ii|e|ys|ies|es|s|) = Singular or plural form + ### (\/|$) = End of string or another resource type level + $resourceTypeRegex = '^{0}(y|ii|e|ys|ies|es|s|ses|)(\/|$)$' -f ($reducedResourceTypeElements -join '(y|ii|e|ys|ies|es|s|ses|)(\/|$)') + $resourceType = $innerResourceTypes | Where-Object { $_ -match $resourceTypeRegex } + + # Special case handling: Ambiguous resource types (usually incorrect RP implementations) + if ($resourceType.count -gt 1) { + switch ($rawResourceType) { + 'service/api/policy' { + # Setting explicitely as both [apimanagement/service/apis/policies] & [apimanagement/service/apis/policy] exist in the specs and the later seem to have been an initial incorrect publish (only one API version exists) + $resourceType = 'service/apis/policies' + } + Default { + throw ('Found ambiguous resource types [{0}] for identifier [{1}]' -f ($resourceType -join ','), $rawResourceType) } } + } + # Special case handling: If no resource type is found, fall back one level (e.g., for 'authorization\role-definition\management-group' as 'management-group' in this context is no actual resource type) + if (-not $resourceType) { + $fallbackResourceTypeRegex = '{0}$' -f ($resourceTypeRegex -split $reducedResourceTypeElements[-1])[0] + $resourceType = $innerResourceTypes | Where-Object { $_ -match $fallbackResourceTypeRegex } if (-not $resourceType) { - # Try removing last split of each match, then reduce to core and compare - # This is needed to deal cases such as Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers where backupFabrics does not exist on its own - foreach ($foundResourceTypeMatch in $foundResourceTypeMatches) { - $foundResourceTypeMatch = $foundResourceTypeMatch.SubString(0, $foundResourceTypeMatch.LastIndexOf('/')) - $foundResourceTypeMatchReduced = Get-ReducedWordString -StringToReduce $foundResourceTypeMatch - if ($rawResourceTypeReduced -eq $foundResourceTypeMatchReduced) { - $resourceType = $foundResourceTypeMatch - break - } - } - # Finally fallback to first match in the list - if (-not $resourceType) { - $resourceType = $foundResourceTypeMatches[0] - Write-Warning "Failed to find exact match between core matched resource types and [$rawResourceTypeReduced]. Fallback to first ResourceType in the match list [$resourceType]." - } + # if we still don't find anything (because the resource type straight up does not exist, we fall back to itself as the default) + Write-Warning "Resource type [$rawResourceType] does not exist in the API / is custom. Falling back to it as default." + $resourceType = $rawResourceType + } else { + Write-Warning ('Failed to find exact match between core matched resource types and [{0}]. Fallback on [{1}].' -f $rawResourceType, (Split-Path $rawResourceType -Parent)) } } + # Build result return "$providerNamespace/$resourceType" }