From 54dc4e439cf49e206970f98b9764e74802cd533a Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sat, 20 Feb 2021 01:03:03 +1000 Subject: [PATCH] Added optional snippets and badges #31 #30 #42 (#44) * Added optional snippets and badges #31 #30 #42 * Remove quick start config * Get option from root path * Doc improvements based on feedback @vicperdana --- .vscode/settings.json | 5 +- CHANGELOG.md | 15 + README.md | 17 + docs/commands/en-US/Get-AzDocTemplateFile.md | 2 +- .../en-US/about_PSDocs_Azure_Badges.md | 91 +++++ .../en-US/about_PSDocs_Azure_Configuration.md | 61 +++ pipeline.build.ps1 | 10 +- src/PSDocs.Azure/docs/Azure.Template.Doc.ps1 | 52 ++- src/PSDocs.Azure/en/PSDocs-strings.psd1 | 1 + templates/storage/v1/README.md | 2 + .../.ps-docs/azure-template-badges.md | 12 + .../Azure.QuickStart.Tests.ps1 | 50 +++ .../template-test/template.json | 366 ++++++++++++++++++ 13 files changed, 668 insertions(+), 16 deletions(-) create mode 100644 docs/concepts/en-US/about_PSDocs_Azure_Badges.md create mode 100644 docs/concepts/en-US/about_PSDocs_Azure_Configuration.md create mode 100644 tests/PSDocs.Azure.Tests/.ps-docs/azure-template-badges.md create mode 100644 tests/PSDocs.Azure.Tests/Azure.QuickStart.Tests.ps1 create mode 100644 tests/PSDocs.Azure.Tests/template-test/template.json diff --git a/.vscode/settings.json b/.vscode/settings.json index ebe9f41..5c6434c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,7 +22,10 @@ "**/.azure-pipelines/jobs/*.yaml": "azure-pipelines" }, "cSpell.words": [ - "cmdlet" + "Concat", + "Quickstart", + "cmdlet", + "cmdlets" ], "cSpell.enabledLanguageIds": [ "csharp", diff --git a/CHANGELOG.md b/CHANGELOG.md index 2906baa..ca3e937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +What's changed since pre-release v0.2.0-B2101002: + +- New features: + - Added the ability to disable parameter file snippet. [#31](https://github.com/Azure/PSDocs.Azure/issues/31) + - To disable parameter file snippet set configuration `AZURE_USE_PARAMETER_FILE_SNIPPET`. + - See [about_PSDocs_Azure_Configuration] for details. + - Added the ability to include badges in template document. [#30](https://github.com/Azure/PSDocs.Azure/issues/30) + - Set the `.ps-docs/azure-template-badges.md` file to include badge content. + - See [about_PSDocs_Azure_Badges] for details. +- Engineering: + - Bump PSDocs dependency to v0.8.0. [#42](https://github.com/Azure/PSDocs.Azure/issues/42) + ## v0.2.0-B2101002 (pre-release) What's changed since v0.1.0: @@ -28,3 +40,6 @@ What's changed since pre-release v0.1.0-B2012006: ## v0.1.0-B2012006 (pre-release) - Initial pre-release. + +[about_PSDocs_Azure_Configuration]: docs/concepts/en-US/about_PSDocs_Azure_Configuration.md +[about_PSDocs_Azure_Badges]: docs/concepts/en-US/about_PSDocs_Azure_Badges.md diff --git a/README.md b/README.md index ffd742e..08db904 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,23 @@ jobs: source_dir: 'out/docs/*' ``` +## Language reference + +PSDocs for Azure extends PowerShell with the following cmdlets and concepts. + +### Commands + +The following commands exist in the `PSDocs.Azure` module: + +- [Get-AzDocTemplateFile](docs/commands/en-US/Get-AzDocTemplateFile.md) - Get Azure template files within a directory structure. + +### Concepts + +The following conceptual topics exist in the `PSDocs.Azure` module: + +- [Badges](docs/concepts/en-US/about_PSDocs_Azure_Badges.md) +- [Configuration](docs/concepts/en-US/about_PSDocs_Azure_Configuration.md) + - [AZURE_USE_PARAMETER_FILE_SNIPPET](docs/concepts/en-US/about_PSDocs_Azure_Configuration.md#azure_use_parameter_file_snippet) ## Changes and versioning diff --git a/docs/commands/en-US/Get-AzDocTemplateFile.md b/docs/commands/en-US/Get-AzDocTemplateFile.md index ab82350..486ae15 100644 --- a/docs/commands/en-US/Get-AzDocTemplateFile.md +++ b/docs/commands/en-US/Get-AzDocTemplateFile.md @@ -9,7 +9,7 @@ schema: 2.0.0 ## SYNOPSIS -Get Azure template files. +Get Azure template files within a directory structure. ## SYNTAX diff --git a/docs/concepts/en-US/about_PSDocs_Azure_Badges.md b/docs/concepts/en-US/about_PSDocs_Azure_Badges.md new file mode 100644 index 0000000..6f6d47e --- /dev/null +++ b/docs/concepts/en-US/about_PSDocs_Azure_Badges.md @@ -0,0 +1,91 @@ +# PSDocs_Azure_Badges + +## about_PSDocs_Azure_Badges + +## SHORT DESCRIPTION + +Describes how to insert template badges in to documentation. + +## LONG DESCRIPTION + +PSDocs allows external files to be included in Azure template documentation. +Commonly this concept is used to include images that represent the validation status of the template. +These images, are commonly referred to as _badges_. +PSDocs can include badges that have been generated by an external validation tool. + +To include badge markdown: + +- Create a sub-directory called `.ps-docs` in the working path of PSDocs. +This would normally be root directory (`$PWD`) of the repository where your Azure template are stored. +- Create a file named `azure-template-badges.md` within the `.ps-docs` sub-directory. +- When creating all files and folder use lower case names. + +The contents of this file is automatically inserted in generated output after the title but before description. + +### Include badges + +To include badge images use standard markdown syntax within the `azure-template-badges.md` file. + +> Markdown uses links to reference images. +> A person viewing the page must have permissions to view the source image. +> If not, the badge may be shown as a broken or placeholder image. + +For example: + +```markdown +![label](https://image_uri) +``` + +For example, a Github Actions badge for PSDocs.Azure would be: + +```markdown +![Analyze](https://github.com/Azure/PSDocs.Azure/workflows/Analyze/badge.svg) +``` + +To include badges images with a clickable link use standard markdown syntax: + +```markdown +[![label](https://image_uri)](https://link_uri) +``` + +For example, an Azure Pipelines badge for PSDocs.Azure would be: + +```markdown +[![Build Status](https://dev.azure.com/PSDocs/PSDocs.Azure/_apis/build/status/PSDocs.Azure-CI?branchName=refs%2Fpull%2F44%2Fmerge)](https://dev.azure.com/PSDocs/PSDocs.Azure/_build/latest?definitionId=1&branchName=refs%2Fpull%2F44%2Fmerge) +``` + +### Dynamic links + +In additional to inserting static content, some replacement tokens have been defined. +When specified within `azure-template-badges.md` each token will be replaced when the file is included. +The following replacement tokens have been defined: + +- `{{ template_path }}` - The relative path of the template directory. +- `{{ template_path_encoded }}` - The relative path of the template directory URL encoded. + +For example, if the template path was `.\templates\storage\v1\template.json` the following would be used: + +- `{{ template_path }}` = `templates/storage/v1` +- `{{ template_path_encoded }}` = `templates%2fstorage%2fv1` + +The follow example shows source markdown for including badges: + +```markdown +![Best Practice Check](https://azurequickstartsservice.blob.core.windows.net/badges/{{ template_path }}/BestPracticeResult.svg) +[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F{{ template_path_encoded }}%2Fazuredeploy.json) +``` + +After replacement the following would be the resulting output included in the template document: + +```markdown +![Best Practice Check](https://azurequickstartsservice.blob.core.windows.net/badges/templates/storage/v1/BestPracticeResult.svg) +[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2Ftemplates%2fstorage%2fv1%2Fazuredeploy.json) +``` + +## NOTE + +An online version of this document is available at https://github.com/Azure/PSDocs.Azure/blob/main/docs/concepts/en-US/about_PSDocs_Azure_Badges.md. + +## KEYWORDS + +- Badge diff --git a/docs/concepts/en-US/about_PSDocs_Azure_Configuration.md b/docs/concepts/en-US/about_PSDocs_Azure_Configuration.md new file mode 100644 index 0000000..afd072d --- /dev/null +++ b/docs/concepts/en-US/about_PSDocs_Azure_Configuration.md @@ -0,0 +1,61 @@ +# PSDocs_Azure_Configuration + +## about_PSDocs_Azure_Configuration + +## SHORT DESCRIPTION + +Describes PSDocs configuration options specific to `PSDocs.Azure`. + +## LONG DESCRIPTION + +PSDocs exposes configuration options that can be used to customize execution of document generation. +This topic describes what configuration options are available. + +PSDocs configuration options can be specified by setting the configuration option in `ps-docs.yaml`. +Additionally, configuration options can be configured in a baseline or set at runtime. +For details of setting configuration options see [PSDocs options][options] + +The following configurations options are available for use: + +- [AZURE_USE_PARAMETER_FILE_SNIPPET](#azure_use_parameter_file_snippet) + +### AZURE_USE_PARAMETER_FILE_SNIPPET + +This configuration option determines if a parameter file snippet is added to documentation. +By default, a snippet is generated. +To prevent a parameter file snippet being generated, set this option to `false`. + +Syntax: + +```yaml +configuration: + AZURE_USE_PARAMETER_FILE_SNIPPET: bool # Either true or false +``` + +Default: + +```yaml +# YAML: The default AZURE_USE_PARAMETER_FILE_SNIPPET configuration option +configuration: + AZURE_USE_PARAMETER_FILE_SNIPPET: true +``` + +Example: + +```yaml +# YAML: Prevent parameter file snippet from being generated +configuration: + AZURE_USE_PARAMETER_FILE_SNIPPET: false +``` + +## NOTE + +An online version of this document is available at https://github.com/Azure/PSDocs.Azure/blob/main/docs/concepts/en-US/about_PSDocs_Azure_Configuration.md. + +## KEYWORDS + +- Configuration +- Document +- Snippet + +[options]: https://github.com/BernieWhite/PSDocs/blob/main/docs/concepts/PSDocs/en-US/about_PSDocs_Configuration.md diff --git a/pipeline.build.ps1 b/pipeline.build.ps1 index bfd6290..583d3c6 100644 --- a/pipeline.build.ps1 +++ b/pipeline.build.ps1 @@ -108,7 +108,7 @@ task VersionModule ModuleDependencies, { $manifest = Test-ModuleManifest -Path $manifestPath; $requiredModules = $manifest.RequiredModules | ForEach-Object -Process { if ($_.Name -eq 'PSDocs' -and $Configuration -eq 'Release') { - @{ ModuleName = 'PSDocs'; ModuleVersion = '0.7.0' } + @{ ModuleName = 'PSDocs'; ModuleVersion = '0.8.0' } } else { @{ ModuleName = $_.Name; ModuleVersion = $_.Version } @@ -155,8 +155,8 @@ task PSScriptAnalyzer NuGet, { # Synopsis: Install PSRule task PSRule NuGet, { - if ($Null -eq (Get-InstalledModule -Name PSRule -MinimumVersion 1.0.1 -ErrorAction Ignore)) { - Install-Module -Name PSRule -Repository PSGallery -MinimumVersion 1.0.1 -Scope CurrentUser -Force; + if ($Null -eq (Get-InstalledModule -Name PSRule -MinimumVersion 1.0.3 -ErrorAction Ignore)) { + Install-Module -Name PSRule -Repository PSGallery -MinimumVersion 1.0.3 -Scope CurrentUser -Force; } if ($Null -eq (Get-InstalledModule -Name PSRule.Rules.MSFT.OSS -MinimumVersion '0.1.0-B2012007' -AllowPrerelease -ErrorAction Ignore)) { Install-Module -Name PSRule.Rules.MSFT.OSS -Repository PSGallery -MinimumVersion '0.1.0-B2012007' -AllowPrerelease -Scope CurrentUser -Force; @@ -166,8 +166,8 @@ task PSRule NuGet, { # Synopsis: Install PSDocs task PSDocs NuGet, { - if ($Null -eq (Get-InstalledModule -Name PSDocs -MinimumVersion 0.7.0 -ErrorAction Ignore)) { - Install-Module -Name PSDocs -Repository PSGallery -MinimumVersion 0.7.0 -Scope CurrentUser -Force; + if ($Null -eq (Get-InstalledModule -Name PSDocs -MinimumVersion 0.8.0 -ErrorAction Ignore)) { + Install-Module -Name PSDocs -Repository PSGallery -MinimumVersion 0.8.0 -Scope CurrentUser -Force; } Import-Module -Name PSDocs -Verbose:$False; } diff --git a/src/PSDocs.Azure/docs/Azure.Template.Doc.ps1 b/src/PSDocs.Azure/docs/Azure.Template.Doc.ps1 index ff2321b..38b37b5 100644 --- a/src/PSDocs.Azure/docs/Azure.Template.Doc.ps1 +++ b/src/PSDocs.Azure/docs/Azure.Template.Doc.ps1 @@ -47,11 +47,7 @@ function global:GetTemplateExample { $Path = Join-Path -Path $PWD -ChildPath $Path; } $template = Get-Content -Path $Path -Raw | ConvertFrom-Json; - $normalPath = $Path; - if ($normalPath.StartsWith($PWD, [System.StringComparison]::InvariantCultureIgnoreCase)) { - $normalPath = $Path.Substring(([String]$PWD).Length); - $normalPath = ($normalPath -replace '\\', '/').TrimStart('/'); - } + $normalPath = GetTemplateRelativePath -Path $Path; $baseContent = [PSCustomObject]@{ '$schema'= "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json`#" contentVersion = '1.0.0.0' @@ -68,7 +64,7 @@ function global:GetTemplateExample { continue; } - if ($property.Value.type -eq 'securestring') { + if ($property.Value.type -eq 'secureString') { $param = [PSCustomObject]@{ reference = [PSCustomObject]@{ keyVault = [PSCustomObject]@{ @@ -109,6 +105,27 @@ function global:GetTemplateExample { } } +# Synopsis: Function to get the relative path of the template +function global:GetTemplateRelativePath { + [CmdletBinding()] + [OutputType([String])] + param ( + [Parameter(Mandatory = $True)] + [String]$Path + ) + process { + if (![System.IO.Path]::IsPathRooted($Path)) { + $Path = [System.IO.Path]::Combine($PWD, $Path); + } + $normalPath = [System.IO.Path]::GetFullPath($Path); + if ($normalPath.StartsWith($PWD, [System.StringComparison]::InvariantCultureIgnoreCase)) { + $normalPath = $normalPath.Substring(([String]$PWD).Length); + $normalPath = $normalPath.Replace('\', '/').TrimStart('/') + } + return $normalPath; + } +} + # A function to import metadata function global:GetTemplateMetadata { [CmdletBinding()] @@ -164,6 +181,17 @@ Document 'README' { Title $LocalizedData.DefaultTitle } + # Add badges + $relativePath = (Split-Path (GetTemplateRelativePath -Path $templatePath) -Parent).Replace('\', '/').TrimStart('/'); + $relativePathEncoded = [System.Web.HttpUtility]::UrlEncode($relativePath); + Include '.ps-docs/azure-template-badges.md' -ErrorAction SilentlyContinue -BaseDirectory $PWD -Replace @{ + '{{ template_path }}' = $relativePath + '{{ template_path_encoded }}' = $relativePathEncoded + } + + Write-Verbose $relativePath + Write-Verbose $relativePathEncoded + # Write opening line if ($Null -ne $metadata -and [bool]$metadata.PSObject.Properties['description']) { $metadata.description @@ -198,9 +226,15 @@ Document 'README' { @{ Name = $LocalizedData.Description; Expression = { $_.Description }} } - # Insert snippet - $example = GetTemplateExample -Path $templatePath; + # Insert snippets Section $LocalizedData.Snippets { - $example | Code 'json' + + # Add parameter file snippet + if ($PSDocs.Configuration.GetBoolOrDefault('AZURE_USE_PARAMETER_FILE_SNIPPET', $True)) { + Section $LocalizedData.ParameterFile { + $example = GetTemplateExample -Path $templatePath; + $example | Code 'json' + } + } } } diff --git a/src/PSDocs.Azure/en/PSDocs-strings.psd1 b/src/PSDocs.Azure/en/PSDocs-strings.psd1 index 93f2e4f..1e56890 100644 --- a/src/PSDocs.Azure/en/PSDocs-strings.psd1 +++ b/src/PSDocs.Azure/en/PSDocs-strings.psd1 @@ -12,4 +12,5 @@ Type = 'Type' Name = 'Name' DefaultTitle = 'Azure template' + ParameterFile = 'Parameter file' } diff --git a/templates/storage/v1/README.md b/templates/storage/v1/README.md index c2db869..9791749 100644 --- a/templates/storage/v1/README.md +++ b/templates/storage/v1/README.md @@ -104,6 +104,8 @@ resourceId | string | A unique resource identifier for the storage account. ## Snippets +### Parameter file + ```json { "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", diff --git a/tests/PSDocs.Azure.Tests/.ps-docs/azure-template-badges.md b/tests/PSDocs.Azure.Tests/.ps-docs/azure-template-badges.md new file mode 100644 index 0000000..32a7f78 --- /dev/null +++ b/tests/PSDocs.Azure.Tests/.ps-docs/azure-template-badges.md @@ -0,0 +1,12 @@ +![Azure Public Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/{{ template_path }}/PublicLastTestDate.svg) +![Azure Public Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/{{ template_path }}/PublicDeployment.svg) + +![Azure US Gov Last Test Date](https://azurequickstartsservice.blob.core.windows.net/badges/{{ template_path }}/FairfaxLastTestDate.svg) +![Azure US Gov Last Test Result](https://azurequickstartsservice.blob.core.windows.net/badges/{{ template_path }}/FairfaxDeployment.svg) + +![Best Practice Check](https://azurequickstartsservice.blob.core.windows.net/badges/{{ template_path }}/BestPracticeResult.svg) +![Cred Scan Check](https://azurequickstartsservice.blob.core.windows.net/badges/{{ template_path }}/CredScanResult.svg) + +[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F{{ template_path_encoded }}%2Fazuredeploy.json) +[![Deploy To Azure Gov](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazuregov.svg?sanitize=true)](https://portal.azure.us/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F{{ template_path_encoded }}%2Fazuredeploy.json) +[![Visualize](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/visualizebutton.svg?sanitize=true)](http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F{{ template_path_encoded }}%2Fazuredeploy.json) diff --git a/tests/PSDocs.Azure.Tests/Azure.QuickStart.Tests.ps1 b/tests/PSDocs.Azure.Tests/Azure.QuickStart.Tests.ps1 new file mode 100644 index 0000000..f1a867d --- /dev/null +++ b/tests/PSDocs.Azure.Tests/Azure.QuickStart.Tests.ps1 @@ -0,0 +1,50 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# +# Unit tests for PSDocs.Azure functionality Azure Quick Start templates +# + +[CmdletBinding()] +param () + +# Setup error handling +$ErrorActionPreference = 'Stop'; +Set-StrictMode -Version latest; + +# Setup tests paths +$rootPath = $PWD; + +Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSDocs.Azure) -Force; + +$outputPath = Join-Path -Path $rootPath -ChildPath out/tests/PSDocs.Azure.Tests/Common; +Remove-Item -Path $outputPath -Force -Recurse -Confirm:$False -ErrorAction Ignore; +$Null = New-Item -Path $outputPath -ItemType Directory -Force; +$here = $PSScriptRoot; + +Describe 'Templates' -Tag 'QuickStart' { + Context 'Documentation for examples' { + It 'Generates expected output' { + $invokeParams = @{ + Module = 'PSDocs.Azure' + Option = (New-PSDocumentOption) + } + + Push-Location -Path $here; + $templatePath = Join-Path -Path $here -ChildPath 'template-test/'; + try { + # Generates templates + $result = Get-AzDocTemplateFile -Path $templatePath | ForEach-Object { + $template = Get-Item -Path $_.TemplateFile; + $actualContent = Invoke-PSDocument @invokeParams -OutputPath $outputPath -InputObject $template.FullName -PassThru; + $actualContent | Should -BeLike '*!`[Azure Public Test Date`](https://azurequickstartsservice.blob.core.windows.net/badges/template-test/PublicLastTestDate.svg)*'; + $actualContent; + } + $result | Should -Not -BeNullOrEmpty; + } + finally { + Pop-Location; + } + } + } +} diff --git a/tests/PSDocs.Azure.Tests/template-test/template.json b/tests/PSDocs.Azure.Tests/template-test/template.json new file mode 100644 index 0000000..8173a2c --- /dev/null +++ b/tests/PSDocs.Azure.Tests/template-test/template.json @@ -0,0 +1,366 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "name": "Storage Account", + "description": "Create or update a Storage Account." + }, + "parameters": { + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Storage Account." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The Azure region to deploy to.", + "example": "EastUS", + "ignore": true + } + }, + "sku": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS" + ], + "metadata": { + "description": "Optional. Create the Storage Account as LRS or GRS." + } + }, + "suffixLength": { + "type": "int", + "defaultValue": 0, + "minValue": 0, + "maxValue": 13, + "metadata": { + "description": "Optional. Determine how many additional characters are added to the storage account name as a suffix.", + "ignore": true + } + }, + "containers": { + "type": "array", + "defaultValue": [ + ], + "metadata": { + "description": "Optional. An array of storage containers to create on the storage account.", + "example": [ + { + "name": "logs", + "publicAccess": "None", + "metadata": {} + } + ] + } + }, + "lifecycleRules": { + "type": "array", + "defaultValue": [ + ], + "metadata": { + "description": "Optional. An array of lifecycle management policies for the storage account.", + "example": { + "enabled": true, + "name": "", + "type": "Lifecycle", + "definition": { + "actions": { + "baseBlob": { + "delete": { + "daysAfterModificationGreaterThan": 7 + } + } + }, + "filters": { + "blobTypes": [ + "blockBlob" + ], + "prefixMatch": [ + "logs/" + ] + } + } + } + } + }, + "blobSoftDeleteDays": { + "type": "int", + "defaultValue": 0, + "minValue": 0, + "maxValue": 365, + "metadata": { + "description": "Optional. The number of days to retain deleted blobs. When set to 0, soft delete is disabled.", + "example": 7 + } + }, + "containerSoftDeleteDays": { + "type": "int", + "defaultValue": 0, + "minValue": 0, + "maxValue": 365, + "metadata": { + "description": "Optional. The number of days to retain deleted containers. When set to 0, soft delete is disabled.", + "example": 7 + } + }, + "shares": { + "type": "array", + "defaultValue": [ + ], + "metadata": { + "description": "Optional. An array of file shares to create on the storage account.", + "example": [ + { + "name": "", + "shareQuota": 5, + "metadata": {} + } + ] + } + }, + "useLargeFileShares": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Determines if large file shares are enabled. This can not be disabled once enabled.", + "ignore": true + } + }, + "shareSoftDeleteDays": { + "type": "int", + "defaultValue": 0, + "minValue": 0, + "maxValue": 365, + "metadata": { + "description": "Optional. The number of days to retain deleted shares. When set to 0, soft delete is disabled.", + "example": 7 + } + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Determines if any containers can be configured with the anonymous access types of blob or container." + } + }, + "keyVaultPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Set to the objectId of Azure Key Vault to delegated permission for use with Key Managed Storage Accounts.", + "ignore": true + } + }, + "tags": { + "type": "object", + "defaultValue": { + }, + "metadata": { + "description": "Optional. Tags to apply to the resource.", + "example": { + "service": "", + "env": "prod" + } + } + } + }, + "variables": { + "storageAccountName": "[concat(parameters('storageAccountName'), if(greater(parameters('suffixLength'), 0), substring(uniqueString(resourceGroup().id), 0, parameters('suffixLength')), ''))]", + "blobSoftDeleteLookup": { + "true": { + "enabled": true, + "days": "[parameters('blobSoftDeleteDays')]" + }, + "false": { + "enabled": false + } + }, + "containerSoftDeleteLookup": { + "true": { + "enabled": true, + "days": "[parameters('containerSoftDeleteDays')]" + }, + "false": null + }, + "shareSoftDeleteLookup": { + "true": { + "enabled": true, + "days": "[parameters('shareSoftDeleteDays')]" + }, + "false": { + "enabled": false + } + }, + "largeFileSharesState": "[if(parameters('useLargeFileShares'), 'Enabled', 'Disabled')]", + "storageAccountKeyOperatorRoleId": "[resourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]" + }, + "resources": [ + { + "comments": "Storage Account", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "name": "[variables('storageAccountName')]", + "location": "[parameters('location')]", + "sku": { + "name": "[parameters('sku')]", + "tier": "Standard" + }, + "kind": "StorageV2", + "properties": { + "networkAcls": { + "bypass": "AzureServices", + "virtualNetworkRules": [ + ], + "ipRules": [ + ], + "defaultAction": "Allow" + }, + "supportsHttpsTrafficOnly": true, + "encryption": { + "services": { + "file": { + "enabled": true + }, + "blob": { + "enabled": true + } + }, + "keySource": "Microsoft.Storage" + }, + "accessTier": "Hot", + "largeFileSharesState": "[variables('largeFileSharesState')]", + "allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]", + "minimumTlsVersion": "TLS1_2" + }, + "tags": "[parameters('tags')]", + "resources": [ + { + "comments": "Configure blob storage services", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2019-06-01", + "name": "[concat(variables('storageAccountName'), '/default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "sku": { + "name": "[parameters('sku')]" + }, + "properties": { + "cors": { + "corsRules": [ + ] + }, + "deleteRetentionPolicy": "[variables('blobSoftDeleteLookup')[string(greater(parameters('blobSoftDeleteDays'), 0))]]", + "containerDeleteRetentionPolicy": "[variables('containerSoftDeleteLookup')[string(greater(parameters('containerSoftDeleteDays'), 0))]]" + } + }, + { + "comments": "Configure file storage services", + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2019-06-01", + "name": "[concat(variables('storageAccountName'), '/default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "sku": { + "name": "[parameters('sku')]" + }, + "properties": { + "shareDeleteRetentionPolicy": "[variables('shareSoftDeleteLookup')[string(greater(parameters('shareSoftDeleteDays'), 0))]]" + } + } + ] + }, + { + "comments": "Create a blob container", + "condition": "[not(equals(length(parameters('containers')), 0))]", + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2019-06-01", + "name": "[if(equals(length(parameters('containers')), 0), concat(variables('storageAccountName'), '/default/empty'), concat(variables('storageAccountName'), '/default/', parameters('containers')[copyIndex('containerIndex')].name))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('storageAccountName'), 'default')]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "copy": { + "mode": "Parallel", + "count": "[if(equals(length(parameters('containers')), 0), 1, length(parameters('containers')))]", + "name": "containerIndex" + }, + "properties": { + "metadata": "[parameters('containers')[copyIndex('containerIndex')].metadata]", + "publicAccess": "[parameters('containers')[copyIndex('containerIndex')].publicAccess]" + } + }, + { + "comments": "Create blob lifecycle policy", + "condition": "[not(empty(parameters('lifecycleRules')))]", + "name": "[concat(variables('storageAccountName'), '/default')]", + "type": "Microsoft.Storage/storageAccounts/managementPolicies", + "apiVersion": "2019-06-01", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "properties": { + "policy": { + "rules": "[parameters('lifecycleRules')]" + } + } + }, + { + "comments": "Create a share", + "condition": "[not(equals(length(parameters('shares')), 0))]", + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2019-06-01", + "name": "[if(equals(length(parameters('shares')), 0), concat(variables('storageAccountName'), '/default/empty'), concat(variables('storageAccountName'), '/default/', parameters('shares')[copyIndex('shareIndex')].name))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('storageAccountName'), 'default')]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "copy": { + "mode": "Parallel", + "count": "[if(equals(length(parameters('shares')), 0), 1, length(parameters('shares')))]", + "name": "shareIndex" + }, + "properties": { + "metadata": "[parameters('shares')[copyIndex('shareIndex')].metadata]", + "shareQuota": "[parameters('shares')[copyIndex('shareIndex')].shareQuota]" + } + }, + { + "comments": "Delegate Key Vault permission to rotate keys", + "condition": "[not(empty(parameters('keyVaultPrincipalId')))]", + "type": "Microsoft.Storage/storageAccounts/providers/roleAssignments", + "apiVersion": "2018-09-01-preview", + "name": "[concat(variables('storageAccountName'), '/Microsoft.Authorization/', guid(parameters('keyVaultPrincipalId'), variables('storageAccountKeyOperatorRoleId')))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "properties": { + "roleDefinitionId": "[variables('storageAccountKeyOperatorRoleId')]", + "principalId": "[parameters('keyVaultPrincipalId')]", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", + "principalType": "ServicePrincipal" + } + } + ], + "outputs": { + "blobEndpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').primaryEndpoints.blob]", + "metadata": { + "description": "A URI to the blob storage endpoint." + } + }, + "resourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", + "metadata": { + "description": "A unique resource identifier for the storage account." + } + } + } +}