From b3a6fc7f615b8c163931597787de438fc52821ad Mon Sep 17 00:00:00 2001 From: MrMCake Date: Mon, 19 Sep 2022 16:40:10 +0200 Subject: [PATCH 1/5] Latest draft --- .../REST2CARML/Add-ModuleFileStructure.ps1 | 175 +++++++++++++++--- .../src/azureDevOpsPipelineTemplateFile.yml | 40 ++++ .../src/gitHubWorkflowTemplateFile.yml | 145 +++++++++++++++ .../tools/REST2CARML/src/moduleVersion.json | 4 + 4 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml create mode 100644 utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml create mode 100644 utilities/tools/REST2CARML/src/moduleVersion.json diff --git a/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 b/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 index 33badf94f7..131c941c56 100644 --- a/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 +++ b/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 @@ -1,49 +1,174 @@ - +#region Helper functions +<# +.SYNOPSIS +Replace tokens like '<>' in the given file with an actual value + +.DESCRIPTION +Replace tokens like '<>' in the given file with an actual value. Tokens that are replaced: +- <> +- <> +- <> +- <> +- <> +- <> + +.PARAMETER Content +Mandatory. The content to update + +.PARAMETER ProviderNamespace +Mandatory. The Provider Namespace to replaces tokens for + +.PARAMETER ResourceType +Mandatory. The Resource Type to replaces tokens for + +.EXAMPLE +Format-AutomationTemplate -Content "Hello <>-<>" -ProviderNamespace 'Microsoft.KeyVault' -ResourceType 'vaults' + +Update the provided content with different Provider Namespace & Resource Type token variant. Would return 'Hello keyvault-Vaults' +#> +function Format-AutomationTemplate { -function Add-ModuleFileStructure{ param( - [Parameter(Mandatory)] - [string] $ProviderNamespace, + [Parameter(Mandatory)] + [string] $Content, - [Parameter(Mandatory)] - [string] $ResourceType, + [Parameter(Mandatory)] + [string] $ProviderNamespace, + [Parameter(Mandatory)] + [string] $ResourceType + ) + $tokens = @{ + providerNamespace = $ProviderNamespace + providerNamespacePascal = $ProviderNamespace.substring(0, 1).toupper() + $ProviderNamespace.substring(1) + shortProviderNamespaceLower = ($ProviderNamespace -split '\.')[-1].ToLower() + resourceType = $ResourceType + resourceTypePascal = $ResourceType.substring(0, 1).toupper() + $ResourceType.substring(1) + resourceTypeLower = $ResourceType.ToLower() + } - $filesArray=@('deploy.bicep', 'readme.md', 'version.json'), + foreach ($token in $tokens.Keys) { + $content = $content -replace "<<$token>>", $tokens[$token] + } + + return $content +} +#endregion - $gitHubWorkflowYAMLPath = "github/workflows/", - $azDevOpsModulePipelineYAMLPath = ".azureDevOps/modulePipelines/" +function Add-ModuleFileStructure { + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string] $ProviderNamespace, + + [Parameter(Mandatory)] + [string] $ResourceType ) - try{ + begin { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) $repoRootPath = (Get-Item $PSScriptRoot).Parent.Parent - $ModulePath = Join-path -path $repoRootPath 'modules' + } + + process { + + # Create folders + # -------------- + $expectedModuleFolderPath = Join-Path $repoRootPath 'modules' $ProviderNamespace $ResourceType + @( + $expectedModuleFolderPath, + (Join-Path $expectedModuleFolderPath '.bicep'), + (Join-Path $expectedModuleFolderPath '.test') + (Join-Path $expectedModuleFolderPath '.test' 'common') + (Join-Path $expectedModuleFolderPath '.test' 'min') + ) | ForEach-Object { + if (-not (Test-Path $_)) { + if ($PSCmdlet.ShouldProcess(('Folder [{0}]' -f ($_ -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $_ -ItemType 'Directory' + } + } else { + Write-Verbose "Folder [$_] already exists." + } + } + + # Create module files + # ------------------- + ## Root files + ### Template file + $templateFilePath = Join-Path $expectedModuleFolderPath 'deploy.bicep' + if (-not (Test-Path $templateFilePath)) { + if ($PSCmdlet.ShouldProcess(('Template file [{0}]' -f ($templateFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $templateFilePath -ItemType 'File' + } + } else { + Write-Verbose ('Template file [{0}] already exists.' -f ($templateFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) + } - $ModuleName = "$ProviderNamespace\$ResourceType" - $ProviderDir = New-Item -Path $ModulePath -Name $ModuleName -ItemType "directory" + ### Version file + $versionFilePath = Join-Path $expectedModuleFolderPath 'version.json' + if (-not (Test-Path $versionFilePath)) { + if ($PSCmdlet.ShouldProcess(('Version file [{0}]' -f ($versionFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $versionFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'moduleVersion.json') -Raw + $null = New-Item -Path $versionFilePath -ItemType 'File' -Value $versionFileContent + } + } else { + Write-Verbose ('Version file [{0}] already exists.' -f ($versionFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) + } - write-Host $ProviderDir + ### ReadMe file - if($filesArray){ - foreach($fileName in $filesArray){ - if($fileName){ - write-Host "Creating File: $fileName" - New-Item -Path $ProviderDir -Name $fileName -ItemType "file" + ## .test files + @( + (Join-Path $expectedModuleFolderPath '.test' 'common' 'deploy.bicep') + (Join-Path $expectedModuleFolderPath '.test' 'min' 'deploy.bicep') + ) | ForEach-Object { + if (-not (Test-Path $_)) { + if ($PSCmdlet.ShouldProcess(('File [{0}]' -f ($_ -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $_ -ItemType 'File' } + } else { + Write-Verbose "File [$_] already exists." } } - $workflowYAMLPath = Join-path -path $repoRootPath $gitHubWorkflowYAMLPath - write-Host $workflowYAMLPath + # Create/Update DevOps files + # -------------------------- + ## GitHub + $automationFileName = ('ms.{0}.{1}.yml' -f ($ProviderNamespace -split '\.')[-1], $ResourceType).ToLower() + $gitHubWorkflowYAMLPath = Join-Path $repoRootPath '.github' 'workflows' $automationFileName + $workflowFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'gitHubWorkflowTemplateFile.yml') -Raw + $workflowFileContent = Format-AutomationTemplate -Content $workflowFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType + if (-not (Test-Path $gitHubWorkflowYAMLPath)) { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { + $null = New-Item $gitHubWorkflowYAMLPath -ItemType 'File' -Value $workflowFileContent + } + } else { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { + $null = Set-Content -Path $gitHubWorkflowYAMLPath -Value $workflowFileContent + } + } - }catch{ - Write-Host "An error occurred:" - Write-Host $_ + ## Azure DevOps + $azureDevOpsPipelineYAMLPath = Join-Path $repoRootPath '.azuredevops'' modulePipelines' $automationFileName + $pipelineFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'azureDevOpsPipelineTemplateFile.yml') -Raw + $pipelineFileContent = Format-AutomationTemplate -Content $pipelineFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType + if (-not (Test-Path $azureDevOpsPipelineYAMLPath)) { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { + $null = New-Item $azureDevOpsPipelineYAMLPath -ItemType 'File' -Value $pipelineFileContent + } + } else { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { + $null = Set-Content -Path $azureDevOpsPipelineYAMLPath -Value $pipelineFileContent + } + } } + end { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + } } - Add-ModuleFileStructure +Add-ModuleFileStructure -ProviderNamespace 'Microsoft.Storage' -ResourceType 'customStorage' -Verbose -WhatIf diff --git a/utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml b/utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml new file mode 100644 index 0000000000..ee10b9ae9f --- /dev/null +++ b/utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml @@ -0,0 +1,40 @@ +name: '<> - <>' + +parameters: + - name: removeDeployment + displayName: Remove deployed module + type: boolean + default: true + - name: prerelease + displayName: Publish prerelease module + type: boolean + default: false + +pr: none + +trigger: + batch: true + branches: + include: + - main + paths: + include: + - '/.azuredevops/modulePipelines/ms.<>.<>.yml' + - '/.azuredevops/pipelineTemplates/*.yml' + - '/modules/<>/<>/*' + - '/utilities/pipelines/*' + exclude: + - '/utilities/pipelines/dependencies/*' + - '/**/*.md' + +variables: + - template: '../../settings.yml' + - group: 'PLATFORM_VARIABLES' + - name: modulePath + value: '/modules/<>/<>' + +stages: + - template: /.azuredevops/pipelineTemplates/stages.module.yml + parameters: + removeDeployment: '${{ parameters.removeDeployment }}' + prerelease: '${{ parameters.prerelease }}' diff --git a/utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml b/utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml new file mode 100644 index 0000000000..d1b6426327 --- /dev/null +++ b/utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml @@ -0,0 +1,145 @@ +name: '<>: <>' + +on: + workflow_dispatch: + inputs: + removeDeployment: + type: boolean + description: 'Remove deployed module' + required: false + default: true + prerelease: + type: boolean + description: 'Publish prerelease module' + required: false + default: false + push: + branches: + - main + paths: + - '.github/actions/templates/**' + - '.github/workflows/ms.<>.<>.yml' + - 'modules/<>/<>/**' + - 'utilities/pipelines/**' + - '!utilities/pipelines/dependencies/**' + - '!*/**/readme.md' + +env: + variablesPath: 'settings.yml' + modulePath: 'modules/<>/<>' + workflowPath: '.github/workflows/ms.<>.<>.yml' + AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} + ARM_SUBSCRIPTION_ID: '${{ secrets.ARM_SUBSCRIPTION_ID }}' + ARM_MGMTGROUP_ID: '${{ secrets.ARM_MGMTGROUP_ID }}' + ARM_TENANT_ID: '${{ secrets.ARM_TENANT_ID }}' + TOKEN_NAMEPREFIX: '${{ secrets.TOKEN_NAMEPREFIX }}' + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: 'Initialize pipeline' + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: 'Set input parameters to output variables' + id: get-workflow-param + uses: ./.github/actions/templates/getWorkflowInput + with: + workflowPath: '${{ env.workflowPath}}' + - name: 'Get parameter file paths' + id: get-module-test-file-paths + uses: ./.github/actions/templates/getModuleTestFiles + with: + modulePath: '${{ env.modulePath }}' + outputs: + removeDeployment: ${{ steps.get-workflow-param.outputs.removeDeployment }} + moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }} + + ######################### + # Static validation # + ######################### + job_module_pester_validation: + runs-on: ubuntu-20.04 + name: 'Static validation' + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set environment variables + uses: ./.github/actions/templates/setEnvironmentVariables + with: + variablesPath: ${{ env.variablesPath }} + - name: 'Run tests' + uses: ./.github/actions/templates/validateModulePester + with: + modulePath: '${{ env.modulePath }}' + moduleTestFilePath: '${{ env.moduleTestFilePath }}' + + ############################# + # Deployment validation # + ############################# + job_module_deploy_validation: + runs-on: ubuntu-20.04 + name: 'Deployment validation' + needs: + - job_initialize_pipeline + - job_module_pester_validation + strategy: + fail-fast: false + matrix: + moduleTestFilePaths: ${{ fromJSON(needs.job_initialize_pipeline.outputs.moduleTestFilePaths) }} + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set environment variables + uses: ./.github/actions/templates/setEnvironmentVariables + with: + variablesPath: ${{ env.variablesPath }} + - name: 'Using test file [${{ matrix.moduleTestFilePaths }}]' + uses: ./.github/actions/templates/validateModuleDeployment + with: + templateFilePath: '${{ env.modulePath }}/${{ matrix.moduleTestFilePaths }}' + location: '${{ env.location }}' + resourceGroupName: '${{ env.resourceGroupName }}' + subscriptionId: '${{ secrets.ARM_SUBSCRIPTION_ID }}' + managementGroupId: '${{ secrets.ARM_MGMTGROUP_ID }}' + removeDeployment: '${{ needs.job_initialize_pipeline.outputs.removeDeployment }}' + + ################## + # Publishing # + ################## + job_publish_module: + name: 'Publishing' + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event.inputs.prerelease == 'true' + runs-on: ubuntu-20.04 + needs: + - job_module_deploy_validation + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set environment variables + uses: ./.github/actions/templates/setEnvironmentVariables + with: + variablesPath: ${{ env.variablesPath }} + - name: 'Publishing' + uses: ./.github/actions/templates/publishModule + with: + templateFilePath: '${{ env.modulePath }}/deploy.bicep' + templateSpecsRGName: '${{ env.templateSpecsRGName }}' + templateSpecsRGLocation: '${{ env.templateSpecsRGLocation }}' + templateSpecsDescription: '${{ env.templateSpecsDescription }}' + templateSpecsDoPublish: '${{ env.templateSpecsDoPublish }}' + bicepRegistryName: '${{ env.bicepRegistryName }}' + bicepRegistryRGName: '${{ env.bicepRegistryRGName }}' + bicepRegistryRgLocation: '${{ env.bicepRegistryRgLocation }}' + bicepRegistryDoPublish: '${{ env.bicepRegistryDoPublish }}' diff --git a/utilities/tools/REST2CARML/src/moduleVersion.json b/utilities/tools/REST2CARML/src/moduleVersion.json new file mode 100644 index 0000000000..41f66cc990 --- /dev/null +++ b/utilities/tools/REST2CARML/src/moduleVersion.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.1" +} From d2d667ccf58cf5ab886efdec1b5b1c868a3ff522 Mon Sep 17 00:00:00 2001 From: MrMCake Date: Mon, 19 Sep 2022 16:45:00 +0200 Subject: [PATCH 2/5] Added additional files --- .../tools/REST2CARML/Add-ModuleFileStructure.ps1 | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 b/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 index 131c941c56..d7b8e6db13 100644 --- a/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 +++ b/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 @@ -69,7 +69,7 @@ function Add-ModuleFileStructure { begin { Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) - $repoRootPath = (Get-Item $PSScriptRoot).Parent.Parent + $repoRootPath = (Get-Item $PSScriptRoot).Parent.Parent.Parent } process { @@ -117,8 +117,15 @@ function Add-ModuleFileStructure { Write-Verbose ('Version file [{0}] already exists.' -f ($versionFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) } - ### ReadMe file + $readMeFilePath = Join-Path $expectedModuleFolderPath 'readme.md' + if (-not (Test-Path $readMeFilePath)) { + if ($PSCmdlet.ShouldProcess(('ReadMe file [{0}]' -f ($readMeFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $readMeFilePath -ItemType 'File' + } + } else { + Write-Verbose ('ReadMe file [{0}] already exists.' -f ($readMeFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) + } ## .test files @( @@ -171,4 +178,4 @@ function Add-ModuleFileStructure { } } -Add-ModuleFileStructure -ProviderNamespace 'Microsoft.Storage' -ResourceType 'customStorage' -Verbose -WhatIf +Add-ModuleFileStructure -ProviderNamespace 'Microsoft.Storage' -ResourceType 'customStorage' -Verbose From 1a11508edeca621172be72697847ad9fe75bee17 Mon Sep 17 00:00:00 2001 From: MrMCake Date: Mon, 19 Sep 2022 16:56:09 +0200 Subject: [PATCH 3/5] Formatted functions --- .../tools/REST2CARML/Invoke-REST2CARML.ps1 | 5 +- ...ucture.ps1 => Set-ModuleFileStructure.ps1} | 388 ++++++++++-------- 2 files changed, 211 insertions(+), 182 deletions(-) rename utilities/tools/REST2CARML/{Add-ModuleFileStructure.ps1 => Set-ModuleFileStructure.ps1} (83%) diff --git a/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 b/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 index d7256dcfd9..cb60075ac2 100644 --- a/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 +++ b/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 @@ -32,6 +32,7 @@ function Invoke-REST2CARML { # Load used functions . (Join-Path $PSScriptRoot 'Get-ModuleData.ps1') + . (Join-Path $PSScriptRoot 'Set-ModuleFileStructure.ps1') Write-Verbose ('Processing module [{0}/{1}]' -f $ProviderNamespace, $ResourceType) -Verbose } @@ -41,7 +42,7 @@ function Invoke-REST2CARML { # $moduleData = Get-ModuleData -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType if ($PSCmdlet.ShouldProcess(('Module [{0}/{1}] structure' -f $ProviderNamespace, $ResourceType), 'Create/Update')) { - # TODO: Invoke function to create intial module structure & create workflow/pipeline files + Set-ModuleFileStructure -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType } if ($PSCmdlet.ShouldProcess(('Module [{0}/{1}] files' -f $ProviderNamespace, $ResourceType), 'Create/Update')) { @@ -53,3 +54,5 @@ function Invoke-REST2CARML { Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) } } + +Invoke-REST2CARML -ProviderNamespace 'Microsoft.Storage' -ResourceType 'customStorage' -Verbose diff --git a/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 b/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 similarity index 83% rename from utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 rename to utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 index d7b8e6db13..e4e5e53ad1 100644 --- a/utilities/tools/REST2CARML/Add-ModuleFileStructure.ps1 +++ b/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 @@ -1,181 +1,207 @@ -#region Helper functions -<# -.SYNOPSIS -Replace tokens like '<>' in the given file with an actual value - -.DESCRIPTION -Replace tokens like '<>' in the given file with an actual value. Tokens that are replaced: -- <> -- <> -- <> -- <> -- <> -- <> - -.PARAMETER Content -Mandatory. The content to update - -.PARAMETER ProviderNamespace -Mandatory. The Provider Namespace to replaces tokens for - -.PARAMETER ResourceType -Mandatory. The Resource Type to replaces tokens for - -.EXAMPLE -Format-AutomationTemplate -Content "Hello <>-<>" -ProviderNamespace 'Microsoft.KeyVault' -ResourceType 'vaults' - -Update the provided content with different Provider Namespace & Resource Type token variant. Would return 'Hello keyvault-Vaults' -#> -function Format-AutomationTemplate { - - param( - [Parameter(Mandatory)] - [string] $Content, - - [Parameter(Mandatory)] - [string] $ProviderNamespace, - - [Parameter(Mandatory)] - [string] $ResourceType - ) - - $tokens = @{ - providerNamespace = $ProviderNamespace - providerNamespacePascal = $ProviderNamespace.substring(0, 1).toupper() + $ProviderNamespace.substring(1) - shortProviderNamespaceLower = ($ProviderNamespace -split '\.')[-1].ToLower() - resourceType = $ResourceType - resourceTypePascal = $ResourceType.substring(0, 1).toupper() + $ResourceType.substring(1) - resourceTypeLower = $ResourceType.ToLower() - } - - foreach ($token in $tokens.Keys) { - $content = $content -replace "<<$token>>", $tokens[$token] - } - - return $content -} -#endregion - -function Add-ModuleFileStructure { - - [CmdletBinding(SupportsShouldProcess)] - param( - [Parameter(Mandatory)] - [string] $ProviderNamespace, - - [Parameter(Mandatory)] - [string] $ResourceType - ) - - begin { - Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) - $repoRootPath = (Get-Item $PSScriptRoot).Parent.Parent.Parent - } - - process { - - # Create folders - # -------------- - $expectedModuleFolderPath = Join-Path $repoRootPath 'modules' $ProviderNamespace $ResourceType - @( - $expectedModuleFolderPath, - (Join-Path $expectedModuleFolderPath '.bicep'), - (Join-Path $expectedModuleFolderPath '.test') - (Join-Path $expectedModuleFolderPath '.test' 'common') - (Join-Path $expectedModuleFolderPath '.test' 'min') - ) | ForEach-Object { - if (-not (Test-Path $_)) { - if ($PSCmdlet.ShouldProcess(('Folder [{0}]' -f ($_ -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { - $null = New-Item -Path $_ -ItemType 'Directory' - } - } else { - Write-Verbose "Folder [$_] already exists." - } - } - - # Create module files - # ------------------- - ## Root files - ### Template file - $templateFilePath = Join-Path $expectedModuleFolderPath 'deploy.bicep' - if (-not (Test-Path $templateFilePath)) { - if ($PSCmdlet.ShouldProcess(('Template file [{0}]' -f ($templateFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { - $null = New-Item -Path $templateFilePath -ItemType 'File' - } - } else { - Write-Verbose ('Template file [{0}] already exists.' -f ($templateFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) - } - - ### Version file - $versionFilePath = Join-Path $expectedModuleFolderPath 'version.json' - if (-not (Test-Path $versionFilePath)) { - if ($PSCmdlet.ShouldProcess(('Version file [{0}]' -f ($versionFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { - $versionFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'moduleVersion.json') -Raw - $null = New-Item -Path $versionFilePath -ItemType 'File' -Value $versionFileContent - } - } else { - Write-Verbose ('Version file [{0}] already exists.' -f ($versionFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) - } - - ### ReadMe file - $readMeFilePath = Join-Path $expectedModuleFolderPath 'readme.md' - if (-not (Test-Path $readMeFilePath)) { - if ($PSCmdlet.ShouldProcess(('ReadMe file [{0}]' -f ($readMeFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { - $null = New-Item -Path $readMeFilePath -ItemType 'File' - } - } else { - Write-Verbose ('ReadMe file [{0}] already exists.' -f ($readMeFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) - } - - ## .test files - @( - (Join-Path $expectedModuleFolderPath '.test' 'common' 'deploy.bicep') - (Join-Path $expectedModuleFolderPath '.test' 'min' 'deploy.bicep') - ) | ForEach-Object { - if (-not (Test-Path $_)) { - if ($PSCmdlet.ShouldProcess(('File [{0}]' -f ($_ -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { - $null = New-Item -Path $_ -ItemType 'File' - } - } else { - Write-Verbose "File [$_] already exists." - } - } - - # Create/Update DevOps files - # -------------------------- - ## GitHub - $automationFileName = ('ms.{0}.{1}.yml' -f ($ProviderNamespace -split '\.')[-1], $ResourceType).ToLower() - $gitHubWorkflowYAMLPath = Join-Path $repoRootPath '.github' 'workflows' $automationFileName - $workflowFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'gitHubWorkflowTemplateFile.yml') -Raw - $workflowFileContent = Format-AutomationTemplate -Content $workflowFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType - if (-not (Test-Path $gitHubWorkflowYAMLPath)) { - if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { - $null = New-Item $gitHubWorkflowYAMLPath -ItemType 'File' -Value $workflowFileContent - } - } else { - if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { - $null = Set-Content -Path $gitHubWorkflowYAMLPath -Value $workflowFileContent - } - } - - ## Azure DevOps - $azureDevOpsPipelineYAMLPath = Join-Path $repoRootPath '.azuredevops'' modulePipelines' $automationFileName - $pipelineFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'azureDevOpsPipelineTemplateFile.yml') -Raw - $pipelineFileContent = Format-AutomationTemplate -Content $pipelineFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType - if (-not (Test-Path $azureDevOpsPipelineYAMLPath)) { - if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { - $null = New-Item $azureDevOpsPipelineYAMLPath -ItemType 'File' -Value $pipelineFileContent - } - } else { - if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { - $null = Set-Content -Path $azureDevOpsPipelineYAMLPath -Value $pipelineFileContent - } - } - } - - end { - Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) - } -} - -Add-ModuleFileStructure -ProviderNamespace 'Microsoft.Storage' -ResourceType 'customStorage' -Verbose +#region Helper functions +<# +.SYNOPSIS +Replace tokens like '<>' in the given file with an actual value + +.DESCRIPTION +Replace tokens like '<>' in the given file with an actual value. Tokens that are replaced: +- <> +- <> +- <> +- <> +- <> +- <> + +.PARAMETER Content +Mandatory. The content to update + +.PARAMETER ProviderNamespace +Mandatory. The Provider Namespace to replaces tokens for + +.PARAMETER ResourceType +Mandatory. The Resource Type to replaces tokens for + +.EXAMPLE +Format-AutomationTemplate -Content "Hello <>-<>" -ProviderNamespace 'Microsoft.KeyVault' -ResourceType 'vaults' + +Update the provided content with different Provider Namespace & Resource Type token variant. Would return 'Hello keyvault-Vaults' +#> +function Format-AutomationTemplate { + + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $Content, + + [Parameter(Mandatory)] + [string] $ProviderNamespace, + + [Parameter(Mandatory)] + [string] $ResourceType + ) + + begin { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + } + + process { + $tokens = @{ + providerNamespace = $ProviderNamespace + providerNamespacePascal = $ProviderNamespace.substring(0, 1).toupper() + $ProviderNamespace.substring(1) + shortProviderNamespaceLower = ($ProviderNamespace -split '\.')[-1].ToLower() + resourceType = $ResourceType + resourceTypePascal = $ResourceType.substring(0, 1).toupper() + $ResourceType.substring(1) + resourceTypeLower = $ResourceType.ToLower() + } + + foreach ($token in $tokens.Keys) { + $content = $content -replace "<<$token>>", $tokens[$token] + } + + return $content + } + + end { + Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) + } +} +#endregion + +<# +.SYNOPSIS +Update / create the initial folder structure for a CARML module + +.DESCRIPTION +Update / create the initial folder structure for a CARML module + +.PARAMETER ProviderNamespace +Mandatory. The Provider Namespace to process + +.PARAMETER ResourceType +Mandatory. The Resource Type to process + +.EXAMPLE +Set-ModuleFileStructure -ProviderNamespace 'Microsoft.KeyVault' -ResourceType 'vaults' + +Update / create the folder & file structure for the CARML module 'Microsoft.KeyVault/vaults'. +#> +function Set-ModuleFileStructure { + + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string] $ProviderNamespace, + + [Parameter(Mandatory)] + [string] $ResourceType + ) + + begin { + Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + $repoRootPath = (Get-Item $PSScriptRoot).Parent.Parent.Parent + } + + process { + # Create folders + # -------------- + $expectedModuleFolderPath = Join-Path $repoRootPath 'modules' $ProviderNamespace $ResourceType + @( + $expectedModuleFolderPath, + (Join-Path $expectedModuleFolderPath '.bicep'), + (Join-Path $expectedModuleFolderPath '.test') + (Join-Path $expectedModuleFolderPath '.test' 'common') + (Join-Path $expectedModuleFolderPath '.test' 'min') + ) | ForEach-Object { + if (-not (Test-Path $_)) { + if ($PSCmdlet.ShouldProcess(('Folder [{0}]' -f ($_ -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $_ -ItemType 'Directory' + } + } else { + Write-Verbose "Folder [$_] already exists." + } + } + + # Create module files + # ------------------- + ## Root files + ### Template file + $templateFilePath = Join-Path $expectedModuleFolderPath 'deploy.bicep' + if (-not (Test-Path $templateFilePath)) { + if ($PSCmdlet.ShouldProcess(('Template file [{0}]' -f ($templateFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $templateFilePath -ItemType 'File' + } + } else { + Write-Verbose ('Template file [{0}] already exists.' -f ($templateFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) + } + + ### Version file + $versionFilePath = Join-Path $expectedModuleFolderPath 'version.json' + if (-not (Test-Path $versionFilePath)) { + if ($PSCmdlet.ShouldProcess(('Version file [{0}]' -f ($versionFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $versionFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'moduleVersion.json') -Raw + $null = New-Item -Path $versionFilePath -ItemType 'File' -Value $versionFileContent + } + } else { + Write-Verbose ('Version file [{0}] already exists.' -f ($versionFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) + } + + ### ReadMe file + $readMeFilePath = Join-Path $expectedModuleFolderPath 'readme.md' + if (-not (Test-Path $readMeFilePath)) { + if ($PSCmdlet.ShouldProcess(('ReadMe file [{0}]' -f ($readMeFilePath -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $readMeFilePath -ItemType 'File' + } + } else { + Write-Verbose ('ReadMe file [{0}] already exists.' -f ($readMeFilePath -replace ($repoRootPath -replace '\\', '\\'), '')) + } + + ## .test files + @( + (Join-Path $expectedModuleFolderPath '.test' 'common' 'deploy.bicep') + (Join-Path $expectedModuleFolderPath '.test' 'min' 'deploy.bicep') + ) | ForEach-Object { + if (-not (Test-Path $_)) { + if ($PSCmdlet.ShouldProcess(('File [{0}]' -f ($_ -replace ($repoRootPath -replace '\\', '\\'), '')), 'Create')) { + $null = New-Item -Path $_ -ItemType 'File' + } + } else { + Write-Verbose "File [$_] already exists." + } + } + + # Create/Update DevOps files + # -------------------------- + ## GitHub + $automationFileName = ('ms.{0}.{1}.yml' -f ($ProviderNamespace -split '\.')[-1], $ResourceType).ToLower() + $gitHubWorkflowYAMLPath = Join-Path $repoRootPath '.github' 'workflows' $automationFileName + $workflowFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'gitHubWorkflowTemplateFile.yml') -Raw + $workflowFileContent = Format-AutomationTemplate -Content $workflowFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType + if (-not (Test-Path $gitHubWorkflowYAMLPath)) { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { + $null = New-Item $gitHubWorkflowYAMLPath -ItemType 'File' -Value $workflowFileContent + } + } else { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { + $null = Set-Content -Path $gitHubWorkflowYAMLPath -Value $workflowFileContent + } + } + + ## Azure DevOps + $azureDevOpsPipelineYAMLPath = Join-Path $repoRootPath '.azuredevops'' modulePipelines' $automationFileName + $pipelineFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'azureDevOpsPipelineTemplateFile.yml') -Raw + $pipelineFileContent = Format-AutomationTemplate -Content $pipelineFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType + if (-not (Test-Path $azureDevOpsPipelineYAMLPath)) { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { + $null = New-Item $azureDevOpsPipelineYAMLPath -ItemType 'File' -Value $pipelineFileContent + } + } else { + if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { + $null = Set-Content -Path $azureDevOpsPipelineYAMLPath -Value $pipelineFileContent + } + } + } + + end { + Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) + } +} From ea2802197f1d41fcc0d8b5c5a3d3a2d9aada4b75 Mon Sep 17 00:00:00 2001 From: MrMCake Date: Mon, 19 Sep 2022 17:02:00 +0200 Subject: [PATCH 4/5] Update to latest --- utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 | 2 +- utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 b/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 index cb60075ac2..86f9ad27d5 100644 --- a/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 +++ b/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 @@ -31,7 +31,7 @@ function Invoke-REST2CARML { Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) # Load used functions - . (Join-Path $PSScriptRoot 'Get-ModuleData.ps1') + # . (Join-Path $PSScriptRoot 'Get-ModuleData.ps1') . (Join-Path $PSScriptRoot 'Set-ModuleFileStructure.ps1') Write-Verbose ('Processing module [{0}/{1}]' -f $ProviderNamespace, $ResourceType) -Verbose diff --git a/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 b/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 index e4e5e53ad1..780f080186 100644 --- a/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 +++ b/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 @@ -187,7 +187,7 @@ function Set-ModuleFileStructure { } ## Azure DevOps - $azureDevOpsPipelineYAMLPath = Join-Path $repoRootPath '.azuredevops'' modulePipelines' $automationFileName + $azureDevOpsPipelineYAMLPath = Join-Path $repoRootPath '.azuredevops' 'modulePipelines' $automationFileName $pipelineFileContent = Get-Content (Join-Path $PSScriptRoot 'src' 'azureDevOpsPipelineTemplateFile.yml') -Raw $pipelineFileContent = Format-AutomationTemplate -Content $pipelineFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType if (-not (Test-Path $azureDevOpsPipelineYAMLPath)) { From 26417c4b4bf31107ef1dc761daaf5a46d1170989 Mon Sep 17 00:00:00 2001 From: MrMCake Date: Mon, 19 Sep 2022 17:26:02 +0200 Subject: [PATCH 5/5] Update to latest --- .../tools/REST2CARML/Invoke-REST2CARML.ps1 | 2 +- .../REST2CARML/Set-ModuleFileStructure.ps1 | 22 +++++++++---------- .../src/azureDevOpsPipelineTemplateFile.yml | 2 +- .../src/gitHubWorkflowTemplateFile.yml | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 b/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 index 86f9ad27d5..4d6bf8e7c7 100644 --- a/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 +++ b/utilities/tools/REST2CARML/Invoke-REST2CARML.ps1 @@ -55,4 +55,4 @@ function Invoke-REST2CARML { } } -Invoke-REST2CARML -ProviderNamespace 'Microsoft.Storage' -ResourceType 'customStorage' -Verbose +Invoke-REST2CARML -ProviderNamespace 'Microsoft.Storage' -ResourceType 'storageAccounts' -Verbose diff --git a/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 b/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 index 780f080186..372283b84b 100644 --- a/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 +++ b/utilities/tools/REST2CARML/Set-ModuleFileStructure.ps1 @@ -6,7 +6,7 @@ Replace tokens like '<>' in the given file with an actual val .DESCRIPTION Replace tokens like '<>' in the given file with an actual value. Tokens that are replaced: - <> -- <> +- <> - <> - <> - <> @@ -46,12 +46,12 @@ function Format-AutomationTemplate { process { $tokens = @{ - providerNamespace = $ProviderNamespace - providerNamespacePascal = $ProviderNamespace.substring(0, 1).toupper() + $ProviderNamespace.substring(1) - shortProviderNamespaceLower = ($ProviderNamespace -split '\.')[-1].ToLower() - resourceType = $ResourceType - resourceTypePascal = $ResourceType.substring(0, 1).toupper() + $ResourceType.substring(1) - resourceTypeLower = $ResourceType.ToLower() + providerNamespace = $ProviderNamespace + shortProviderNamespacePascal = ($ProviderNamespace -split '\.')[-1].substring(0, 1).toupper() + ($ProviderNamespace -split '\.')[-1].substring(1) + shortProviderNamespaceLower = ($ProviderNamespace -split '\.')[-1].ToLower() + resourceType = $ResourceType + resourceTypePascal = $ResourceType.substring(0, 1).toupper() + $ResourceType.substring(1) + resourceTypeLower = $ResourceType.ToLower() } foreach ($token in $tokens.Keys) { @@ -178,11 +178,11 @@ function Set-ModuleFileStructure { $workflowFileContent = Format-AutomationTemplate -Content $workflowFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType if (-not (Test-Path $gitHubWorkflowYAMLPath)) { if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { - $null = New-Item $gitHubWorkflowYAMLPath -ItemType 'File' -Value $workflowFileContent + $null = New-Item $gitHubWorkflowYAMLPath -ItemType 'File' -Value $workflowFileContent.TrimEnd() } } else { if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { - $null = Set-Content -Path $gitHubWorkflowYAMLPath -Value $workflowFileContent + $null = Set-Content -Path $gitHubWorkflowYAMLPath -Value $workflowFileContent.TrimEnd() } } @@ -192,11 +192,11 @@ function Set-ModuleFileStructure { $pipelineFileContent = Format-AutomationTemplate -Content $pipelineFileContent -ProviderNamespace $ProviderNamespace -ResourceType $ResourceType if (-not (Test-Path $azureDevOpsPipelineYAMLPath)) { if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Create')) { - $null = New-Item $azureDevOpsPipelineYAMLPath -ItemType 'File' -Value $pipelineFileContent + $null = New-Item $azureDevOpsPipelineYAMLPath -ItemType 'File' -Value $pipelineFileContent.TrimEnd() } } else { if ($PSCmdlet.ShouldProcess("GitHub Workflow file [$automationFileName]", 'Update')) { - $null = Set-Content -Path $azureDevOpsPipelineYAMLPath -Value $pipelineFileContent + $null = Set-Content -Path $azureDevOpsPipelineYAMLPath -Value $pipelineFileContent.TrimEnd() } } } diff --git a/utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml b/utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml index ee10b9ae9f..fca91b6c9c 100644 --- a/utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml +++ b/utilities/tools/REST2CARML/src/azureDevOpsPipelineTemplateFile.yml @@ -1,4 +1,4 @@ -name: '<> - <>' +name: '<> - <>' parameters: - name: removeDeployment diff --git a/utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml b/utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml index d1b6426327..cad71a3ef1 100644 --- a/utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml +++ b/utilities/tools/REST2CARML/src/gitHubWorkflowTemplateFile.yml @@ -1,4 +1,4 @@ -name: '<>: <>' +name: '<>: <>' on: workflow_dispatch: