diff --git a/.azuredevops/modulePipelines/ms.resources.tags.yml b/.azuredevops/modulePipelines/ms.resources.tags.yml new file mode 100644 index 0000000000..9b33b7cf86 --- /dev/null +++ b/.azuredevops/modulePipelines/ms.resources.tags.yml @@ -0,0 +1,53 @@ +name: 'Resources - Tags' + +parameters: + - name: removeDeployment + displayName: Remove deployed module + type: boolean + default: true + - name: prerelease + displayName: Publish prerelease module + type: boolean + default: false + +trigger: + batch: true + branches: + include: + - main + paths: + include: + - '/.azuredevops/modulePipelines/ms.resources.tags.yml' + - '/.azuredevops/pipelineTemplates/module.*.yml' + - '/arm/Microsoft.Resources/tags/*' + exclude: + - '/**/*.md' + +variables: + - template: '/.azuredevops/pipelineVariables/global.variables.yml' + - group: 'PLATFORM_VARIABLES' + - name: modulePath + value: '/arm/Microsoft.Resources/tags' + +stages: + - stage: Validation + displayName: Pester tests + jobs: + - template: /.azuredevops/pipelineTemplates/jobs.validateModulePester.yml + + - stage: Deployment + displayName: Deployment tests + jobs: + - template: /.azuredevops/pipelineTemplates/jobs.validateModuleDeployment.yml + parameters: + removeDeployment: '${{ parameters.removeDeployment }}' + deploymentBlocks: + - path: $(modulePath)/.parameters/min.parameters.json + - path: $(modulePath)/.parameters/rg.parameters.json + - path: $(modulePath)/.parameters/sub.parameters.json + + - stage: Publishing + displayName: Publish module + condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq('${{ parameters.prerelease }}', 'true'))) + jobs: + - template: /.azuredevops/pipelineTemplates/jobs.publishModule.yml diff --git a/.github/workflows/ms.resources.tags.yml b/.github/workflows/ms.resources.tags.yml new file mode 100644 index 0000000000..e00eabdacc --- /dev/null +++ b/.github/workflows/ms.resources.tags.yml @@ -0,0 +1,136 @@ +name: 'Resources: Tags' + +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.resources.tags.yml' + - 'arm/Microsoft.Resources/tags/**' + - '!*/**/readme.md' + - 'utilities/pipelines/**' + - '!utilities/pipelines/dependencies/**' + +env: + modulePath: 'arm/Microsoft.Resources/tags' + workflowPath: '.github/workflows/ms.resources.tags.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 }}' + DEPLOYMENT_SP_ID: '${{ secrets.DEPLOYMENT_SP_ID }}' + +jobs: + ############################ + # SET INPUT PARAMETERS # + ############################ + job_set_workflow_param: + runs-on: ubuntu-20.04 + name: 'Set input parameters to output variables' + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: 'Set input parameters' + id: get-workflow-param + uses: ./.github/actions/templates/getWorkflowInput + with: + workflowPath: '${{ env.workflowPath}}' + outputs: + removeDeployment: ${{ steps.get-workflow-param.outputs.removeDeployment }} + + #################### + # Pester Tests # + #################### + job_module_pester_validation: + runs-on: ubuntu-20.04 + name: 'Pester tests' + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: 'Run tests' + uses: ./.github/actions/templates/validateModulePester + with: + modulePath: '${{ env.modulePath }}' + + #################### + # Deployment tests # + #################### + job_module_deploy_validation: + runs-on: ubuntu-20.04 + name: 'Deployment tests' + needs: + - job_set_workflow_param + - job_module_pester_validation + strategy: + fail-fast: false + matrix: + parameterFilePaths: + ['min.parameters.json', 'rg.parameters.json', 'sub.parameters.json'] + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set environment variables + uses: deep-mm/set-variables@v1.0 + with: + variableFileName: 'global.variables' + - name: 'Using parameter file [${{ matrix.parameterFilePaths }}]' + uses: ./.github/actions/templates/validateModuleDeployment + with: + templateFilePath: '${{ env.modulePath }}/deploy.bicep' + parameterFilePath: '${{ env.modulePath }}/.parameters/${{ matrix.parameterFilePaths }}' + location: '${{ env.defaultLocation }}' + resourceGroupName: '${{ env.resourceGroupName }}' + subscriptionId: '${{ secrets.ARM_SUBSCRIPTION_ID }}' + managementGroupId: '${{ secrets.ARM_MGMTGROUP_ID }}' + removeDeployment: '${{ needs.job_set_workflow_param.outputs.removeDeployment }}' + + ############### + # PUBLISH # + ############### + job_publish_module: + name: 'Publish module' + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event.inputs.prerelease == 'true' + runs-on: ubuntu-20.04 + needs: + - job_set_workflow_param + - job_module_deploy_validation + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set environment variables + uses: deep-mm/set-variables@v1.0 + with: + variableFileName: 'global.variables' + - name: 'Publish module' + 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/arm/Microsoft.Resources/tags/.parameters/min.parameters.json b/arm/Microsoft.Resources/tags/.parameters/min.parameters.json new file mode 100644 index 0000000000..d90c44f3fb --- /dev/null +++ b/arm/Microsoft.Resources/tags/.parameters/min.parameters.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": {} +} diff --git a/arm/Microsoft.Resources/tags/.parameters/rg.parameters.json b/arm/Microsoft.Resources/tags/.parameters/rg.parameters.json new file mode 100644 index 0000000000..a90e2e5b2c --- /dev/null +++ b/arm/Microsoft.Resources/tags/.parameters/rg.parameters.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "onlyUpdate": { + "value": false + }, + "tags": { + "value": { + "Test": "Yes", + "TestToo": "No" + } + }, + "resourceGroupName": { + "value": "validation-rg" + } + } +} diff --git a/arm/Microsoft.Resources/tags/.parameters/sub.parameters.json b/arm/Microsoft.Resources/tags/.parameters/sub.parameters.json new file mode 100644 index 0000000000..840b23ba68 --- /dev/null +++ b/arm/Microsoft.Resources/tags/.parameters/sub.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "onlyUpdate": { + "value": true + }, + "tags": { + "value": { + "Test": "Yes", + "TestToo": "No" + } + } + } +} diff --git a/arm/Microsoft.Resources/tags/deploy.bicep b/arm/Microsoft.Resources/tags/deploy.bicep new file mode 100644 index 0000000000..4b3d6b791a --- /dev/null +++ b/arm/Microsoft.Resources/tags/deploy.bicep @@ -0,0 +1,36 @@ +targetScope = 'subscription' + +@description('Optional. Tags for the resource group. If not provided, removes existing tags') +param tags object = {} + +@description('Optional. Instead of overwriting the existing tags, combine them with the new tags') +param onlyUpdate bool = false + +@description('Optional. Name of the Resource Group to assign the tags to. If no Resource Group name is provided, and Subscription ID is provided, the module deploys at subscription level, therefore assigns the provided tags to the subscription.') +param resourceGroupName string = '' + +@description('Optional. Subscription ID of the subscription to assign the tags to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided tags to the subscription.') +param subscriptionId string = subscription().id + +module tags_sub 'subscriptions/deploy.bicep' = if (!empty(subscriptionId) && empty(resourceGroupName)) { + name: '${deployment().name}-Tags-Sub' + params: { + onlyUpdate: onlyUpdate + tags: tags + } +} + +module tags_rg 'resourceGroups/deploy.bicep' = if (!empty(resourceGroupName) && !empty(subscriptionId)) { + name: '${deployment().name}-Tags-RG' + scope: resourceGroup(resourceGroupName) + params: { + onlyUpdate: onlyUpdate + tags: tags + } +} + +@description('The name of the tags resource') +output name string = (!empty(resourceGroupName) && !empty(subscriptionId)) ? tags_rg.outputs.name : tags_sub.outputs.name + +@description('The applied tags') +output tags object = (!empty(resourceGroupName) && !empty(subscriptionId)) ? tags_rg.outputs.tags : tags_sub.outputs.tags diff --git a/arm/Microsoft.Resources/tags/readme.md b/arm/Microsoft.Resources/tags/readme.md new file mode 100644 index 0000000000..6d74aff75b --- /dev/null +++ b/arm/Microsoft.Resources/tags/readme.md @@ -0,0 +1,46 @@ +# Resources Tags `[Microsoft.Resources/tags]` + +This module deploys Resources Tags on a subscription or resource group scope. + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Resources/tags` | 2019-10-01 | + +## Parameters + +| Parameter Name | Type | Default Value | Possible Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `onlyUpdate` | bool | | | Optional. Instead of overwriting the existing tags, combine them with the new tags | +| `resourceGroupName` | string | | | Optional. Name of the Resource Group to assign the tags to. If no Resource Group name is provided, and Subscription ID is provided, the module deploys at subscription level, therefore assigns the provided tags to the subscription. | +| `subscriptionId` | string | `[subscription().id]` | | Optional. Subscription ID of the subscription to assign the tags to. If no Resource Group name is provided, the module deploys at subscription level, therefore assigns the provided tags to the subscription. | +| `tags` | object | `{object}` | | Optional. Tags for the resource group. If not provided, removes existing tags | + +### Parameter Usage: `tags` + +Tag names and tag values can be provided as needed. A tag can be left without a value. + +```json +"tags": { + "value": { + "Environment": "Non-Prod", + "Contact": "test.user@testcompany.com", + "PurchaseOrder": "1234", + "CostCenter": "7890", + "ServiceName": "DeploymentValidation", + "Role": "DeploymentValidation" + } +} +``` + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the tags resource | +| `tags` | object | The applied tags | + +## Template references + +- [Tags](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Resources/2019-10-01/tags) diff --git a/arm/Microsoft.Resources/tags/resourceGroups/.bicep/readTags.bicep b/arm/Microsoft.Resources/tags/resourceGroups/.bicep/readTags.bicep new file mode 100644 index 0000000000..0f3301f974 --- /dev/null +++ b/arm/Microsoft.Resources/tags/resourceGroups/.bicep/readTags.bicep @@ -0,0 +1,9 @@ +@description('Optional. The name of the tags resource.') +param name string = 'default' + +resource tags 'Microsoft.Resources/tags@2019-10-01' existing = { + name: name +} + +@description('Tags currently applied to the subscription level') +output existingTags object = contains(tags.properties, 'tags') ? tags.properties.tags : {} diff --git a/arm/Microsoft.Resources/tags/resourceGroups/deploy.bicep b/arm/Microsoft.Resources/tags/resourceGroups/deploy.bicep new file mode 100644 index 0000000000..3c06c2b2a5 --- /dev/null +++ b/arm/Microsoft.Resources/tags/resourceGroups/deploy.bicep @@ -0,0 +1,33 @@ +@description('Optional. Tags for the resource group. If not provided, removes existing tags') +param tags object = {} + +@description('Optional. The name of the tags resource.') +param name string = 'default' + +@description('Optional. Instead of overwriting the existing tags, combine them with the new tags') +param onlyUpdate bool = false + +module readTags '.bicep/readTags.bicep' = if (onlyUpdate) { + name: '${deployment().name}-ReadTags' +} + +var newTags = (onlyUpdate) ? union(readTags.outputs.existingTags, tags) : tags + +resource tag 'Microsoft.Resources/tags@2019-10-01' = { + name: name + properties: { + tags: newTags + } +} + +@description('The name of the tags resource') +output name string = tag.name + +@description('The resourceId of the resource group the tags were applied to') +output resourceId string = resourceGroup().id + +@description('The name of the resource group the tags were applied to') +output resourceGroupName string = resourceGroup().name + +@description('The applied tags') +output tags object = newTags diff --git a/arm/Microsoft.Resources/tags/resourceGroups/readme.md b/arm/Microsoft.Resources/tags/resourceGroups/readme.md new file mode 100644 index 0000000000..de64be520d --- /dev/null +++ b/arm/Microsoft.Resources/tags/resourceGroups/readme.md @@ -0,0 +1,47 @@ +# Resources Tags ResourceGroups `[Microsoft.Resources/tags/resourceGroups]` + +This module deploys Resources Tags on a resource group scope. + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Resources/tags` | 2019-10-01 | + +## Parameters + +| Parameter Name | Type | Default Value | Possible Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `name` | string | `default` | | Optional. The name of the tags resource. | +| `onlyUpdate` | bool | | | Optional. Instead of overwriting the existing tags, combine them with the new tags | +| `tags` | object | `{object}` | | Optional. Tags for the resource group. If not provided, removes existing tags | + +### Parameter Usage: `tags` + +Tag names and tag values can be provided as needed. A tag can be left without a value. + +```json +"tags": { + "value": { + "Environment": "Non-Prod", + "Contact": "test.user@testcompany.com", + "PurchaseOrder": "1234", + "CostCenter": "7890", + "ServiceName": "DeploymentValidation", + "Role": "DeploymentValidation" + } +} +``` + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the tags resource | +| `resourceGroupName` | string | The name of the resource group the tags were applied to | +| `resourceId` | string | The resourceId of the resource group the tags were applied to | +| `tags` | object | The applied tags | + +## Template references + +- [Tags](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Resources/2019-10-01/tags) diff --git a/arm/Microsoft.Resources/tags/resourceGroups/version.json b/arm/Microsoft.Resources/tags/resourceGroups/version.json new file mode 100644 index 0000000000..41f66cc990 --- /dev/null +++ b/arm/Microsoft.Resources/tags/resourceGroups/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.1" +} diff --git a/arm/Microsoft.Resources/tags/subscriptions/.bicep/readTags.bicep b/arm/Microsoft.Resources/tags/subscriptions/.bicep/readTags.bicep new file mode 100644 index 0000000000..65b2457259 --- /dev/null +++ b/arm/Microsoft.Resources/tags/subscriptions/.bicep/readTags.bicep @@ -0,0 +1,11 @@ +targetScope = 'subscription' + +@description('Optional. The name of the tags resource.') +param name string = 'default' + +resource tags 'Microsoft.Resources/tags@2019-10-01' existing = { + name: name +} + +@description('Tags currently applied to the subscription level') +output existingTags object = contains(tags.properties, 'tags') ? tags.properties.tags : {} diff --git a/arm/Microsoft.Resources/tags/subscriptions/deploy.bicep b/arm/Microsoft.Resources/tags/subscriptions/deploy.bicep new file mode 100644 index 0000000000..37f4759a8e --- /dev/null +++ b/arm/Microsoft.Resources/tags/subscriptions/deploy.bicep @@ -0,0 +1,29 @@ +targetScope = 'subscription' + +@description('Optional. Tags for the resource group. If not provided, removes existing tags') +param tags object = {} + +@description('Optional. The name of the tags resource.') +param name string = 'default' + +@description('Optional. Instead of overwriting the existing tags, combine them with the new tags') +param onlyUpdate bool = false + +module readTags '.bicep/readTags.bicep' = if (onlyUpdate) { + name: '${deployment().name}-ReadTags' +} + +var newTags = (onlyUpdate) ? union(readTags.outputs.existingTags, tags) : tags + +resource tag 'Microsoft.Resources/tags@2019-10-01' = { + name: name + properties: { + tags: newTags + } +} + +@description('The name of the tags resource') +output name string = tag.name + +@description('The applied tags') +output tags object = newTags diff --git a/arm/Microsoft.Resources/tags/subscriptions/readme.md b/arm/Microsoft.Resources/tags/subscriptions/readme.md new file mode 100644 index 0000000000..fe75bc2b3c --- /dev/null +++ b/arm/Microsoft.Resources/tags/subscriptions/readme.md @@ -0,0 +1,45 @@ +# Resources Tags Subscriptions `[Microsoft.Resources/tags/subscriptions]` + +This module deploys Resources Tags on a subscription scope. + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Resources/tags` | 2019-10-01 | + +## Parameters + +| Parameter Name | Type | Default Value | Possible Values | Description | +| :-- | :-- | :-- | :-- | :-- | +| `name` | string | `default` | | Optional. The name of the tags resource. | +| `onlyUpdate` | bool | | | Optional. Instead of overwriting the existing tags, combine them with the new tags | +| `tags` | object | `{object}` | | Optional. Tags for the resource group. If not provided, removes existing tags | + +### Parameter Usage: `tags` + +Tag names and tag values can be provided as needed. A tag can be left without a value. + +```json +"tags": { + "value": { + "Environment": "Non-Prod", + "Contact": "test.user@testcompany.com", + "PurchaseOrder": "1234", + "CostCenter": "7890", + "ServiceName": "DeploymentValidation", + "Role": "DeploymentValidation" + } +} +``` + +## Outputs + +| Output Name | Type | Description | +| :-- | :-- | :-- | +| `name` | string | The name of the tags resource | +| `tags` | object | The applied tags | + +## Template references + +- [Tags](https://docs.microsoft.com/en-us/azure/templates/Microsoft.Resources/2019-10-01/tags) diff --git a/arm/Microsoft.Resources/tags/subscriptions/version.json b/arm/Microsoft.Resources/tags/subscriptions/version.json new file mode 100644 index 0000000000..41f66cc990 --- /dev/null +++ b/arm/Microsoft.Resources/tags/subscriptions/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.1" +} diff --git a/arm/Microsoft.Resources/tags/version.json b/arm/Microsoft.Resources/tags/version.json new file mode 100644 index 0000000000..41f66cc990 --- /dev/null +++ b/arm/Microsoft.Resources/tags/version.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.1" +}