├── completions.gif ├── examples ├── network-interface │ ├── parameters.json │ ├── network-interface.psarm.ps1 │ └── template.json ├── simple-test │ ├── parameters.json │ ├── simple-test.psarm.ps1 │ └── template.json ├── simple-storage-account │ ├── parameters.json │ ├── storage-account.psarm.ps1 │ └── template.json ├── windows-vm │ └── parameters.json ├── linux-vm │ └── parameters.json ├── wvd-backplane │ ├── wvd-backplane.parameters.json │ ├── wvd-backplane.psarm.ps1 │ └── template.json └── simple-parameterless │ ├── Parameterless.PSArm.ps1 │ └── template.json ├── .gitignore ├── .azure-pipelines ├── templates │ ├── steps │ │ ├── publishToGitHub.yaml │ │ ├── logArtifacts.yaml │ │ ├── build.yaml │ │ ├── publishToGallery.yaml │ │ ├── download.yaml │ │ ├── tests.yaml │ │ ├── ciCompliance.yaml │ │ ├── pester.yaml │ │ ├── releaseCompliance.yaml │ │ └── codeSigning.yaml │ └── buildAndTest.yaml ├── ci.yaml └── release.yaml ├── src ├── Types │ ├── SecureObject.cs │ ├── ArmExpressionConverter.cs │ ├── UnsafeCompilerServicesLoadHandler.cs │ ├── ArmElementConverter.cs │ ├── BicepArmTypeVisitor.cs │ ├── ArmBuiltinTypeExtensions.cs │ ├── ArmTypeConversion.cs │ ├── ArmStringConverter.cs │ └── ArmTypeAccelerators.cs ├── Templates │ ├── Visitors │ │ ├── VisitAction.cs │ │ └── IArmVisitor.cs │ ├── Primitives │ │ ├── IArmElement.cs │ │ ├── IArmString.cs │ │ ├── ArmExpression.cs │ │ ├── ArmEntry.cs │ │ ├── ArmNullLiteral.cs │ │ ├── ArmIntegerLiteral.cs │ │ ├── ArmDoubleLiteral.cs │ │ ├── ArmStringLiteral.cs │ │ ├── ArmBooleanLiteral.cs │ │ ├── ArmElement.cs │ │ └── ArmLiteral.cs │ ├── Builders │ │ ├── ConstructingArmBuilder.cs │ │ ├── ArmNestedTemplateBuilder.cs │ │ └── ArmBuilder.cs │ ├── IArmReferenceable.cs │ ├── Metadata │ │ ├── ArmMetadata.cs │ │ ├── PSArmTopLevelTemplateMetadata.cs │ │ ├── ArmGeneratorMetadata.cs │ │ └── PSArmGeneratorMetadata.cs │ ├── Operations │ │ ├── ArmVariableReferenceExpression.cs │ │ ├── ArmReferenceExpression.cs │ │ ├── ArmParameterReferenceExpression.cs │ │ ├── ArmOperation.cs │ │ ├── ArmFunctionCallExpression.cs │ │ ├── ArmIndexAccessExpression.cs │ │ └── ArmMemberAccessExpression.cs │ ├── ArmOutput.cs │ ├── ArmVariable.cs │ ├── ArmSku.cs │ ├── ArmTemplateResource.cs │ ├── ArmNestedTemplate.cs │ ├── ArmResource.cs │ └── ArmParameter.cs ├── Commands │ ├── Internal │ │ ├── ModuleConstants.cs │ │ ├── CmdletExtensions.cs │ │ ├── VerboseHttpLoggingHandler.cs │ │ └── HashtableTransformationAttribute.cs │ ├── Primitive │ │ ├── NewPSArmFunctionCallCommand.cs │ │ ├── NewPSArmElementCommand.cs │ │ ├── NewPSArmArrayCommand.cs │ │ └── NewPSArmEntryCommand.cs │ ├── Template │ │ ├── NewPSArmOutputCommand.cs │ │ ├── NewPSArmSkuCommand.cs │ │ ├── NewPSArmDependsOnCommand.cs │ │ └── NewPSArmTemplateCommand.cs │ └── ConvertFromArmTemplateCommand.cs ├── Schema │ ├── SchemaTest.cs │ ├── Keyword │ │ ├── OpenKeywordSchema.cs │ │ ├── BicepKeywordSchema.cs │ │ ├── DslParameterInfo.cs │ │ ├── DslKeywordSchema.cs │ │ ├── StaticKeywordSchema.cs │ │ ├── ResourceKeywordCache.cs │ │ ├── ObjectResourceKeywordCache.cs │ │ ├── KnownParametersSchema.cs │ │ ├── PSArmSchemaInformation.cs │ │ ├── BicepObjectKeywordSchema.cs │ │ ├── BicepKeywordSchemaBuilder.cs │ │ └── DiscriminatedResourceKeywordCache.cs │ ├── ResourceDslDefinition.cs │ └── ArmResourceName.cs ├── Internal │ ├── ArmStringExtensions.cs │ ├── ListDictionaryExtensions.cs │ ├── HashCodeHelpers.cs │ ├── ArmExpressionTokenExtensions.cs │ ├── ReadOnlyDictionaryExtensions.cs │ ├── StringExtensions.cs │ └── KeywordPowerShellParameterDiscovery.cs ├── Execution │ └── TemplateExecutionException.cs ├── PSArm.csproj ├── Serialization │ └── ArmBuiltinFunction.cs ├── Parameterization │ ├── PowerShellArmVariableConstructor.cs │ ├── ReferenceCollectingPSAstVisitor.cs │ ├── PSArmParameterization.cs │ ├── ReferenceCollectingArmVisitor.cs │ └── TemplateReferenceCollector.cs ├── OnImport.ps1 └── Completion │ └── ArmResourceArgumentCompleter.cs ├── tools ├── ensureInvokeBuildInstalled.ps1 ├── BuildHelper.psm1 ├── release │ └── UserExclusions.xml └── addCopyrightHeaders.ps1 ├── test ├── pester │ ├── TypeAccelerator.Tests.ps1 │ ├── Metadata.Tests.ps1 │ ├── Conversion.Tests.ps1 │ ├── Expression.Tests.ps1 │ ├── assets │ │ └── roundtrip-template.json │ └── Example.Tests.ps1 └── tools │ ├── runPesterTests.ps1 │ └── TestHelper.psm1 ├── README.md ├── LICENSE ├── docs ├── New-PSArmDependsOn.md ├── New-PSArmFunctionCall.md ├── New-PSArmOutput.md ├── New-PSArmTemplate.md ├── New-PSArmSku.md ├── ConvertTo-PSArm.md └── New-PSArmResource.md ├── PSArm.sln ├── ThirdPartyNotices.txt └── .vscode └── launch.json /completions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/PSArm/HEAD/completions.gif -------------------------------------------------------------------------------- /examples/network-interface/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "rgLocation": "WestUS2" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | *.dtbcache.v2 5 | out/ 6 | testResults.xml 7 | coverage.xml 8 | -------------------------------------------------------------------------------- /examples/simple-test/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "storageAccountName": "psarmtest", 3 | "location": "westus2" 4 | } 5 | -------------------------------------------------------------------------------- /examples/simple-storage-account/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "StorageAccountName": "MyStorageAccount", 3 | "allowPublicAccess": 1 4 | } 5 | -------------------------------------------------------------------------------- /examples/windows-vm/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "adminUsername": "banana", 3 | "SubnetName": "MySubnet", 4 | "OSVersion": "2016-Datacenter" 5 | } 6 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/publishToGitHub.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | 3 | - pwsh: | 4 | Write-Host "Publishing to GitHub!" 5 | displayName: 'Publish GitHub release' 6 | -------------------------------------------------------------------------------- /examples/linux-vm/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AdminUsername": "test", 3 | "AuthenticationType": "password", 4 | "AdminPasswordOrKey": "notarealpassword" 5 | } 6 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/logArtifacts.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | 3 | - pwsh: | 4 | tree /f /a '$(Build.ArtifactStagingDirectory)' 5 | displayName: Show artifacts directory 6 | -------------------------------------------------------------------------------- /examples/wvd-backplane/wvd-backplane.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostpoolName": "myFirstHostpool", 3 | "appgroupName": "myFirstAppGroup", 4 | "workspaceName": "myFirstWorkspace" 5 | } 6 | -------------------------------------------------------------------------------- /src/Types/SecureObject.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | 6 | namespace PSArm.Types 7 | { 8 | public sealed class SecureObject 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tools/ensureInvokeBuildInstalled.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | if (Get-Command Invoke-Build -ErrorAction Ignore) 6 | { 7 | return 8 | } 9 | 10 | Install-Module InvokeBuild -Force -Scope CurrentUser -------------------------------------------------------------------------------- /src/Templates/Visitors/VisitAction.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace PSArm.Templates.Visitors 5 | { 6 | public enum VisitAction 7 | { 8 | Continue, 9 | SkipChildren, 10 | Stop, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/build.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | 3 | - pwsh: | 4 | ./tools/ensureInvokeBuildInstalled.ps1 5 | Invoke-Build Build 6 | displayName: Build Module 7 | 8 | - publish: '$(Build.SourcesDirectory)/out' 9 | artifact: unsigned 10 | displayName: Publish unsigned artifacts 11 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/publishToGallery.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | 3 | - template: download.yaml 4 | parameters: 5 | artifact: signed 6 | 7 | - pwsh: | 8 | Import-Module "$(Build.SourcesDirectory)/tools/release/ReleaseTools.psm1" 9 | Publish-Module -ModulePath "$(Build.ArtifactStagingDirectory)/signed/PSArm" 10 | displayName: 'Publish to PSGallery' 11 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/download.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | 3 | - name: artifact 4 | type: string 5 | 6 | steps: 7 | 8 | - task: DownloadPipelineArtifact@2 9 | displayName: 'Download artifact: ${{ parameters.artifact }}' 10 | inputs: 11 | artifact: ${{ parameters.artifact }} 12 | path: '$(Build.ArtifactStagingDirectory)/${{ parameters.artifact }}' 13 | -------------------------------------------------------------------------------- /src/Templates/Primitives/IArmElement.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace PSArm.Templates.Primitives 8 | { 9 | public interface IArmElement 10 | { 11 | IArmElement Instantiate(IReadOnlyDictionary parameters); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/tests.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | 3 | - name: useWinPS 4 | type: boolean 5 | default: false 6 | 7 | steps: 8 | 9 | - template: download.yaml 10 | parameters: 11 | artifact: unsigned 12 | 13 | - template: pester.yaml 14 | parameters: 15 | useWinPS: ${{ parameters.useWinPS }} 16 | psArmPath: '$(Build.ArtifactStagingDirectory)/unsigned/PSArm' 17 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/ciCompliance.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | 3 | - checkout: ComplianceRepo 4 | displayName: 'Checkout the ComplianceRepo' 5 | 6 | - checkout: self 7 | 8 | - template: download.yaml 9 | parameters: 10 | artifact: unsigned 11 | 12 | - template: logArtifacts.yaml 13 | 14 | - template: ci-compliance.yml@ComplianceRepo 15 | parameters: 16 | # CredScan 17 | suppressionsFile: '' 18 | -------------------------------------------------------------------------------- /src/Templates/Builders/ConstructingArmBuilder.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | 7 | namespace PSArm.Templates.Builders 8 | { 9 | public class ConstructingArmBuilder : ArmBuilder where T : ArmObject, new() 10 | { 11 | public ConstructingArmBuilder() : base(new T()) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Templates/IArmReferenceable.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | 7 | namespace PSArm.Templates 8 | { 9 | public interface IArmReferenceable 10 | { 11 | IArmString ReferenceName { get; } 12 | } 13 | 14 | public interface IArmReferenceable : IArmReferenceable 15 | { 16 | TReference GetReference(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tools/BuildHelper.psm1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | filter Write-Log 6 | { 7 | param( 8 | [Parameter(ValueFromPipeline)] 9 | [string[]] 10 | $Message 11 | ) 12 | 13 | $Message | Write-Host 14 | } 15 | 16 | function Unsplat 17 | { 18 | param([hashtable]$SplatParams) 19 | 20 | ($SplatParams.GetEnumerator() | ForEach-Object { $name = $_.Key; $value = $_.Value; "-$name $value" }) -join " " 21 | } 22 | -------------------------------------------------------------------------------- /src/Commands/Internal/ModuleConstants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace PSArm.Commands.Internal 5 | { 6 | /// 7 | /// Constants to be used by PSArm commands 8 | /// 9 | internal static class ModuleConstants 10 | { 11 | /// 12 | /// The command prefix for the PSArm module 13 | /// 14 | public const string ModulePrefix = "PSArm"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/pester/TypeAccelerator.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | Describe "PSArm type accelerators" { 5 | It "Defines an ArmVariable type accelerator" { 6 | [ArmVariable].FullName | Should -BeExactly 'PSArm.Templates.ArmVariable' 7 | } 8 | 9 | It "Defines a generic ArmParameter type accelerator" { 10 | [ArmParameter].FullName | Should -BeExactly 'PSArm.Templates.ArmParameter`1' 11 | [ArmParameter[string]].GenericTypeArguments[0].FullName | Should -BeExactly 'System.String' 12 | } 13 | } -------------------------------------------------------------------------------- /src/Schema/SchemaTest.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | namespace PSArm.Schema 6 | { 7 | public static class SchemaTest 8 | { 9 | public static void Run() 10 | { 11 | var index = new ResourceIndex(); 12 | var factory = new PSArmDslFactory(); 13 | foreach (ResourceSchema schema in index.GetResourceSchemas()) 14 | { 15 | factory.CreateResourceDslDefinition(schema.Properties, schema.DiscriminatedSubtypes); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Internal/ArmStringExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | 7 | namespace PSArm.Internal 8 | { 9 | internal static class ArmStringExtensions 10 | { 11 | public static ArmStringLiteral CoerceToLiteral(this IArmString armString) 12 | { 13 | return (ArmStringLiteral)armString; 14 | } 15 | 16 | public static string CoerceToString(this IArmString armString) 17 | { 18 | return armString.CoerceToLiteral().Value; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Templates/Primitives/IArmString.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Visitors; 6 | using PSArm.Types; 7 | using System.ComponentModel; 8 | 9 | namespace PSArm.Templates.Primitives 10 | { 11 | [TypeConverter(typeof(ArmStringConverter))] 12 | public interface IArmString : IArmExpression 13 | { 14 | string ToExpressionString(); 15 | 16 | string ToIdentifierString(); 17 | 18 | TResult RunVisit(IArmVisitor visitor); 19 | 20 | VisitAction RunVisit(ArmTravsersingVisitor visitor); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/pester/Metadata.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe "Module and assembly metadata" { 2 | It "Should have matching module and assembly metadata" { 3 | $module = Get-Module 'PSArm' 4 | $asmVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($module.Path) 5 | 6 | $moduleVersion = "$($module.Version)" 7 | 8 | if ($module.PrivateData.PSData.Prerelease) 9 | { 10 | $prerelease = $module.PrivateData.PSData.Prerelease 11 | $moduleVersion = "$moduleVersion-$prerelease" 12 | } 13 | 14 | $asmVersion.ProductVersion | Should -BeExactly $moduleVersion 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.azure-pipelines/ci.yaml: -------------------------------------------------------------------------------- 1 | variables: 2 | # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds 3 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 4 | 5 | resources: 6 | repositories: 7 | - repository: ComplianceRepo 8 | type: github 9 | endpoint: ComplianceGHRepo 10 | name: PowerShell/Compliance 11 | 12 | stages: 13 | 14 | - template: templates/buildAndTest.yaml 15 | 16 | - stage: Compliance 17 | dependsOn: Build 18 | jobs: 19 | - job: Compliance 20 | pool: 21 | vmImage: windows-latest 22 | steps: 23 | - template: templates/steps/ciCompliance.yaml 24 | -------------------------------------------------------------------------------- /src/Commands/Internal/CmdletExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System; 6 | using System.Management.Automation; 7 | 8 | namespace PSArm.Commands.Internal 9 | { 10 | internal static class CmdletExtensions 11 | { 12 | public static void ThrowTerminatingError( 13 | this Cmdlet cmdlet, 14 | Exception e, 15 | string errorId, 16 | ErrorCategory errorCategory, 17 | object target = null) 18 | { 19 | cmdlet.ThrowTerminatingError(new ErrorRecord(e, errorId, errorCategory, target)); 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Internal/ListDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace PSArm.Internal 8 | { 9 | internal static class ListDictionaryExtensions 10 | { 11 | public static void AddToDictionaryList(this IDictionary> dictionary, TKey key, TValue value) 12 | { 13 | if (!dictionary.TryGetValue(key, out List list)) 14 | { 15 | list = new List(); 16 | dictionary[key] = list; 17 | } 18 | 19 | list.Add(value); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Internal/HashCodeHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace PSArm.Internal 5 | { 6 | internal static class HashCodeHelpers 7 | { 8 | public static int CombineHashCodes(T1 x1, T2 x2, T3 x3) 9 | { 10 | // From https://stackoverflow.com/a/34006336 11 | const int seed = 1009; 12 | const int factor = 9176; 13 | 14 | int hash = seed; 15 | hash = hash * factor + x1.GetHashCode(); 16 | hash = hash * factor + x2.GetHashCode(); 17 | hash = hash * factor + x3.GetHashCode(); 18 | return hash; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmExpression.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Types; 6 | using System.ComponentModel; 7 | 8 | namespace PSArm.Templates.Primitives 9 | { 10 | [TypeConverter(typeof(ArmExpressionConverter))] 11 | public interface IArmExpression : IArmElement 12 | { 13 | string ToInnerExpressionString(); 14 | } 15 | 16 | [TypeConverter(typeof(ArmExpressionConverter))] 17 | public abstract class ArmExpression : ArmElement, IArmExpression 18 | { 19 | public abstract string ToInnerExpressionString(); 20 | 21 | public override string ToString() => ToJsonString(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Execution/TemplateExecutionException.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System; 6 | using System.Management.Automation; 7 | 8 | namespace PSArm.Execution 9 | { 10 | public class TemplateExecutionException : Exception 11 | { 12 | public TemplateExecutionException(string message, ErrorRecord errorRecord) 13 | : base(message) 14 | { 15 | ErrorRecord = errorRecord; 16 | } 17 | 18 | public TemplateExecutionException(ErrorRecord errorRecord) 19 | { 20 | ErrorRecord = errorRecord; 21 | } 22 | 23 | public ErrorRecord ErrorRecord { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Schema/Keyword/OpenKeywordSchema.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Completion; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Schema.Keyword 9 | { 10 | internal sealed class OpenKeywordSchema : KnownParametersSchema 11 | { 12 | public OpenKeywordSchema( 13 | IReadOnlyDictionary parameters, 14 | bool useParametersForCompletions) 15 | : base(parameters, useParametersForCompletions) 16 | { 17 | } 18 | 19 | public override IReadOnlyDictionary GetInnerKeywords(KeywordContextFrame context) => null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSArm 2 | 3 | ## This project is now archived 4 | 5 | Active development on PSArm has been stopped and the project is now archived. 6 | See: [PSArm Experiment Update](https://devblogs.microsoft.com/powershell/psarm-experiment-update/). 7 | 8 | The repository will remain readonly and available under MIT license 9 | for anyone wishing to reuse the project or its components and ideas. 10 | 11 | ## Description 12 | 13 | PSArm was an experimental PowerShell module that provides a 14 | domain-specific language (DSL) embedded in PowerShell 15 | for Azure Resource Manager (ARM) templates, 16 | allowing you to use PowerShell 17 | to build ARM templates. 18 | 19 | For more information, see [the old README](./README_Old.md). 20 | -------------------------------------------------------------------------------- /src/Templates/Metadata/ArmMetadata.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Templates.Metadata 9 | { 10 | public class ArmMetadata : ArmObject 11 | { 12 | public IArmString Comments 13 | { 14 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Comments); 15 | set => this[ArmTemplateKeys.Comments] = (ArmElement)value; 16 | } 17 | 18 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 19 | => InstantiateIntoCopy(new ArmMetadata(), parameters); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmEntry.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | namespace PSArm.Templates.Primitives 6 | { 7 | public class ArmEntry 8 | { 9 | public ArmEntry(IArmString key, ArmElement value) : this(key, value, isArrayElement: false) 10 | { 11 | } 12 | 13 | public ArmEntry(IArmString key, ArmElement value, bool isArrayElement) 14 | { 15 | Key = key; 16 | Value = value; 17 | IsArrayElement = isArrayElement; 18 | } 19 | 20 | public IArmString Key { get; } 21 | 22 | public ArmElement Value { get; } 23 | 24 | public bool IsArrayElement { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Schema/Keyword/BicepKeywordSchema.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Azure.Bicep.Types.Concrete; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Schema.Keyword 9 | { 10 | internal abstract class BicepKeywordSchema : DslKeywordSchema where TBicepType : TypeBase 11 | { 12 | protected static IEnumerable BodyParameter { get; } = new[] { "Body" }; 13 | 14 | public BicepKeywordSchema(TBicepType bicepType) 15 | { 16 | BicepType = bicepType; 17 | } 18 | 19 | public override bool ShouldUseDefaultParameterCompletions => false; 20 | 21 | public TBicepType BicepType { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Schema/Keyword/DslParameterInfo.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace PSArm.Schema.Keyword 8 | { 9 | internal class DslParameterInfo 10 | { 11 | public DslParameterInfo(string name, string type, IReadOnlyList values) 12 | : this(name, type) 13 | { 14 | Values = values; 15 | } 16 | 17 | public DslParameterInfo(string name, string type) 18 | { 19 | Name = name; 20 | Type = type; 21 | } 22 | 23 | public string Name { get; } 24 | 25 | public string Type { get; } 26 | 27 | public IReadOnlyList Values { get; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Internal/ArmExpressionTokenExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Serialization; 6 | 7 | namespace PSArm.Internal 8 | { 9 | internal static class ArmExpressionTokenExtensions 10 | { 11 | public static string CoerceToString(this ArmExpressionToken token) 12 | { 13 | return ((ArmExpressionStringToken)token).Value; 14 | } 15 | 16 | public static long CoerceToLong(this ArmExpressionToken token) 17 | { 18 | return ((ArmExpressionIntegerToken)token).Value; 19 | } 20 | 21 | public static bool CoerceToBool(this ArmExpressionToken token) 22 | { 23 | return ((ArmExpressionBooleanToken)token).Value; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmNullLiteral.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Operations; 6 | using PSArm.Templates.Visitors; 7 | using PSArm.Types; 8 | using System.ComponentModel; 9 | 10 | namespace PSArm.Templates.Primitives 11 | { 12 | [TypeConverter(typeof(ArmElementConverter))] 13 | public sealed class ArmNullLiteral : ArmFunctionCallExpression 14 | { 15 | public static ArmNullLiteral Value { get; } = new ArmNullLiteral(); 16 | 17 | private ArmNullLiteral() : base(new ArmStringLiteral("json"), new [] { new ArmStringLiteral("null") }) 18 | { 19 | } 20 | 21 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitNullValue(this); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/pester.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | 3 | # Location of PSArm module 4 | - name: psArmPath 5 | type: string 6 | # If true, uses Windows PowerShell 5.1, otherwise uses PowerShell 7 7 | - name: useWinPS 8 | type: boolean 9 | default: false 10 | 11 | steps: 12 | 13 | - task: PowerShell@2 14 | displayName: 'Pester Tests' 15 | inputs: 16 | targetType: inline 17 | pwsh: ${{ not(parameters.useWinPS) }} 18 | script: | 19 | ./tools/ensureInvokeBuildInstalled.ps1 20 | Invoke-Build TestPester -RunTestsInProcess -RunTestsInCIMode -TestPSArmPath '${{ parameters.psArmPath }}' 21 | 22 | - task: PublishTestResults@2 23 | inputs: 24 | testRunner: NUnit 25 | testResultsFiles: 'testResults.xml' 26 | failTaskOnFailedTests: true 27 | condition: succeededOrFailed() 28 | -------------------------------------------------------------------------------- /src/PSArm.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.1.0-alpha1 5 | Microsoft Corporation 6 | (c) Microsoft Corporation 7 | 8 | net471;netcoreapp3.1 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | $(DefineConstants);CoreCLR 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Schema/Keyword/DslKeywordSchema.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Completion; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Schema.Keyword 9 | { 10 | internal abstract class DslKeywordSchema 11 | { 12 | public abstract bool ShouldUseDefaultParameterCompletions { get; } 13 | 14 | public abstract IEnumerable GetParameterNames(KeywordContextFrame context); 15 | 16 | public abstract IEnumerable GetParameterValues(KeywordContextFrame context, string parameterName); 17 | 18 | public abstract string GetParameterType(KeywordContextFrame context, string parameterName); 19 | 20 | public abstract IReadOnlyDictionary GetInnerKeywords(KeywordContextFrame context); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tools/release/UserExclusions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | .GIT 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Internal/ReadOnlyDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Runtime.CompilerServices; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace PSArm.Internal 14 | { 15 | internal static class ReadOnlyDictionaryExtensions 16 | { 17 | public static IReadOnlyDictionary ShallowClone(this IReadOnlyDictionary value) 18 | { 19 | var dict = new Dictionary(value.Count); 20 | foreach (KeyValuePair entry in value) 21 | { 22 | dict[entry.Key] = entry.Value; 23 | } 24 | return dict; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmIntegerLiteral.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Visitors; 6 | using PSArm.Types; 7 | using System.ComponentModel; 8 | 9 | namespace PSArm.Templates.Primitives 10 | { 11 | [TypeConverter(typeof(ArmElementConverter))] 12 | public sealed class ArmIntegerLiteral : ArmLiteral 13 | { 14 | public ArmIntegerLiteral(long value) : base(value, ArmType.Int) 15 | { 16 | } 17 | 18 | public override int GetHashCode() 19 | { 20 | return Value.GetHashCode(); 21 | } 22 | 23 | public override string ToInnerExpressionString() 24 | { 25 | return Value.ToString(); 26 | } 27 | 28 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitIntegerValue(this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmDoubleLiteral.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Visitors; 6 | using PSArm.Types; 7 | using System.Collections.Generic; 8 | using System.ComponentModel; 9 | 10 | namespace PSArm.Templates.Primitives 11 | { 12 | [TypeConverter(typeof(ArmElementConverter))] 13 | public class ArmDoubleLiteral : ArmLiteral 14 | { 15 | public ArmDoubleLiteral(double value) : base(value, ArmType.Double) 16 | { 17 | } 18 | 19 | public override string ToInnerExpressionString() 20 | { 21 | return Value.ToString(); 22 | } 23 | 24 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitDoubleValue(this); 25 | 26 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 27 | => this; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Templates/Metadata/PSArmTopLevelTemplateMetadata.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Templates.Metadata 9 | { 10 | public class PSArmTopLevelTemplateMetadata : ArmMetadata 11 | { 12 | public PSArmTopLevelTemplateMetadata() 13 | { 14 | GeneratorMetadata = new PSArmGeneratorMetadata(); 15 | } 16 | 17 | public PSArmGeneratorMetadata GeneratorMetadata 18 | { 19 | get => (PSArmGeneratorMetadata)GetElementOrNull(ArmTemplateKeys.GeneratorKey); 20 | set => this[ArmTemplateKeys.GeneratorKey] = value; 21 | } 22 | 23 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 24 | => InstantiateIntoCopy(new PSArmTopLevelTemplateMetadata(), parameters); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/pester/Conversion.Tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | 4 | BeforeAll { 5 | Import-Module "$PSScriptRoot/../tools/TestHelper.psm1" 6 | } 7 | 8 | Describe "ARM conversion cmdlets" { 9 | It "Can round-trip an ARM template successfully" { 10 | $templatePath = "$PSScriptRoot/assets/roundtrip-template.json" 11 | $psarmScriptPath = Join-Path $TestDrive 'test-template.psarm.ps1' 12 | 13 | ConvertFrom-ArmTemplate -Path $templatePath | ConvertTo-PSArm -OutFile $psarmScriptPath -Force 14 | $armObject = Publish-PSArmTemplate -TemplatePath $psarmScriptPath -NoWriteFile -NoHashTemplate -PassThru 15 | 16 | $armTemplate = $armObject.Resources[0]['properties']['template'] 17 | 18 | $original = Get-Content -Path $templatePath -Raw | ConvertFrom-Json 19 | $created = $armTemplate.ToJson() 20 | 21 | Assert-StructurallyEqual -ComparisonObject $original -JsonObject $created 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/buildAndTest.yaml: -------------------------------------------------------------------------------- 1 | stages: 2 | 3 | - stage: Build 4 | jobs: 5 | - job: Build 6 | pool: 7 | vmImage: windows-latest 8 | steps: 9 | - template: steps/build.yaml 10 | 11 | - stage: Tests 12 | dependsOn: Build 13 | jobs: 14 | - job: 15 | strategy: 16 | matrix: 17 | 'Ubuntu | PowerShell 7': 18 | vmImage: ubuntu-18.04 19 | 'macOS | PowerShell 7': 20 | vmImage: macOS-10.15 21 | 'Windows | PowerShell 7': 22 | vmImage: windows-latest 23 | pool: 24 | vmImage: $[variables['vmImage']] 25 | steps: 26 | - template: steps/tests.yaml 27 | - job: 28 | strategy: 29 | matrix: 30 | 'Windows | PowerShell 5.1': 31 | vmImage: windows-latest 32 | useWinPS: true 33 | pool: 34 | vmImage: $[ variables['vmImage'] ] 35 | steps: 36 | - template: steps/tests.yaml 37 | parameters: 38 | useWinPS: true 39 | -------------------------------------------------------------------------------- /src/Schema/Keyword/StaticKeywordSchema.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Completion; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Schema.Keyword 9 | { 10 | internal class StaticKeywordSchema : KnownParametersSchema 11 | { 12 | private readonly IReadOnlyDictionary _innerKeywords; 13 | 14 | public StaticKeywordSchema( 15 | IReadOnlyDictionary parameters, 16 | IReadOnlyDictionary schema) 17 | : base(parameters, useParametersForCompletions: false) 18 | { 19 | _innerKeywords = schema; 20 | } 21 | 22 | public override IEnumerable GetParameterNames(KeywordContextFrame context) 23 | => Parameters.Keys; 24 | 25 | public override IReadOnlyDictionary GetInnerKeywords(KeywordContextFrame context) => _innerKeywords; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Commands/Primitive/NewPSArmFunctionCallCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Templates.Operations; 7 | using PSArm.Templates.Primitives; 8 | using System.Management.Automation; 9 | 10 | namespace PSArm.Commands.Primitive 11 | { 12 | [OutputType(typeof(ArmFunctionCallExpression))] 13 | [Alias(KeywordName)] 14 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "FunctionCall")] 15 | public class NewPSArmFunctionCallCommand : Cmdlet 16 | { 17 | public const string KeywordName = "RawCall"; 18 | 19 | [Parameter(Mandatory = true, Position = 0)] 20 | public IArmString Name { get; set; } 21 | 22 | [Parameter(ValueFromRemainingArguments = true)] 23 | public ArmExpression[] Arguments { get; set; } 24 | 25 | protected override void EndProcessing() 26 | { 27 | WriteObject(new ArmFunctionCallExpression(Name, Arguments)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Serialization/ArmBuiltinFunction.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Newtonsoft.Json; 6 | 7 | namespace PSArm.Serialization 8 | { 9 | public class ArmBuiltinFunction 10 | { 11 | [JsonConstructor] 12 | public ArmBuiltinFunction( 13 | string name, 14 | string description, 15 | int minimumArguments, 16 | int? maximumArguments, 17 | string[] returnValueMembers) 18 | { 19 | Name = name; 20 | Description = description; 21 | MinimumArguments = minimumArguments; 22 | MaximumArguments = maximumArguments; 23 | ReturnValueMembers = returnValueMembers; 24 | } 25 | 26 | public string Name { get; } 27 | 28 | public string Description { get; } 29 | 30 | public int MinimumArguments { get; } 31 | 32 | public int? MaximumArguments { get; } 33 | 34 | public string[] ReturnValueMembers { get; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Templates/Operations/ArmVariableReferenceExpression.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | 9 | namespace PSArm.Templates.Operations 10 | { 11 | public class ArmVariableReferenceExpression : ArmReferenceExpression 12 | { 13 | private static readonly ArmStringLiteral s_variableReferenceFunction = new ArmStringLiteral("variables"); 14 | 15 | internal ArmVariableReferenceExpression(IArmString variableName) 16 | : base(s_variableReferenceFunction, variableName) 17 | { 18 | } 19 | 20 | public ArmVariableReferenceExpression(ArmVariable variable) 21 | : base(s_variableReferenceFunction, variable) 22 | { 23 | } 24 | 25 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitVariableReference(this); 26 | 27 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 28 | => this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmStringLiteral.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Internal; 6 | using PSArm.Templates.Visitors; 7 | using PSArm.Types; 8 | using System.ComponentModel; 9 | 10 | namespace PSArm.Templates.Primitives 11 | { 12 | [TypeConverter(typeof(ArmElementConverter))] 13 | public sealed class ArmStringLiteral : ArmLiteral, IArmString 14 | { 15 | public ArmStringLiteral(string value) : base(value, ArmType.String) 16 | { 17 | } 18 | 19 | public string ToExpressionString() 20 | { 21 | string value = Value; 22 | if (value.StartsWith("[") && value.EndsWith("]")) 23 | { 24 | value = $"[{value}"; 25 | } 26 | return value.Replace("\"", "\\\""); 27 | } 28 | 29 | public string ToIdentifierString() => Value.CamelCase(); 30 | 31 | public override string ToInnerExpressionString() => $"'{Value}'"; 32 | 33 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitStringValue(this); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Templates/Metadata/ArmGeneratorMetadata.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Templates.Metadata 9 | { 10 | public class ArmGeneratorMetadata : ArmObject 11 | { 12 | public ArmStringLiteral Name 13 | { 14 | get => GetElementOrNull(ArmTemplateKeys.Name); 15 | set => this[ArmTemplateKeys.Name] = value; 16 | } 17 | 18 | public ArmStringLiteral Version 19 | { 20 | get => GetElementOrNull(ArmTemplateKeys.Version); 21 | set => this[ArmTemplateKeys.Version] = value; 22 | } 23 | 24 | public ArmStringLiteral TemplateHash 25 | { 26 | get => GetElementOrNull(ArmTemplateKeys.TemplateHash); 27 | set => this[ArmTemplateKeys.TemplateHash] = value; 28 | } 29 | 30 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 31 | => InstantiateIntoCopy(new ArmGeneratorMetadata(), parameters); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Schema/ResourceDslDefinition.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System.Collections.Generic; 6 | using System.Management.Automation; 7 | 8 | namespace PSArm.Schema 9 | { 10 | public class ResourceDslDefinition 11 | { 12 | public ResourceDslDefinition( 13 | Dictionary resourceKeywordDefinitions) 14 | : this(resourceKeywordDefinitions, discriminatedKeywordDefinitions: null) 15 | { 16 | } 17 | 18 | public ResourceDslDefinition( 19 | Dictionary resourceKeywordDefinitions, 20 | IReadOnlyDictionary> discriminatedKeywordDefinitions) 21 | { 22 | ResourceKeywordDefinitions = resourceKeywordDefinitions; 23 | DiscriminatedKeywordDefinitions = discriminatedKeywordDefinitions; 24 | } 25 | 26 | public Dictionary ResourceKeywordDefinitions { get; } 27 | 28 | public IReadOnlyDictionary> DiscriminatedKeywordDefinitions { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/simple-storage-account/storage-account.psarm.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory)] 3 | [string] 4 | $StorageAccountName, 5 | 6 | [Parameter()] 7 | [ValidateSet('WestUS2', 'CentralUS')] 8 | [string] 9 | $Location = 'WestUS2' 10 | ) 11 | 12 | Arm { 13 | param( 14 | [ValidateSet('Hot', 'Cool', 'Archive')] 15 | [ArmParameter[string]] 16 | $accessTier = 'Hot', 17 | 18 | [ArmParameter[int]] 19 | $allowPublicAccess, 20 | 21 | [ArmParameter[int]] 22 | $httpsOnly, 23 | 24 | [ArmParameter[string]] 25 | $deploymentTime = (utcNow), 26 | 27 | [ArmVariable] 28 | $timePlus3 = (dateTimeAdd $deploymentTime 'PT3H') 29 | ) 30 | 31 | Resource $StorageAccountName -Namespace Microsoft.Storage -Type storageAccounts -ApiVersion 2019-06-01 -Kind StorageV2 -Location $Location { 32 | ArmSku Standard_LRS 33 | properties { 34 | accessTier $accessTier 35 | supportsHTTPSTrafficOnly $httpsOnly 36 | allowBlobPublicAccess $allowPublicAccess 37 | allowSharedKeyAccess 1 38 | } 39 | } 40 | 41 | Output 'deploymentTime' -Type string -Value $deploymentTime 42 | Output 'timePlus3' -Type string -Value $timePlus3 43 | } 44 | -------------------------------------------------------------------------------- /src/Schema/Keyword/ResourceKeywordCache.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Template; 6 | using PSArm.Completion; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace PSArm.Schema.Keyword 11 | { 12 | internal abstract class ResourceKeywordCache 13 | { 14 | protected ResourceKeywordCache(ResourceSchema resource) 15 | { 16 | Resource = resource; 17 | } 18 | 19 | protected ResourceSchema Resource { get; } 20 | 21 | public abstract IReadOnlyDictionary GetInnerKeywords(KeywordContextFrame context); 22 | 23 | protected Dictionary GetBaseKeywordDictionary() 24 | { 25 | return new Dictionary(StringComparer.OrdinalIgnoreCase) 26 | { 27 | { NewPSArmSkuCommand.KeywordName, PSArmSchemaInformation.SkuSchema }, 28 | { NewPSArmDependsOnCommand.KeywordName, PSArmSchemaInformation.DependsOnSchema }, 29 | { NewPSArmResourceCommand.KeywordName, ResourceKeywordSchema.Value }, 30 | }; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/releaseCompliance.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: artifact 3 | type: string 4 | default: signed 5 | 6 | steps: 7 | 8 | # From https://github.com/PowerShell/Compliance 9 | 10 | - checkout: ComplianceRepo 11 | displayName: 'Checkout the ComplianceRepo' 12 | 13 | - checkout: self 14 | 15 | - template: download.yaml 16 | parameters: 17 | artifact: ${{ parameters.artifact }} 18 | 19 | - template: logArtifacts.yaml 20 | 21 | - template: assembly-module-compliance.yml@ComplianceRepo 22 | parameters: 23 | # binskim 24 | AnalyzeTarget: '$(Build.ArtifactStagingDirectory)/${{ parameters.artifact }}/PSArm/*.dll' 25 | AnalyzeSymPath: 'SRV*' 26 | # Component governance 27 | sourceScanPath: '$(Build.SourcesDirectory)/PSArm' 28 | # CredScan 29 | suppressionsFile: '' 30 | # TermCheck AKA PoliCheck 31 | targetArgument: '$(Build.SourcesDirectory)/PSArm' 32 | optionsUEPATH: '$(Build.SourcesDirectory)/PSArm/tools/release/UserExclusions.xml' 33 | optionsRulesDBPath: '' 34 | optionsFTPath: '$(Build.SourcesDirectory)/PSArm/tools/release/FileTypeSet.xml' 35 | # TSA upload 36 | codeBaseName: PSArm_202009 37 | # Turn off ApiScan 38 | APIScan: false 39 | -------------------------------------------------------------------------------- /src/Templates/Metadata/PSArmGeneratorMetadata.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Templates.Metadata 9 | { 10 | public class PSArmGeneratorMetadata : ArmGeneratorMetadata 11 | { 12 | private static readonly ArmStringLiteral s_psarmGeneratorName = new ArmStringLiteral("psarm"); 13 | 14 | private static readonly ArmStringLiteral s_psarmGeneratorVersion = new ArmStringLiteral(typeof(PSArmGeneratorMetadata).Assembly.GetName().Version.ToString()); 15 | 16 | public PSArmGeneratorMetadata() 17 | { 18 | Name = s_psarmGeneratorName; 19 | Version = s_psarmGeneratorVersion; 20 | } 21 | 22 | public ArmStringLiteral PowerShellVersion 23 | { 24 | get => GetElementOrNull(ArmTemplateKeys.Metadata_PSVersion); 25 | set => this[ArmTemplateKeys.Metadata_PSVersion] = value; 26 | } 27 | 28 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 29 | => InstantiateIntoCopy(new PSArmGeneratorMetadata(), parameters); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Commands/Template/NewPSArmOutputCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Templates; 7 | using PSArm.Templates.Primitives; 8 | using System.Management.Automation; 9 | 10 | namespace PSArm.Commands.Template 11 | { 12 | [OutputType(typeof(ArmEntry))] 13 | [Alias(KeywordName)] 14 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "Output")] 15 | public class NewPSArmOutputCommand : PSArmKeywordCommand 16 | { 17 | internal const string KeywordName = "Output"; 18 | 19 | [Parameter(Position = 0, Mandatory = true)] 20 | public IArmString Name { get; set; } 21 | 22 | [Parameter(Mandatory = true)] 23 | public IArmString Type { get; set; } 24 | 25 | [Parameter(Mandatory = true)] 26 | public IArmString Value { get; set; } 27 | 28 | protected override void EndProcessing() 29 | { 30 | WriteArmValueEntry( 31 | ArmTemplateKeys.Outputs, 32 | new ArmObject 33 | { 34 | [Name] = new ArmOutput(Name) { Type = Type, Value = Value } 35 | }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Parameterization/PowerShellArmVariableConstructor.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates; 6 | using PSArm.Templates.Primitives; 7 | using System.Collections.Generic; 8 | using System.Management.Automation; 9 | using System.Management.Automation.Language; 10 | 11 | namespace PSArm.Parameterization 12 | { 13 | internal class PowerShellArmVariableConstructor : PowerShellArmTemplateParameterConstructor 14 | { 15 | private readonly List _parameterValues; 16 | 17 | public PowerShellArmVariableConstructor(HashSet parameterNames, List parameterValues) 18 | : base(parameterNames) 19 | { 20 | _parameterValues = parameterValues; 21 | } 22 | 23 | protected override List CreateEvaluationState() 24 | { 25 | return _parameterValues; 26 | } 27 | 28 | protected override ArmVariable EvaluateParameter(List variables, ParameterAst parameter) 29 | { 30 | return new ArmVariable(new ArmStringLiteral(GetParameterName(parameter)), GetParameterValue(parameter, variables)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmBooleanLiteral.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Visitors; 6 | using PSArm.Types; 7 | using System.Collections.Generic; 8 | using System.ComponentModel; 9 | 10 | namespace PSArm.Templates.Primitives 11 | { 12 | [TypeConverter(typeof(ArmElementConverter))] 13 | public class ArmBooleanLiteral : ArmLiteral 14 | { 15 | public static ArmBooleanLiteral True { get; } = new ArmBooleanLiteral(true); 16 | 17 | public static ArmBooleanLiteral False { get; } = new ArmBooleanLiteral(false); 18 | 19 | public static ArmBooleanLiteral FromBool(bool value) => value ? True : False; 20 | 21 | private ArmBooleanLiteral(bool value) : base(value, ArmType.Bool) 22 | { 23 | } 24 | 25 | public override string ToInnerExpressionString() 26 | { 27 | return Value ? "true" : "false"; 28 | } 29 | 30 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitBooleanValue(this); 31 | 32 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 33 | => this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Templates/ArmOutput.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | 9 | namespace PSArm.Templates 10 | { 11 | public class ArmOutput : ArmObject 12 | { 13 | public ArmOutput(IArmString name) 14 | { 15 | Name = name; 16 | } 17 | 18 | public IArmString Name { get; } 19 | 20 | public IArmString Type 21 | { 22 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Type); 23 | set => this[ArmTemplateKeys.Type] = (ArmElement)value; 24 | } 25 | 26 | public IArmString Value 27 | { 28 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Value); 29 | set => this[ArmTemplateKeys.Value] = (ArmElement)value; 30 | } 31 | 32 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitOutput(this); 33 | 34 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 35 | => InstantiateIntoCopy(new ArmOutput((IArmString)Name.Instantiate(parameters)), parameters); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Templates/Operations/ArmReferenceExpression.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | 7 | namespace PSArm.Templates.Operations 8 | { 9 | public abstract class ArmReferenceExpression : ArmFunctionCallExpression where TReference : IArmReferenceable 10 | { 11 | private TReference _referencedValue; 12 | 13 | protected ArmReferenceExpression(ArmStringLiteral referenceFunction, IArmString referencedName) 14 | : base(referenceFunction, new[] { (ArmExpression)referencedName }) 15 | { 16 | ReferenceName = referencedName; 17 | } 18 | 19 | protected ArmReferenceExpression(ArmStringLiteral referenceFunction, TReference referencedValue) 20 | : this(referenceFunction, referencedValue.ReferenceName) 21 | { 22 | ReferencedValue = referencedValue; 23 | } 24 | 25 | public IArmString ReferenceName { get; private set; } 26 | 27 | public TReference ReferencedValue 28 | { 29 | get => _referencedValue; 30 | set 31 | { 32 | ReferenceName = value?.ReferenceName; 33 | _referencedValue = value; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmElement.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Newtonsoft.Json.Linq; 6 | using PSArm.Serialization; 7 | using PSArm.Templates.Visitors; 8 | using PSArm.Types; 9 | using System.Collections.Generic; 10 | using System.ComponentModel; 11 | 12 | namespace PSArm.Templates.Primitives 13 | { 14 | [TypeConverter(typeof(ArmElementConverter))] 15 | public abstract class ArmElement : IArmElement 16 | { 17 | public JToken ToJson() 18 | { 19 | return Visit(new ArmJsonBuildingVisitor()); 20 | } 21 | 22 | public TResult RunVisit(IArmVisitor visitor) 23 | { 24 | return Visit(visitor); 25 | } 26 | 27 | public VisitAction RunVisit(ArmTravsersingVisitor visitor) 28 | { 29 | VisitAction result = Visit(visitor); 30 | visitor.PostVisit(this); 31 | return result; 32 | } 33 | 34 | protected abstract TResult Visit(IArmVisitor visitor); 35 | 36 | public abstract IArmElement Instantiate(IReadOnlyDictionary parameters); 37 | 38 | public string ToJsonString() 39 | { 40 | return ToJson().ToString(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/simple-test/simple-test.psarm.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | 4 | 5 | param( 6 | [Parameter(Mandatory)] 7 | [string] 8 | $storageAccountName, 9 | 10 | [Parameter(Mandatory)] 11 | [string] 12 | $location, 13 | 14 | [Parameter()] 15 | [string] 16 | $accountType = 'Standard_LRS', 17 | 18 | [Parameter()] 19 | [string] 20 | $kind = 'StorageV2', 21 | 22 | [Parameter()] 23 | [string] 24 | $accessTier = 'Hot', 25 | 26 | [Parameter()] 27 | [string] 28 | $minimumTLSVersion = 'TLS1_2', 29 | 30 | [Parameter()] 31 | [bool] 32 | $supportsHTTPSTrafficOnly = 1, 33 | 34 | [Parameter()] 35 | [bool] 36 | $allowBlobPublicAccess = 1, 37 | 38 | [Parameter()] 39 | [bool] 40 | $allowSharedKeyAccess = 1 41 | ) 42 | 43 | Arm { 44 | Resource $storageAccountName -Namespace 'Microsoft.Storage' -Type 'storageAccounts' -apiVersion '2019-06-01' -kind $kind -Location $location { 45 | ArmSku $accountType 46 | Properties { 47 | accessTier $accessTier 48 | minimumTLSVersion $minimumTLSVersion 49 | supportsHTTPSTrafficOnly $supportsHTTPSTrafficOnly 50 | allowBlobPublicAccess $allowBlobPublicAccess 51 | allowSharedKeyAccess $allowSharedKeyAccess 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Schema/Keyword/ObjectResourceKeywordCache.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Azure.Bicep.Types.Concrete; 6 | using PSArm.Completion; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace PSArm.Schema.Keyword 11 | { 12 | internal class ObjectResourceKeywordCache : ResourceKeywordCache 13 | { 14 | private Lazy> _keywordsLazy; 15 | 16 | public ObjectResourceKeywordCache(ResourceSchema resource) 17 | : base(resource) 18 | { 19 | _keywordsLazy = new Lazy>(GetKeywordTableFromResource); 20 | } 21 | 22 | public override IReadOnlyDictionary GetInnerKeywords(KeywordContextFrame context) 23 | { 24 | return _keywordsLazy.Value; 25 | } 26 | 27 | private IReadOnlyDictionary GetKeywordTableFromResource() 28 | { 29 | Dictionary dict = GetBaseKeywordDictionary(); 30 | foreach (KeyValuePair property in Resource.Properties) 31 | { 32 | dict[property.Key] = BicepKeywordSchemaBuilder.GetKeywordSchemaForBicepType(property.Value); 33 | } 34 | return dict; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Templates/Operations/ArmParameterReferenceExpression.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | 9 | namespace PSArm.Templates.Operations 10 | { 11 | public class ArmParameterReferenceExpression : ArmReferenceExpression 12 | { 13 | private static readonly ArmStringLiteral s_parameterReferenceFunction = new ArmStringLiteral("parameters"); 14 | 15 | internal ArmParameterReferenceExpression(IArmString parameterName) 16 | : base(s_parameterReferenceFunction, parameterName) 17 | { 18 | } 19 | 20 | public ArmParameterReferenceExpression(ArmParameter parameter) 21 | : base(s_parameterReferenceFunction, parameter) 22 | { 23 | } 24 | 25 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitParameterReference(this); 26 | 27 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 28 | { 29 | if (parameters is null) 30 | { 31 | return this; 32 | } 33 | 34 | return parameters.TryGetValue(ReferenceName, out ArmElement value) 35 | ? value 36 | : this; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Commands/Primitive/NewPSArmElementCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Templates.Primitives; 7 | using System.Management.Automation; 8 | 9 | namespace PSArm.Commands.Primitive 10 | { 11 | [OutputType(typeof(ArmElement))] 12 | [Alias(KeywordName)] 13 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "Element", DefaultParameterSetName = "Value")] 14 | public class NewPSArmElementCommand : PSArmKeywordCommand 15 | { 16 | internal const string KeywordName = "ArmElement"; 17 | 18 | [Parameter(Mandatory = true, Position = 1, ParameterSetName = "Value")] 19 | public ArmElement Value { get; set; } 20 | 21 | [Parameter(Mandatory = true, Position = 1, ParameterSetName = "Body")] 22 | public ScriptBlock Body { get; set; } 23 | 24 | [Parameter(ParameterSetName = "Body")] 25 | public SwitchParameter ArrayBody { get; set; } 26 | 27 | protected override void EndProcessing() 28 | { 29 | if (Value != null) 30 | { 31 | WriteObject(Value); 32 | return; 33 | } 34 | 35 | if (ArrayBody) 36 | { 37 | WriteArmArrayElement(Body); 38 | return; 39 | } 40 | 41 | WriteArmObjectElement(Body); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Commands/Template/NewPSArmSkuCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Templates; 7 | using PSArm.Templates.Primitives; 8 | using System.Management.Automation; 9 | 10 | namespace PSArm.Commands.Template 11 | { 12 | [OutputType(typeof(ArmEntry))] 13 | [Alias(KeywordName)] 14 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "Sku")] 15 | public class NewPSArmSkuCommand : PSArmKeywordCommand 16 | { 17 | internal const string KeywordName = "ArmSku"; 18 | 19 | [Parameter(Position = 0, Mandatory = true)] 20 | public IArmString Name { get; set; } 21 | 22 | [Parameter] 23 | public IArmString Tier { get; set; } 24 | 25 | [Parameter] 26 | public IArmString Size { get; set; } 27 | 28 | [Parameter] 29 | public IArmString Family { get; set; } 30 | 31 | [Parameter] 32 | public IArmString Capacity { get; set; } 33 | 34 | protected override void EndProcessing() 35 | { 36 | WriteArmValueEntry( 37 | ArmTemplateKeys.Sku, 38 | new ArmSku 39 | { 40 | Name = Name, 41 | Tier = Tier, 42 | Size = Size, 43 | Family = Family, 44 | Capacity = Capacity 45 | }); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/simple-parameterless/Parameterless.PSArm.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | $namePrefix = 'my' 6 | $vnetNamespace = 'myVnet/' 7 | $rgLocation = 'WestUS2' 8 | 9 | Arm { 10 | $PSDefaultParameterValues['Resource:Location'] = $rgLocation 11 | 12 | Resource "${vnetNamespace}${namePrefix}-subnet" -Namespace Microsoft.Network -ApiVersion 2019-11-01 -Type virtualNetworks/subnets { 13 | properties { 14 | addressPrefix 10.0.0.0/24 15 | } 16 | } 17 | 18 | 'pip1','pip2' | ForEach-Object { 19 | Resource "$namePrefix-$_" -Namespace Microsoft.Network -ApiVersion 2019-11-01 -Type publicIPAddresses { 20 | properties { 21 | publicIPAllocationMethod Dynamic 22 | } 23 | } 24 | } 25 | 26 | Resource "$namePrefix-nic" -Namespace Microsoft.Network -ApiVersion 2019-11-01 -Type networkInterfaces { 27 | properties { 28 | ipConfigurations { 29 | name 'myConfig' 30 | properties { 31 | privateIPAllocationMethod Dynamic 32 | subnet { 33 | id (resourceId 'Microsoft.Network/virtualNetworks/subnets' "$namePrefix-nic-subnet") 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | Output 'nicResourceId' -Type 'string' -Value (resourceId 'Microsoft.Network/networkInterfaces' "$namePrefix-nic") 41 | } 42 | -------------------------------------------------------------------------------- /examples/simple-test/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "psarm", 7 | "version": "0.1.0.0" 8 | } 9 | }, 10 | "resources": [ 11 | { 12 | "name": "simple-test", 13 | "type": "Microsoft.Resources/deployments", 14 | "apiVersion": "2019-10-01", 15 | "properties": { 16 | "mode": "Incremental", 17 | "expressionEvaluationOptions": { 18 | "scope": "inner" 19 | }, 20 | "template": { 21 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 22 | "contentVersion": "1.0.0.0", 23 | "resources": [ 24 | { 25 | "name": "psarmtest", 26 | "apiVersion": "2019-06-01", 27 | "type": "Microsoft.Storage/storageAccounts", 28 | "kind": "StorageV2", 29 | "location": "westus2", 30 | "sku": { 31 | "name": "Standard_LRS" 32 | }, 33 | "properties": { 34 | "accessTier": "Hot", 35 | "minimumTlsVersion": "TLS1_2", 36 | "supportsHttpsTrafficOnly": true, 37 | "allowBlobPublicAccess": true, 38 | "allowSharedKeyAccess": true 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /src/Templates/ArmVariable.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Operations; 6 | using PSArm.Templates.Primitives; 7 | using PSArm.Templates.Visitors; 8 | using System.Collections.Generic; 9 | 10 | namespace PSArm.Templates 11 | { 12 | public class ArmVariable : ArmElement, IArmReferenceable 13 | { 14 | public static explicit operator ArmVariableReferenceExpression(ArmVariable variable) => variable.GetReference(); 15 | 16 | public static explicit operator ArmExpression(ArmVariable variable) => (ArmVariableReferenceExpression)variable; 17 | 18 | public ArmVariable(IArmString name, ArmElement value) 19 | { 20 | Name = name; 21 | Value = value; 22 | } 23 | 24 | public IArmString Name { get; } 25 | 26 | public ArmElement Value { get; } 27 | 28 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitVariableDeclaration(this); 29 | 30 | public ArmVariableReferenceExpression GetReference() => new ArmVariableReferenceExpression(this); 31 | 32 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 33 | => this; 34 | 35 | public override string ToString() 36 | { 37 | return GetReference().ToString(); 38 | } 39 | 40 | IArmString IArmReferenceable.ReferenceName => Name; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Commands/Internal/VerboseHttpLoggingHandler.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System.Management.Automation.Host; 6 | using System.Net.Http; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace PSArm.Commands.Internal 11 | { 12 | internal class VerboseHttpLoggingHandler : DelegatingHandler 13 | { 14 | PSHostUserInterface _psUI; 15 | 16 | public VerboseHttpLoggingHandler(PSHostUserInterface psUI, HttpMessageHandler innerHandler) 17 | : base(innerHandler) 18 | { 19 | _psUI = psUI; 20 | } 21 | 22 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 23 | { 24 | _psUI.WriteVerboseLine($"Sending {request.Method} request to '{request.RequestUri}'"); 25 | _psUI.WriteVerboseLine("Body:"); 26 | _psUI.WriteVerboseLine(await request.Content.ReadAsStringAsync().ConfigureAwait(false)); 27 | 28 | HttpResponseMessage response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); 29 | 30 | _psUI.WriteVerboseLine($"Got response: {response}"); 31 | if (response.Content is not null) 32 | { 33 | _psUI.WriteVerboseLine("Body:"); 34 | _psUI.WriteVerboseLine(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); 35 | } 36 | 37 | return response; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.azure-pipelines/templates/steps/codeSigning.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | 3 | # From https://github.com/PowerShell/Compliance 4 | 5 | - checkout: ComplianceRepo 6 | displayName: 'Checkout the ComplianceRepo' 7 | 8 | - checkout: self 9 | 10 | - template: download.yaml 11 | parameters: 12 | artifact: unsigned 13 | 14 | - template: logArtifacts.yaml 15 | 16 | - template: EsrpSign.yml@ComplianceRepo 17 | parameters: 18 | buildOutputPath: '$(Build.ArtifactStagingDirectory)/unsigned' 19 | signOutputPath: '$(Build.ArtifactStagingDirectory)/FirstPartySigned' 20 | alwaysCopy: true 21 | certificateId: 'CP-230012' # Authenticode certificate 22 | useMinimatch: true # Enable globbing 23 | pattern: | 24 | PSArm/{Desktop,Core}/PSArm.dll 25 | PSArm/*.{ps1,psm1,psd1,ps1xml} 26 | 27 | - template: EsrpSign.yml@ComplianceRepo 28 | parameters: 29 | buildOutputPath: '$(Build.ArtifactStagingDirectory)/FirstPartySigned' 30 | signOutputPath: '$(Build.ArtifactStagingDirectory)/signed' 31 | alwaysCopy: true 32 | certificateId: 'CP-231522' # Third-party certificate 33 | useMinimatch: true # This enables the use of globbing 34 | pattern: | 35 | PSArm/{Desktop,Core}/Newtonsoft.Json.dll 36 | 37 | - pwsh: | 38 | Import-Module '$(Build.SourcesDirectory)/PSArm/tools/release/ReleaseTools.psm1' 39 | Assert-FilesAreSigned -Path '$(Build.ArtifactStagingDirectory)/signed/PSArm' 40 | displayName: Validate that files are signed 41 | 42 | - publish: '$(Build.ArtifactStagingDirectory)/signed' 43 | artifact: signed 44 | displayName: Publish signed artifacts 45 | -------------------------------------------------------------------------------- /src/Templates/Operations/ArmOperation.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System.Dynamic; 7 | 8 | namespace PSArm.Templates.Operations 9 | { 10 | using Expression = System.Linq.Expressions.Expression; 11 | 12 | public abstract class ArmOperation : ArmExpression, IDynamicMetaObjectProvider, IArmString 13 | { 14 | public string ToIdentifierString() => ToInnerExpressionString(); 15 | 16 | public string ToExpressionString() 17 | { 18 | return $"[{ToInnerExpressionString()}]"; 19 | } 20 | 21 | public DynamicMetaObject GetMetaObject(Expression parameter) 22 | { 23 | return new ArmExpressionMetaObject(parameter, this); 24 | } 25 | 26 | private class ArmExpressionMetaObject : DynamicMetaObject 27 | { 28 | public ArmExpressionMetaObject(Expression expression, ArmOperation value) 29 | : base(expression, BindingRestrictions.Empty, value) 30 | { 31 | } 32 | 33 | public override DynamicMetaObject BindGetMember(GetMemberBinder binder) 34 | { 35 | return ArmMemberAccessExpression.CreateMetaObject(this, binder); 36 | } 37 | 38 | public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) 39 | { 40 | return ArmIndexAccessExpression.CreateMetaObject(this, binder, indexes); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/tools/runPesterTests.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | param( 6 | $PSArmPath = "$PSScriptRoot/../../out/PSArm", 7 | [switch]$CI 8 | ) 9 | 10 | $ErrorActionPreference = 'Stop' 11 | 12 | Import-Module -Name Pester 13 | Import-Module "$PSScriptRoot/../../tools/BuildHelper.psm1" 14 | Import-Module -Name $PSArmPath 15 | Import-Module -Name "$PSScriptRoot/TestHelper.psm1" 16 | 17 | Write-Log "PSModulePath: '$env:PSModulePath'" 18 | Write-Log "PSArmPath: '$PSArmPath'" 19 | 20 | $armDepsDir = join-path ([System.IO.Path]::GetTempPath()) 'PSArmDeps' 21 | if (Test-Path $armDepsDir) 22 | { 23 | Write-Log "PSArmDeps: '$(Get-ChildItem $armDepsDir)'" 24 | } 25 | else 26 | { 27 | Write-Log "PSArmDeps directory not found" 28 | } 29 | 30 | $repoRoot = (Resolve-Path "$PSScriptRoot/../../").Path 31 | $testsPath = Join-Path $repoRoot "test/pester" 32 | $testResultsPath = Join-Path $repoRoot "testResults.xml" 33 | 34 | $config = [PesterConfiguration]@{ 35 | Run = @{ 36 | Path = $testsPath 37 | PassThru = $true 38 | } 39 | Output = @{ 40 | Verbosity = 'Detailed' 41 | } 42 | } 43 | 44 | if ($CI) 45 | { 46 | $config.TestResult = @{ 47 | Enabled = $true 48 | OutputFormat = 'NUnitXml' 49 | OutputPath = $testResultsPath 50 | } 51 | } 52 | 53 | $testResults = Invoke-Pester -Configuration $config 54 | 55 | if ($null -eq $testResults -or $testResults.Failed -or ($CI -and -not (Test-Path $testResultsPath))) 56 | { 57 | throw "Pester tests failed. See output for details" 58 | } 59 | -------------------------------------------------------------------------------- /src/Types/ArmExpressionConverter.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System; 7 | using System.Management.Automation; 8 | 9 | namespace PSArm.Types 10 | { 11 | public class ArmExpressionConverter : PSTypeConverter 12 | { 13 | public override bool CanConvertFrom(object sourceValue, Type destinationType) 14 | { 15 | if (sourceValue is null) 16 | { 17 | return true; 18 | } 19 | 20 | if (Type.GetTypeCode(sourceValue.GetType()) != TypeCode.Object) 21 | { 22 | return true; 23 | } 24 | 25 | return sourceValue is ArmExpression; 26 | } 27 | 28 | public override bool CanConvertTo(object sourceValue, Type destinationType) 29 | { 30 | return false; 31 | } 32 | 33 | public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) 34 | { 35 | if (!ArmElementConversion.TryConvertToArmExpression(sourceValue, out ArmExpression armExpression)) 36 | { 37 | throw new InvalidCastException($"Value of type '{sourceValue.GetType()}' could not be converted to type '{destinationType}'"); 38 | } 39 | 40 | return armExpression; 41 | } 42 | 43 | public override object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) 44 | { 45 | throw new NotImplementedException(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Parameterization/ReferenceCollectingPSAstVisitor.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Internal; 6 | using System.Collections.Generic; 7 | using System.Management.Automation.Language; 8 | 9 | namespace PSArm.Parameterization 10 | { 11 | internal class ReferenceCollectingPSAstVisitor : AstVisitor2 12 | { 13 | private readonly HashSet _variablesToFind; 14 | 15 | private readonly Dictionary> _references; 16 | 17 | public ReferenceCollectingPSAstVisitor(HashSet variablesToFind) 18 | : this() 19 | { 20 | _variablesToFind = variablesToFind; 21 | } 22 | 23 | public ReferenceCollectingPSAstVisitor() 24 | { 25 | _references = new Dictionary>(); 26 | } 27 | 28 | public IReadOnlyDictionary> References => _references; 29 | 30 | public void Reset() 31 | { 32 | _references.Clear(); 33 | } 34 | 35 | public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) 36 | { 37 | string variableName = variableExpressionAst.VariablePath.UserPath; 38 | 39 | if (_variablesToFind is null 40 | || _variablesToFind.Contains(variableName)) 41 | { 42 | _references.AddToDictionaryList(variableExpressionAst.VariablePath.UserPath, variableExpressionAst); 43 | } 44 | 45 | return AstVisitAction.Continue; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Templates/Primitives/ArmLiteral.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Types; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Templates.Primitives 9 | { 10 | 11 | public abstract class ArmLiteral : ArmExpression 12 | { 13 | private readonly object _value; 14 | 15 | protected ArmLiteral(object value, ArmType armType) 16 | { 17 | _value = value; 18 | ArmType = armType; 19 | } 20 | 21 | public ArmType ArmType { get; } 22 | 23 | public object GetValue() => _value; 24 | 25 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 26 | => this; 27 | } 28 | 29 | public abstract class ArmLiteral : ArmLiteral 30 | { 31 | protected ArmLiteral(T value, ArmType armType) 32 | : base(value, armType) 33 | { 34 | Value = value; 35 | } 36 | 37 | public T Value { get; } 38 | 39 | public override bool Equals(object obj) 40 | { 41 | if (ReferenceEquals(this, obj)) 42 | { 43 | return true; 44 | } 45 | 46 | if (!(obj is ArmLiteral armVal)) 47 | { 48 | return false; 49 | } 50 | 51 | return Equals(Value, armVal.Value); 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | if (Value == null) 57 | { 58 | return 0; 59 | } 60 | 61 | return Value.GetHashCode(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Templates/ArmSku.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | 9 | namespace PSArm.Templates 10 | { 11 | public class ArmSku : ArmObject 12 | { 13 | public IArmString Name 14 | { 15 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Name); 16 | set => this[ArmTemplateKeys.Name] = (ArmElement)value; 17 | } 18 | 19 | public IArmString Tier 20 | { 21 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Tier); 22 | set => this[ArmTemplateKeys.Tier] = (ArmElement)value; 23 | } 24 | 25 | public IArmString Size 26 | { 27 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Size); 28 | set => this[ArmTemplateKeys.Size] = (ArmElement)value; 29 | } 30 | 31 | public IArmString Family 32 | { 33 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Family); 34 | set => this[ArmTemplateKeys.Family] = (ArmElement)value; 35 | } 36 | 37 | public IArmString Capacity 38 | { 39 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Capacity); 40 | set => this[ArmTemplateKeys.Capacity] = (ArmElement)value; 41 | } 42 | 43 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitSku(this); 44 | 45 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 46 | => InstantiateIntoCopy(new ArmSku(), parameters); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/New-PSArmDependsOn.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSArm.dll-Help.xml 3 | Module Name: PSArm 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # New-PSArmDependsOn 9 | 10 | ## SYNOPSIS 11 | Declares an ARM template dependency 12 | 13 | ## SYNTAX 14 | 15 | ### Value (Default) 16 | ``` 17 | New-PSArmDependsOn [-Value] [] 18 | ``` 19 | 20 | ### Body 21 | ``` 22 | New-PSArmDependsOn [-Body] [] 23 | ``` 24 | 25 | ## DESCRIPTION 26 | Declares a dependency from the resource this is used in to the resources it refers to. 27 | 28 | ## EXAMPLES 29 | 30 | ### Example 1 31 | ```powershell 32 | DependsOn @( 33 | resourceId 'Microsoft.Storage/storageAccounts' $storageAccountName 34 | resourceId 'Microsoft.Network/networkInterfaces' $nicName 35 | ) 36 | ``` 37 | 38 | Adds an entry in the current resource that it depends on the two other resources referred to. 39 | 40 | ## PARAMETERS 41 | 42 | ### -Value 43 | References to the resources depended on. 44 | 45 | ```yaml 46 | Type: IArmString[] 47 | Parameter Sets: Value 48 | Aliases: 49 | 50 | Required: True 51 | Position: 0 52 | Default value: None 53 | Accept pipeline input: False 54 | Accept wildcard characters: False 55 | ``` 56 | 57 | ### CommonParameters 58 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 59 | 60 | ## INPUTS 61 | 62 | ### None 63 | 64 | ## OUTPUTS 65 | 66 | ### PSArm.Templates.Primitives.ArmEntry 67 | ## NOTES 68 | 69 | ## RELATED LINKS 70 | -------------------------------------------------------------------------------- /test/pester/Expression.Tests.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | Describe "Arm expression tests" { 5 | $testCases = @( 6 | @{ 7 | Expression = Concat 'a' 'b' 8 | Expected = "[concat('a', 'b')]" 9 | } 10 | @{ 11 | Expression = (ResourceGroup).Location 12 | Expected = "[resourceGroup().location]" 13 | } 14 | @{ 15 | Expression = Concat 'storage' (UniqueString (ResourceGroup).Id) 16 | Expected = "[concat('storage', uniqueString(resourceGroup().id))]" 17 | } 18 | @{ 19 | Expression = [PSArm.Templates.Primitives.ArmStringLiteral]'hello' 20 | Expected = "hello" 21 | } 22 | @{ 23 | Expression = [PSArm.Templates.Primitives.ArmStringLiteral]'[things]' 24 | Expected = "[[things]" 25 | } 26 | @{ 27 | Expression = [PSArm.Templates.Primitives.ArmStringLiteral]'[something] else' 28 | Expected = "[something] else" 29 | } 30 | @{ 31 | Expression = [PSArm.Templates.Primitives.ArmStringLiteral]'something [else]' 32 | Expected = "something [else]" 33 | } 34 | @{ 35 | Expression = [PSArm.Templates.Primitives.ArmStringLiteral]'"quoted"' 36 | Expected = '\"quoted\"' 37 | } 38 | ) 39 | 40 | It "Creates the expected expression: " -TestCases $testCases { 41 | param( 42 | [PSArm.Templates.Primitives.ArmExpression] 43 | $Expression, 44 | 45 | [string] 46 | $Expected 47 | ) 48 | 49 | $Expression.ToExpressionString() | Should -BeExactly $Expected 50 | } 51 | } -------------------------------------------------------------------------------- /src/Commands/Template/NewPSArmDependsOnCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Templates; 7 | using PSArm.Templates.Primitives; 8 | using System.Management.Automation; 9 | 10 | namespace PSArm.Commands.Template 11 | { 12 | [OutputType(typeof(ArmEntry))] 13 | [Alias(KeywordName)] 14 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "DependsOn", DefaultParameterSetName = "Value")] 15 | public class NewPSArmDependsOnCommand : PSArmKeywordCommand 16 | { 17 | internal const string KeywordName = "DependsOn"; 18 | 19 | [Parameter(Position = 0, Mandatory = true, ParameterSetName = "Value")] 20 | public IArmString[] Value { get; set; } 21 | 22 | [Parameter(Position = 0, Mandatory = true, ParameterSetName = "Body")] 23 | public ScriptBlock Body { get; set; } 24 | 25 | protected override void EndProcessing() 26 | { 27 | var array = new ArmArray(); 28 | 29 | if (Value != null) 30 | { 31 | foreach (IArmString value in Value) 32 | { 33 | array.Add((ArmElement)value); 34 | } 35 | WriteArmValueEntry(ArmTemplateKeys.DependsOn, array); 36 | return; 37 | } 38 | 39 | foreach (PSObject output in InvokeBody(Body)) 40 | { 41 | if (LanguagePrimitives.TryConvertTo(output, typeof(IArmString), out object result)) 42 | { 43 | array.Add((ArmElement)result); 44 | } 45 | } 46 | 47 | WriteArmValueEntry(ArmTemplateKeys.DependsOn, array); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Templates/Visitors/IArmVisitor.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Operations; 6 | using PSArm.Templates.Primitives; 7 | 8 | namespace PSArm.Templates.Visitors 9 | { 10 | public interface IArmVisitor 11 | { 12 | TResult VisitStringValue(ArmStringLiteral stringValue); 13 | 14 | TResult VisitNullValue(ArmNullLiteral nullValue); 15 | 16 | TResult VisitIntegerValue(ArmIntegerLiteral integerValue); 17 | 18 | TResult VisitDoubleValue(ArmDoubleLiteral doubleValue); 19 | 20 | TResult VisitBooleanValue(ArmBooleanLiteral booleanValue); 21 | 22 | TResult VisitArray(ArmArray array); 23 | 24 | TResult VisitObject(ArmObject obj); 25 | 26 | TResult VisitFunctionCall(ArmFunctionCallExpression functionCall); 27 | 28 | TResult VisitMemberAccess(ArmMemberAccessExpression memberAccess); 29 | 30 | TResult VisitIndexAccess(ArmIndexAccessExpression indexAccess); 31 | 32 | TResult VisitParameterReference(ArmParameterReferenceExpression parameterReference); 33 | 34 | TResult VisitVariableReference(ArmVariableReferenceExpression variableReference); 35 | 36 | TResult VisitTemplate(ArmTemplate template); 37 | 38 | TResult VisitNestedTemplate(ArmNestedTemplate nestedTemplate); 39 | 40 | TResult VisitTemplateResource(ArmTemplateResource templateResource); 41 | 42 | TResult VisitOutput(ArmOutput output); 43 | 44 | TResult VisitResource(ArmResource resource); 45 | 46 | TResult VisitSku(ArmSku sku); 47 | 48 | TResult VisitParameterDeclaration(ArmParameter parameter); 49 | 50 | TResult VisitVariableDeclaration(ArmVariable variable); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Schema/Keyword/KnownParametersSchema.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Completion; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Schema.Keyword 9 | { 10 | internal abstract class KnownParametersSchema : DslKeywordSchema 11 | { 12 | private readonly bool _shouldUseDefaultParameterCompletions; 13 | 14 | public KnownParametersSchema( 15 | IReadOnlyDictionary parameters, 16 | bool useParametersForCompletions) 17 | { 18 | _shouldUseDefaultParameterCompletions = !useParametersForCompletions; 19 | Parameters = parameters; 20 | } 21 | 22 | protected IReadOnlyDictionary Parameters { get; } 23 | 24 | public override bool ShouldUseDefaultParameterCompletions => _shouldUseDefaultParameterCompletions; 25 | 26 | public override IEnumerable GetParameterNames(KeywordContextFrame context) 27 | => Parameters.Keys; 28 | 29 | public override string GetParameterType(KeywordContextFrame context, string parameterName) 30 | { 31 | if (!Parameters.TryGetValue(parameterName, out DslParameterInfo parameterInfo)) 32 | { 33 | return null; 34 | } 35 | 36 | return parameterInfo.Type; 37 | } 38 | 39 | public override IEnumerable GetParameterValues(KeywordContextFrame context, string parameterName) 40 | { 41 | if (!Parameters.TryGetValue(parameterName, out DslParameterInfo parameterInfo)) 42 | { 43 | return null; 44 | } 45 | 46 | return parameterInfo.Values; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PSArm.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSArm", "src\PSArm.csproj", "{8F7AC9B5-667C-4836-8176-35B1819C040A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Debug|x64.Build.0 = Debug|Any CPU 25 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Debug|x86.Build.0 = Debug|Any CPU 27 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Release|x64.ActiveCfg = Release|Any CPU 30 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Release|x64.Build.0 = Release|Any CPU 31 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Release|x86.ActiveCfg = Release|Any CPU 32 | {8F7AC9B5-667C-4836-8176-35B1819C040A}.Release|x86.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/Commands/Internal/HashtableTransformationAttribute.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System.Collections; 6 | using System.Management.Automation; 7 | 8 | namespace PSArm.Commands.Internal 9 | { 10 | public class HashtableTransformationAttribute : ArgumentTransformationAttribute 11 | { 12 | public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) 13 | { 14 | switch (inputData) 15 | { 16 | case null: 17 | return null; 18 | 19 | case PSObject psObject: 20 | switch (psObject.BaseObject) 21 | { 22 | case null: 23 | return TransformPSObject(psObject); 24 | 25 | case PSCustomObject _: 26 | return TransformPSObject(psObject); 27 | 28 | case object innerObject: 29 | return Transform(engineIntrinsics, innerObject); 30 | } 31 | 32 | case Hashtable parameterHashtable: 33 | return parameterHashtable; 34 | 35 | default: 36 | throw new ArgumentTransformationMetadataException($"Unable to transform type '{inputData.GetType()}' to a hashtable"); 37 | } 38 | } 39 | 40 | private Hashtable TransformPSObject(PSObject psObject) 41 | { 42 | var hashtable = new Hashtable(); 43 | foreach (PSPropertyInfo property in psObject.Properties) 44 | { 45 | hashtable[property.Name] = property.Value; 46 | } 47 | return hashtable; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Templates/Builders/ArmNestedTemplateBuilder.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System.Collections.Generic; 7 | 8 | namespace PSArm.Templates.Builders 9 | { 10 | internal class ArmNestedTemplateBuilder 11 | { 12 | private readonly ArmArray _templateResources; 13 | 14 | private readonly Dictionary _templateNameCounts; 15 | 16 | public ArmNestedTemplateBuilder() 17 | { 18 | _templateResources = new ArmArray(); 19 | _templateNameCounts = new Dictionary(); 20 | } 21 | 22 | public ArmNestedTemplateBuilder AddTemplate(ArmTemplate template) 23 | { 24 | string templateName = template.TemplateName; 25 | if (_templateNameCounts.TryGetValue(templateName, out int count)) 26 | { 27 | count++; 28 | templateName = $"{templateName}_{count}"; 29 | _templateNameCounts[templateName] = count; 30 | } 31 | else 32 | { 33 | _templateNameCounts[templateName] = 0; 34 | } 35 | 36 | _templateResources.Add(new ArmTemplateResource(new ArmStringLiteral(templateName)) 37 | { 38 | Template = template, 39 | }); 40 | 41 | return this; 42 | } 43 | 44 | public ArmNestedTemplate Build() 45 | { 46 | return new ArmNestedTemplate 47 | { 48 | Resources = _templateResources, 49 | }; 50 | } 51 | 52 | public void Clear() 53 | { 54 | _templateResources.Clear(); 55 | _templateNameCounts.Clear(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Types/UnsafeCompilerServicesLoadHandler.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System; 6 | using System.IO; 7 | using System.Management.Automation; 8 | using System.Reflection; 9 | 10 | namespace PSArm.Types 11 | { 12 | #if !CoreCLR 13 | public class UnsafeCompilerServicesLoadHandler : IModuleAssemblyInitializer, IModuleAssemblyCleanup 14 | { 15 | private static readonly string s_moduleAsmDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 16 | 17 | public void OnImport() 18 | { 19 | AppDomain.CurrentDomain.AssemblyResolve += HandleAssemblyResolve; 20 | } 21 | 22 | public void OnRemove(PSModuleInfo psModuleInfo) 23 | { 24 | AppDomain.CurrentDomain.AssemblyResolve -= HandleAssemblyResolve; 25 | } 26 | 27 | private static Assembly HandleAssemblyResolve(object sender, ResolveEventArgs args) 28 | { 29 | var requiredAsmName = new AssemblyName(args.Name); 30 | 31 | string possibleAsmPath = Path.Combine(s_moduleAsmDir, $"{requiredAsmName.Name}.dll"); 32 | 33 | AssemblyName bundledAsmName = null; 34 | try 35 | { 36 | bundledAsmName = AssemblyName.GetAssemblyName(possibleAsmPath); 37 | } 38 | catch 39 | { 40 | // If we don't bundle the assembly we're looking for, we don't have it so return nothing 41 | return null; 42 | } 43 | 44 | // Now make sure our version is greater 45 | if (bundledAsmName.Version < requiredAsmName.Version) 46 | { 47 | return null; 48 | } 49 | 50 | return Assembly.LoadFrom(possibleAsmPath); 51 | } 52 | } 53 | #endif 54 | } 55 | 56 | -------------------------------------------------------------------------------- /docs/New-PSArmFunctionCall.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSArm.dll-Help.xml 3 | Module Name: PSArm 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # New-PSArmFunctionCall 9 | 10 | ## SYNOPSIS 11 | Low-level way to declare a function call in ARM 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | New-PSArmFunctionCall [-Name] [-Arguments ] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | `New-PSArmFunctionCall`, better used as the `RawCall` keyword, 21 | specifies an ARM function call with the given name and arguments. 22 | 23 | ## EXAMPLES 24 | 25 | ### Example 1 26 | ```powershell 27 | RawCall concat prefix suffic 28 | ``` 29 | 30 | Specifies the parameter call `[concat('prefix', 'suffix')]`. 31 | 32 | ## PARAMETERS 33 | 34 | ### -Arguments 35 | The arguments to the function call. 36 | 37 | ```yaml 38 | Type: ArmExpression[] 39 | Parameter Sets: (All) 40 | Aliases: 41 | 42 | Required: False 43 | Position: Named 44 | Default value: None 45 | Accept pipeline input: False 46 | Accept wildcard characters: False 47 | ``` 48 | 49 | ### -Name 50 | The name of the function being called. 51 | 52 | ```yaml 53 | Type: IArmString 54 | Parameter Sets: (All) 55 | Aliases: 56 | 57 | Required: True 58 | Position: 0 59 | Default value: None 60 | Accept pipeline input: False 61 | Accept wildcard characters: False 62 | ``` 63 | 64 | ### CommonParameters 65 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 66 | 67 | ## INPUTS 68 | 69 | ### PSArm.Templates.Primitives.ArmExpression[] 70 | 71 | ## OUTPUTS 72 | 73 | ### PSArm.Templates.Operations.ArmFunctionCallExpression 74 | ## NOTES 75 | 76 | ## RELATED LINKS 77 | -------------------------------------------------------------------------------- /src/Schema/Keyword/PSArmSchemaInformation.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Template; 6 | using PSArm.Internal; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace PSArm.Schema.Keyword 11 | { 12 | internal static class PSArmSchemaInformation 13 | { 14 | public static DslKeywordSchema PSArmSchema { get; } = new StaticKeywordSchema( 15 | parameters: null, 16 | new Dictionary(StringComparer.OrdinalIgnoreCase) 17 | { 18 | { NewPSArmTemplateCommand.KeywordName, new StaticKeywordSchema( 19 | KeywordPowerShellParameterDiscovery.GetKeywordParametersFromCmdletType(typeof(NewPSArmTemplateCommand)), 20 | new Dictionary(StringComparer.OrdinalIgnoreCase) 21 | { 22 | { NewPSArmResourceCommand.KeywordName, ResourceKeywordSchema.Value }, 23 | { NewPSArmOutputCommand.KeywordName, new OpenKeywordSchema( 24 | KeywordPowerShellParameterDiscovery.GetKeywordParametersFromCmdletType(typeof(NewPSArmOutputCommand)), 25 | useParametersForCompletions: false) } 26 | })}, 27 | }); 28 | 29 | public static DslKeywordSchema SkuSchema { get; } = new OpenKeywordSchema( 30 | KeywordPowerShellParameterDiscovery.GetKeywordParametersFromCmdletType(typeof(NewPSArmSkuCommand)), 31 | useParametersForCompletions: true); 32 | 33 | public static DslKeywordSchema DependsOnSchema { get; } = new OpenKeywordSchema( 34 | KeywordPowerShellParameterDiscovery.GetKeywordParametersFromCmdletType(typeof(NewPSArmDependsOnCommand)), 35 | useParametersForCompletions: true); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Types/ArmElementConverter.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System; 7 | using System.Collections; 8 | using System.Management.Automation; 9 | 10 | namespace PSArm.Types 11 | { 12 | public class ArmElementConverter : PSTypeConverter 13 | { 14 | public override bool CanConvertFrom(object sourceValue, Type destinationType) 15 | { 16 | if (sourceValue is null) 17 | { 18 | return true; 19 | } 20 | 21 | Type sourceType = sourceValue.GetType(); 22 | switch (Type.GetTypeCode(sourceType)) 23 | { 24 | case TypeCode.Object: 25 | return typeof(IEnumerable).IsAssignableFrom(destinationType) 26 | || typeof(IDictionary).IsAssignableFrom(destinationType); 27 | 28 | default: 29 | return true; 30 | } 31 | } 32 | 33 | public override bool CanConvertTo(object sourceValue, Type destinationType) 34 | { 35 | return false; 36 | } 37 | 38 | public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) 39 | { 40 | if (!ArmElementConversion.TryConvertToArmElement(sourceValue, out ArmElement armElement)) 41 | { 42 | throw new InvalidCastException($"Value of type '{sourceValue.GetType()}' could not be converted to type '{destinationType}'"); 43 | } 44 | 45 | return armElement; 46 | } 47 | 48 | public override object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Commands/Primitive/NewPSArmArrayCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Templates.Primitives; 7 | using PSArm.Types; 8 | using System; 9 | using System.Management.Automation; 10 | 11 | namespace PSArm.Commands.Primitive 12 | { 13 | [OutputType(typeof(ArmArray))] 14 | [Alias(KeywordName)] 15 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "Array", DefaultParameterSetName = "Body")] 16 | public class NewPSArmArrayCommand : PSArmKeywordCommand 17 | { 18 | public const string KeywordName = "ArmArray"; 19 | 20 | [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Body")] 21 | public ScriptBlock Body { get; set; } 22 | 23 | [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ObjectArray")] 24 | public object[] Values { get; set; } 25 | 26 | protected override void EndProcessing() 27 | { 28 | if (Body != null) 29 | { 30 | WriteArmArrayElement(Body); 31 | return; 32 | } 33 | 34 | var armArray = new ArmArray(); 35 | for (int i = 0; i < Values.Length; i++) 36 | { 37 | object currVal = Values[i]; 38 | if (!ArmElementConversion.TryConvertToArmElement(currVal, out ArmElement element)) 39 | { 40 | this.ThrowTerminatingError( 41 | new InvalidCastException($"Unable to convert value '{currVal}' of type '{currVal.GetType()}' to type '{typeof(ArmElement)}'"), 42 | "InvalidArmTypeCase", 43 | ErrorCategory.InvalidArgument, 44 | currVal); 45 | return; 46 | } 47 | armArray.Add(element); 48 | } 49 | WriteObject(armArray); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Templates/ArmTemplateResource.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | 9 | namespace PSArm.Templates 10 | { 11 | public class ArmTemplateResource : ArmResource 12 | { 13 | private static readonly ArmStringLiteral s_type = new ArmStringLiteral("Microsoft.Resources/deployments"); 14 | private static readonly ArmStringLiteral s_apiVersion = new ArmStringLiteral("2019-10-01"); 15 | private static readonly ArmStringLiteral s_incrementalMode = new ArmStringLiteral("Incremental"); 16 | private static readonly ArmStringLiteral s_inner = new ArmStringLiteral("inner"); 17 | 18 | public ArmTemplateResource(IArmString name) 19 | { 20 | Name = name; 21 | Type = s_type; 22 | ApiVersion = s_apiVersion; 23 | this[ArmTemplateKeys.Properties] = new ArmObject 24 | { 25 | [ArmTemplateKeys.Mode] = s_incrementalMode, 26 | [ArmTemplateKeys.ExpressionEvaluationOptions] = new ArmObject 27 | { 28 | [ArmTemplateKeys.Scope] = s_inner, 29 | }, 30 | }; 31 | } 32 | 33 | public ArmTemplate Template 34 | { 35 | get => (ArmTemplate)((ArmObject)GetElementOrNull(ArmTemplateKeys.Properties))?[ArmTemplateKeys.Template]; 36 | set => ((ArmObject)this[ArmTemplateKeys.Properties])[ArmTemplateKeys.Template] = value; 37 | } 38 | 39 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitTemplateResource(this); 40 | 41 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 42 | => InstantiateIntoCopy(new ArmTemplateResource((IArmString)Name.Instantiate(parameters)), parameters); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Types/BicepArmTypeVisitor.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Azure.Bicep.Types.Concrete; 6 | using System; 7 | 8 | namespace PSArm.Types 9 | { 10 | internal abstract class BicepArmTypeVisitor 11 | { 12 | public TResult Visit(TypeBase armType) 13 | { 14 | switch (armType) 15 | { 16 | case ArrayType armArray: 17 | return VisitArray(armArray); 18 | 19 | case BuiltInType armBuiltin: 20 | return VisitBuiltin(armBuiltin); 21 | 22 | case DiscriminatedObjectType armDiscriminatedObject: 23 | return VisitDiscriminatedObject(armDiscriminatedObject); 24 | 25 | case ObjectType armObject: 26 | return VisitObject(armObject); 27 | 28 | case ResourceType armResource: 29 | return VisitResource(armResource); 30 | 31 | case StringLiteralType armString: 32 | return VisitString(armString); 33 | 34 | case UnionType armUnion: 35 | return VisitUnion(armUnion); 36 | 37 | default: 38 | throw new ArgumentException($"Unknown ARM schema type: '{armType}'"); 39 | } 40 | } 41 | 42 | protected abstract TResult VisitArray(ArrayType armArray); 43 | 44 | protected abstract TResult VisitBuiltin(BuiltInType armBuiltin); 45 | 46 | protected abstract TResult VisitDiscriminatedObject(DiscriminatedObjectType armDiscriminatedObject); 47 | 48 | protected abstract TResult VisitObject(ObjectType armObject); 49 | 50 | protected abstract TResult VisitResource(ResourceType armResource); 51 | 52 | protected abstract TResult VisitString(StringLiteralType armString); 53 | 54 | protected abstract TResult VisitUnion(UnionType armUnion); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Internal/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System; 6 | 7 | namespace PSArm.Internal 8 | { 9 | /// 10 | /// Helper class to reduce boilerplate needed for string manipulation. 11 | /// 12 | internal static class StringExtensions 13 | { 14 | /// 15 | /// Check case-insensitive string equality. 16 | /// 17 | /// 18 | /// 19 | /// True if the strings are case-insensitively equal, false otherwise. 20 | public static bool Is(this string thisStr, string thatStr) 21 | { 22 | return string.Equals(thisStr, thatStr, StringComparison.OrdinalIgnoreCase); 23 | } 24 | 25 | public static bool HasPrefix(this string s, string prefix) 26 | => s.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); 27 | 28 | /// 29 | /// Convert a string from PascalCase to camelCase. 30 | /// 31 | /// The string to convert. 32 | /// The camel-cased string. 33 | public static string CamelCase(this string s) 34 | { 35 | return char.IsLower(s[0]) 36 | ? s 37 | : char.ToLower(s[0]) + s.Substring(1); 38 | } 39 | 40 | public static string PascalCase(this string s) 41 | { 42 | return char.IsUpper(s[0]) 43 | ? s 44 | : char.ToUpper(s[0]) + s.Substring(1); 45 | } 46 | 47 | public static string Depluralize(this string s) 48 | { 49 | int lastIdx = s.Length - 1; 50 | 51 | if (s[lastIdx] != 's') 52 | { 53 | return s; 54 | } 55 | 56 | return s.Substring(0, lastIdx); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/Templates/ArmNestedTemplate.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Metadata; 6 | using PSArm.Templates.Primitives; 7 | using PSArm.Templates.Visitors; 8 | using System.Collections.Generic; 9 | 10 | namespace PSArm.Templates 11 | { 12 | public class ArmNestedTemplate : ArmTemplate 13 | { 14 | public static ArmNestedTemplate CreateFromTemplates(IEnumerable templates) 15 | { 16 | var resourceArray = new ArmArray(); 17 | var templateNames = new Dictionary(); 18 | foreach (ArmTemplate template in templates) 19 | { 20 | string templateName = template.TemplateName; 21 | if (templateNames.TryGetValue(templateName, out int count)) 22 | { 23 | count++; 24 | templateName = $"{templateName}_{count}"; 25 | templateNames[templateName] = count; 26 | } 27 | else 28 | { 29 | templateNames[templateName] = 0; 30 | } 31 | 32 | resourceArray.Add(new ArmTemplateResource(new ArmStringLiteral(templateName)) 33 | { 34 | Template = template, 35 | }); 36 | } 37 | 38 | return new ArmNestedTemplate 39 | { 40 | Resources = resourceArray, 41 | }; 42 | } 43 | 44 | public ArmNestedTemplate() 45 | { 46 | Metadata = new PSArmTopLevelTemplateMetadata(); 47 | } 48 | 49 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitNestedTemplate(this); 50 | 51 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 52 | => InstantiateIntoCopy(new ArmNestedTemplate(), parameters); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/pester/assets/roundtrip-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "resources": [ 5 | { 6 | "name": "myVnet/my-subnet", 7 | "apiVersion": "2019-11-01", 8 | "type": "Microsoft.Network/virtualNetworks/subnets", 9 | "properties": { 10 | "addressPrefix": "10.0.0.0/24" 11 | } 12 | }, 13 | { 14 | "name": "my-pip1", 15 | "apiVersion": "2019-11-01", 16 | "type": "Microsoft.Network/publicIPAddresses", 17 | "location": "WestUS2", 18 | "properties": { 19 | "publicIPAllocationMethod": "Dynamic" 20 | } 21 | }, 22 | { 23 | "name": "my-pip2", 24 | "apiVersion": "2019-11-01", 25 | "type": "Microsoft.Network/publicIPAddresses", 26 | "location": "WestUS2", 27 | "properties": { 28 | "publicIPAllocationMethod": "Dynamic" 29 | } 30 | }, 31 | { 32 | "name": "my-nic", 33 | "apiVersion": "2019-11-01", 34 | "type": "Microsoft.Network/networkInterfaces", 35 | "location": "WestUS2", 36 | "properties": { 37 | "ipConfigurations": [ 38 | { 39 | "name": "myConfig", 40 | "properties": { 41 | "privateIPAllocationMethod": "Dynamic", 42 | "subnet": { 43 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'my-nic-subnet')]" 44 | } 45 | } 46 | } 47 | ] 48 | } 49 | } 50 | ], 51 | "outputs": { 52 | "nicResourceId": { 53 | "type": "string", 54 | "value": "[resourceId('Microsoft.Network/networkInterfaces', 'my-nic')]" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tools/addCopyrightHeaders.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | param( 6 | [Parameter()] 7 | [string] 8 | $PSHeader, 9 | 10 | [Parameter()] 11 | [string] 12 | $CSharpHeader, 13 | 14 | [Parameter()] 15 | [string] 16 | $RepoRoot = ((Resolve-Path "$PSScriptRoot/../").Path) 17 | ) 18 | 19 | if (-not $PSHeader) 20 | { 21 | $PSHeader = @' 22 | 23 | # Copyright (c) Microsoft Corporation. 24 | # Licensed under the MIT License. 25 | 26 | 27 | '@ 28 | } 29 | 30 | $psHeaderTrimmed = $PSHeader.Trim() 31 | 32 | if (-not $CSharpHeader) 33 | { 34 | $CSharpHeader = @' 35 | 36 | // Copyright (c) Microsoft Corporation. 37 | // Licensed under the MIT License. 38 | 39 | 40 | '@ 41 | } 42 | 43 | $csharpHeaderTrimmed = $CSharpHeader.Trim() 44 | 45 | :loop foreach ($file in Get-ChildItem -Path $RepoRoot -Recurse) 46 | { 47 | Write-Verbose "Looking at file '$file'" 48 | if ($file -is [System.IO.DirectoryInfo]) 49 | { 50 | continue 51 | } 52 | 53 | $content = Get-Content -Raw $file.FullName 54 | switch ($file.Extension) 55 | { 56 | '.cs' 57 | { 58 | if ($content.TrimStart().StartsWith($csharpHeaderTrimmed)) 59 | { 60 | continue loop 61 | } 62 | 63 | $content = $CSharpHeader + $content 64 | $encoding = 'utf8NoBOM' 65 | break 66 | } 67 | 68 | { $_ -in '.ps1', '.psm1' } 69 | { 70 | if ($content.TrimStart().StartsWith($psHeaderTrimmed)) 71 | { 72 | continue loop 73 | } 74 | 75 | $content = $PSHeader + $content 76 | $encoding = 'utf8BOM' 77 | break 78 | } 79 | 80 | default 81 | { 82 | continue loop 83 | } 84 | } 85 | 86 | Write-Host "Adding copyright header to '$file'" 87 | Set-Content -Path $file.FullName -Value $content -NoNewline -Encoding $encoding 88 | } 89 | -------------------------------------------------------------------------------- /src/Schema/Keyword/BicepObjectKeywordSchema.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Azure.Bicep.Types.Concrete; 6 | using PSArm.Completion; 7 | using PSArm.Internal; 8 | using System; 9 | using System.Collections.Generic; 10 | 11 | namespace PSArm.Schema.Keyword 12 | { 13 | internal class BicepObjectKeywordSchema : BicepKeywordSchema 14 | { 15 | private readonly Lazy> _innerKeywordsLazy; 16 | 17 | public BicepObjectKeywordSchema(ObjectType objectType) 18 | : base(objectType) 19 | { 20 | _innerKeywordsLazy = new Lazy>(BuildInnerKeywordDict); 21 | } 22 | 23 | public override IReadOnlyDictionary GetInnerKeywords(KeywordContextFrame context) => _innerKeywordsLazy.Value; 24 | 25 | public override IEnumerable GetParameterNames(KeywordContextFrame context) 26 | { 27 | return BodyParameter; 28 | } 29 | 30 | public override string GetParameterType(KeywordContextFrame context, string parameterName) 31 | { 32 | if (parameterName.Is("Body")) 33 | { 34 | return "scriptblock"; 35 | } 36 | 37 | return null; 38 | } 39 | 40 | public override IEnumerable GetParameterValues(KeywordContextFrame context, string parameterName) 41 | { 42 | return null; 43 | } 44 | 45 | private IReadOnlyDictionary BuildInnerKeywordDict() 46 | { 47 | Dictionary dict = new Dictionary(StringComparer.OrdinalIgnoreCase); 48 | foreach (KeyValuePair property in BicepType.Properties) 49 | { 50 | dict[property.Key] = BicepKeywordSchemaBuilder.GetKeywordSchemaForBicepType(property.Value.Type.Type); 51 | } 52 | return dict; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/New-PSArmOutput.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSArm.dll-Help.xml 3 | Module Name: PSArm 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # New-PSArmOutput 9 | 10 | ## SYNOPSIS 11 | Declare outputs of the template 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | New-PSArmOutput [-Name] -Type -Value [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Declares any outputs of the ARM template. Best used as the `Output` keyword 21 | 22 | ## EXAMPLES 23 | 24 | ### Example 1 25 | ```powershell 26 | Output 'hostname' -Type 'string' -Value (reference $publicIPAddressName).dnsSettings.fqdn 27 | ``` 28 | 29 | Declares the `hostname` output, which outputs the fully qualified domain name of the public IP address, which is a string. 30 | 31 | ## PARAMETERS 32 | 33 | ### -Name 34 | The name of the output 35 | 36 | ```yaml 37 | Type: IArmString 38 | Parameter Sets: (All) 39 | Aliases: 40 | 41 | Required: True 42 | Position: 0 43 | Default value: None 44 | Accept pipeline input: False 45 | Accept wildcard characters: False 46 | ``` 47 | 48 | ### -Type 49 | The ARM type of the output 50 | 51 | ```yaml 52 | Type: IArmString 53 | Parameter Sets: (All) 54 | Aliases: 55 | 56 | Required: True 57 | Position: Named 58 | Default value: None 59 | Accept pipeline input: False 60 | Accept wildcard characters: False 61 | ``` 62 | 63 | ### -Value 64 | The reference expression for the output 65 | 66 | ```yaml 67 | Type: IArmString 68 | Parameter Sets: (All) 69 | Aliases: 70 | 71 | Required: True 72 | Position: Named 73 | Default value: None 74 | Accept pipeline input: False 75 | Accept wildcard characters: False 76 | ``` 77 | 78 | ### CommonParameters 79 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 80 | 81 | ## INPUTS 82 | 83 | ### None 84 | 85 | ## OUTPUTS 86 | 87 | ### PSArm.Templates.Primitives.ArmEntry 88 | ## NOTES 89 | 90 | ## RELATED LINKS 91 | -------------------------------------------------------------------------------- /src/Templates/ArmResource.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | 9 | namespace PSArm.Templates 10 | { 11 | public class ArmResource : ArmObject 12 | { 13 | public IArmString ApiVersion 14 | { 15 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.ApiVersion); 16 | set => this[ArmTemplateKeys.ApiVersion] = (ArmElement)value; 17 | } 18 | 19 | public IArmString Type 20 | { 21 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Type); 22 | set => this[ArmTemplateKeys.Type] = (ArmElement)value; 23 | } 24 | 25 | public IArmString Name 26 | { 27 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Name); 28 | set => this[ArmTemplateKeys.Name] = (ArmElement)value; 29 | } 30 | 31 | public ArmObject Properties 32 | { 33 | get => (ArmObject)GetElementOrNull(ArmTemplateKeys.Properties); 34 | set => this[ArmTemplateKeys.Properties] = value; 35 | } 36 | 37 | public ArmObject Resources 38 | { 39 | get => (ArmObject)GetElementOrNull(ArmTemplateKeys.Resources); 40 | set => this[ArmTemplateKeys.Resources] = value; 41 | } 42 | 43 | public ArmSku Sku 44 | { 45 | get => (ArmSku)GetElementOrNull(ArmTemplateKeys.Sku); 46 | set => this[ArmTemplateKeys.Sku] = value; 47 | } 48 | 49 | public ArmArray DependsOn 50 | { 51 | get => (ArmArray)GetElementOrNull(ArmTemplateKeys.DependsOn); 52 | set => this[ArmTemplateKeys.DependsOn] = value; 53 | } 54 | 55 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitResource(this); 56 | 57 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 58 | => InstantiateIntoCopy(new ArmResource(), parameters); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ThirdPartyNotices.txt: -------------------------------------------------------------------------------- 1 | NOTICES AND INFORMATION 2 | Do Not Translate or Localize 3 | 4 | This software incorporates material from third parties. 5 | Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, 6 | or you may send a check or money order for US $5.00, including the product name, 7 | the open source component name, platform, and version number, to: 8 | 9 | Source Code Compliance Team 10 | Microsoft Corporation 11 | One Microsoft Way 12 | Redmond, WA 98052 13 | USA 14 | 15 | Notwithstanding any other terms, you may reverse engineer this software to the extent 16 | required to debug changes to any libraries licensed under the GNU Lesser General Public License. 17 | 18 | 19 | 20 | ------------------------------------------------------------------- 21 | 22 | Newtonsoft.Json 12.0.2 - MIT 23 | (c) 2008 VeriSign, Inc. 24 | Copyright James Newton-King 2008 25 | Copyright (c) 2007 James Newton-King 26 | Copyright (c) James Newton-King 2008 27 | 28 | The MIT License (MIT) 29 | 30 | Copyright (c) 2007 James Newton-King 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy of 33 | this software and associated documentation files (the "Software"), to deal in 34 | the Software without restriction, including without limitation the rights to 35 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 36 | the Software, and to permit persons to whom the Software is furnished to do so, 37 | subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all 40 | copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 44 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 45 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 46 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 47 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | 49 | ------------------------------------------------------------------- -------------------------------------------------------------------------------- /src/OnImport.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | Set-Item function:\__OldTabExpansion2 (Get-Content -Raw Function:\TabExpansion2) 6 | 7 | function TabExpansion2 8 | { 9 | [CmdletBinding(DefaultParameterSetName = 'ScriptInputSet')] 10 | Param( 11 | [Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 0)] 12 | [string] $inputScript, 13 | 14 | [Parameter(ParameterSetName = 'ScriptInputSet', Position = 1)] 15 | [int] $cursorColumn = $inputScript.Length, 16 | 17 | [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 0)] 18 | [System.Management.Automation.Language.Ast] $ast, 19 | 20 | [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 1)] 21 | [System.Management.Automation.Language.Token[]] $tokens, 22 | 23 | [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 2)] 24 | [System.Management.Automation.Language.IScriptPosition] $positionOfCursor, 25 | 26 | [Parameter(ParameterSetName = 'ScriptInputSet', Position = 2)] 27 | [Parameter(ParameterSetName = 'AstInputSet', Position = 3)] 28 | [Hashtable] $options = $null 29 | ) 30 | 31 | if ($PSCmdlet.ParameterSetName -eq 'ScriptInputSet') 32 | { 33 | $convertedInput = [System.Management.Automation.CommandCompletion]::MapStringInputToParsedInput($inputScript, $cursorColumn) 34 | $ast = $convertedInput.Item1 35 | $tokens = $convertedInput.Item2 36 | $positionOfCursor = $convertedInput.Item3 37 | } 38 | 39 | $result = __OldTabExpansion2 $ast $tokens $positionOfCursor $options 40 | 41 | [PSArm.Completion.DslCompleter]::PrependDslCompletions($result, $ast, $tokens, $positionOfCursor, $options) 42 | 43 | return $result 44 | } 45 | 46 | # Use an event to set the OnRemove script 47 | $null = Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -MaxTriggerCount 1 -Action { 48 | (Get-Module PSArm).OnRemove = { 49 | Set-Item Function:\TabExpansion2 (Get-Content -Raw Function:__OldTabExpansion2) 50 | Remove-Item Function:__OldTabExpansion2 51 | } 52 | } -------------------------------------------------------------------------------- /examples/simple-storage-account/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "psarm", 7 | "version": "0.1.0.0" 8 | } 9 | }, 10 | "resources": [ 11 | { 12 | "name": "storage-account", 13 | "type": "Microsoft.Resources/deployments", 14 | "apiVersion": "2019-10-01", 15 | "properties": { 16 | "mode": "Incremental", 17 | "expressionEvaluationOptions": { 18 | "scope": "inner" 19 | }, 20 | "template": { 21 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 22 | "contentVersion": "1.0.0.0", 23 | "parameters": { 24 | "httpsOnly": { 25 | "type": "int" 26 | }, 27 | "deploymentTime": { 28 | "type": "string", 29 | "defaultValue": "[utcNow()]" 30 | } 31 | }, 32 | "variables": { 33 | "timePlus3": "[dateTimeAdd(parameters('deploymentTime'), 'PT3H')]" 34 | }, 35 | "resources": [ 36 | { 37 | "name": "MyStorageAccount", 38 | "apiVersion": "2019-06-01", 39 | "type": "Microsoft.Storage/storageAccounts", 40 | "kind": "StorageV2", 41 | "location": "WestUS2", 42 | "sku": { 43 | "name": "Standard_LRS" 44 | }, 45 | "properties": { 46 | "accessTier": "Hot", 47 | "supportsHttpsTrafficOnly": "[parameters('httpsOnly')]", 48 | "allowBlobPublicAccess": 1, 49 | "allowSharedKeyAccess": 1 50 | } 51 | } 52 | ], 53 | "outputs": { 54 | "deploymentTime": { 55 | "type": "string", 56 | "value": "[parameters('deploymentTime')]" 57 | }, 58 | "timePlus3": { 59 | "type": "string", 60 | "value": "[variables('timePlus3')]" 61 | } 62 | } 63 | } 64 | } 65 | } 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /src/Commands/ConvertFromArmTemplateCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Serialization; 7 | using PSArm.Templates; 8 | using System; 9 | using System.Management.Automation; 10 | 11 | namespace PSArm.Commands 12 | { 13 | [OutputType(typeof(ArmTemplate))] 14 | [Cmdlet(VerbsData.ConvertFrom, "ArmTemplate")] 15 | public class ConvertFromArmTemplateCommand : PSCmdlet 16 | { 17 | private readonly ArmParser _parser; 18 | 19 | private int _templateCount; 20 | 21 | public ConvertFromArmTemplateCommand() 22 | { 23 | _parser = new ArmParser(); 24 | _templateCount = 0; 25 | } 26 | 27 | [ValidateNotNullOrEmpty] 28 | [Parameter(ParameterSetName = "Path", Position = 0, Mandatory = true)] 29 | public string[] Path { get; set; } 30 | 31 | [ValidateNotNullOrEmpty] 32 | [Parameter(ParameterSetName = "Uri", Position = 0, Mandatory = true)] 33 | public Uri[] Uri { get; set; } 34 | 35 | [ValidateNotNullOrEmpty] 36 | [Parameter(ParameterSetName = "Input", Position = 0, Mandatory = true, ValueFromPipeline = true)] 37 | public string[] Input { get; set; } 38 | 39 | protected override void ProcessRecord() 40 | { 41 | _templateCount++; 42 | switch (ParameterSetName) 43 | { 44 | case "Input": 45 | foreach (string input in Input) 46 | { 47 | WriteObject(_parser.ParseString(templateName: $"template_{_templateCount}", input)); 48 | } 49 | return; 50 | 51 | case "Path": 52 | foreach (string path in Path) 53 | { 54 | WriteObject(_parser.ParseFile(path)); 55 | } 56 | return; 57 | 58 | case "Uri": 59 | foreach (Uri uri in Uri) 60 | { 61 | WriteObject(_parser.ParseUri(uri)); 62 | } 63 | return; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/pester/Example.Tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | 5 | BeforeDiscovery { 6 | # Test cases come from the examples folder 7 | $exampleDir = (Resolve-Path "$PSScriptRoot/../../examples").Path 8 | $examples = Get-ChildItem -LiteralPath $exampleDir 9 | 10 | $testCases = $examples | ForEach-Object { 11 | $basePath = $_.FullName 12 | $templatePath = Join-Path -Path $basePath -ChildPath 'template.json' 13 | 14 | if (Test-Path -LiteralPath $templatePath) 15 | { 16 | @{ Name = $_.Name; ExamplePath = $basePath; UseHashtable = $_.Name -eq 'simple-storage-account' } 17 | } 18 | } 19 | } 20 | 21 | BeforeAll { 22 | Import-Module "$PSScriptRoot/../tools/TestHelper.psm1" 23 | } 24 | 25 | Describe "Full ARM template conversions using examples" { 26 | It "Example : PSArm script at evaluates equivalently to " -TestCases $testCases { 27 | param([string]$Name, [string]$ExamplePath, [bool]$UseHashtable) 28 | 29 | $templatePath = Join-Path -Path $ExamplePath -ChildPath 'template.json' 30 | $parameterPath = Join-Path -Path $ExamplePath -ChildPath 'parameters.json' 31 | 32 | if (Test-Path $parameterPath) 33 | { 34 | $jsonParams = if ($PSEdition -eq 'Core') { @{ AsHashtable = $UseHashtable } } else { @{} } 35 | $parameters = Get-Content -Raw $parameterPath | ConvertFrom-Json @jsonParams 36 | } 37 | 38 | $armObject = Publish-PSArmTemplate -TemplatePath $ExamplePath -Parameters $parameters -NoWriteFile -NoHashTemplate -PassThru 39 | 40 | # Deal with PSVersion separately since otherwise tests run across powershell versions will fail 41 | $metadataPSVersion = $armObject.Metadata.GeneratorMetadata['psarm-psversion'].Value 42 | $armObject.Metadata.GeneratorMetadata.Remove('psarm-psversion') 43 | 44 | $generatedJson = $armObject.ToJson() 45 | $referenceJson = Get-Content -Raw -LiteralPath $templatePath | ConvertFrom-Json 46 | 47 | Assert-StructurallyEqual -ComparisonObject $referenceJson -JsonObject $generatedJson 48 | $metadataPSVersion | Should -BeExactly $PSVersionTable.PSVersion 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Types/ArmBuiltinTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Azure.Bicep.Types.Concrete; 6 | using System; 7 | 8 | namespace PSArm.Types 9 | { 10 | internal static class ArmBuiltinTypeExtensions 11 | { 12 | public static string AsPowerShellTypeString(this BuiltInTypeKind builtinType) 13 | { 14 | switch (builtinType) 15 | { 16 | case BuiltInTypeKind.Object: 17 | case BuiltInTypeKind.Null: 18 | case BuiltInTypeKind.Any: 19 | case BuiltInTypeKind.ResourceRef: 20 | return "object"; 21 | 22 | case BuiltInTypeKind.Array: 23 | return "array"; 24 | 25 | case BuiltInTypeKind.Bool: 26 | return "bool"; 27 | 28 | case BuiltInTypeKind.Int: 29 | return "int"; 30 | 31 | case BuiltInTypeKind.String: 32 | return "string"; 33 | 34 | default: 35 | throw new ArgumentException($"Unknown ARM builtin type: '{builtinType}'"); 36 | } 37 | } 38 | 39 | public static ArmType? AsArmType(this BuiltInTypeKind builtinType) 40 | { 41 | switch (builtinType) 42 | { 43 | case BuiltInTypeKind.Any: 44 | return null; 45 | 46 | case BuiltInTypeKind.Array: 47 | return ArmType.Array; 48 | 49 | case BuiltInTypeKind.Bool: 50 | return ArmType.Bool; 51 | 52 | case BuiltInTypeKind.Int: 53 | return ArmType.Int; 54 | 55 | case BuiltInTypeKind.Null: 56 | return ArmType.Null; 57 | 58 | case BuiltInTypeKind.Object: 59 | return ArmType.Object; 60 | 61 | case BuiltInTypeKind.ResourceRef: 62 | return null; 63 | 64 | case BuiltInTypeKind.String: 65 | return ArmType.String; 66 | 67 | default: 68 | throw new ArgumentException($"Unknown ARM builtin type: '{builtinType}'"); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.azure-pipelines/release.yaml: -------------------------------------------------------------------------------- 1 | variables: 2 | # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds 3 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 4 | isManualBuild: $[eq(variables['Build.Reason'], 'Manual')] 5 | 6 | resources: 7 | repositories: 8 | - repository: ComplianceRepo 9 | type: github 10 | endpoint: ComplianceGHRepo 11 | name: PowerShell/compliance 12 | 13 | stages: 14 | 15 | - template: templates/buildAndTest.yaml 16 | 17 | - stage: Signing 18 | condition: eq(variables.isManualBuild, true) 19 | dependsOn: Build 20 | jobs: 21 | - job: CodeSigning 22 | displayName: Code Signing 23 | pool: 24 | name: Package ES Standard Build 25 | demands: DotNetFramework 26 | variables: 27 | - group: ESRP 28 | steps: 29 | - template: templates/steps/codeSigning.yaml 30 | 31 | - stage: Compliance 32 | dependsOn: 33 | - Signing 34 | - Build 35 | condition: in(dependencies.Signing.result, 'Succeeded', 'Skipped') 36 | variables: 37 | signingSucceeded: eq(dependencies.Signing.result, 'Succeeded') 38 | jobs: 39 | - job: Compliance 40 | pool: 41 | name: Package ES Standard Build 42 | demands: DotNetFramework 43 | variables: 44 | - group: ESRP 45 | steps: 46 | - template: templates/steps/releaseCompliance.yaml 47 | parameters: 48 | ${{ if ne(variables.signingSucceeded, true) }}: 49 | artifact: unsigned 50 | 51 | # TODO: Automate release publication by implementing the template steps 52 | # - stage: Publish 53 | # dependsOn: 54 | # - Tests 55 | # - Signing 56 | # jobs: 57 | # - deployment: PublishToGitHub 58 | # displayName: Publish to GitHub 59 | # environment: PSArm release approval 60 | # strategy: 61 | # runOnce: 62 | # deploy: 63 | # steps: 64 | # - template: templates/steps/download.yaml 65 | # - template: templates/steps/publishToGitHub.yaml 66 | # - job: PublishToGallery 67 | # displayName: Publish to PSGallery 68 | # steps: 69 | # - template: templates/steps/download.yaml 70 | # - template: templates/steps/download.yaml 71 | -------------------------------------------------------------------------------- /src/Templates/Operations/ArmFunctionCallExpression.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | 10 | namespace PSArm.Templates.Operations 11 | { 12 | public class ArmFunctionCallExpression : ArmOperation 13 | { 14 | public ArmFunctionCallExpression() 15 | { 16 | Arguments = new List(); 17 | } 18 | 19 | public ArmFunctionCallExpression(IArmString function, IReadOnlyList arguments) 20 | : this() 21 | { 22 | Function = function; 23 | Arguments.AddRange(arguments); 24 | } 25 | 26 | public IArmString Function { get; set; } 27 | 28 | public List Arguments { get; } 29 | 30 | public override string ToInnerExpressionString() 31 | { 32 | var sb = new StringBuilder(); 33 | sb.Append(Function.ToIdentifierString()).Append('('); 34 | 35 | if (Arguments.Count > 0) 36 | { 37 | sb.Append(Arguments[0].ToInnerExpressionString()); 38 | 39 | for (int i = 1; i < Arguments.Count; i++) 40 | { 41 | sb.Append(", ").Append(Arguments[i].ToInnerExpressionString()); 42 | } 43 | } 44 | 45 | sb.Append(')'); 46 | return sb.ToString(); 47 | } 48 | 49 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitFunctionCall(this); 50 | 51 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 52 | { 53 | if (Arguments is null) 54 | { 55 | return this; 56 | } 57 | 58 | var args = new ArmExpression[Arguments.Count]; 59 | for (int i = 0; i < Arguments.Count; i++) 60 | { 61 | args[i] = (ArmExpression)Arguments[i].Instantiate(parameters); 62 | } 63 | 64 | return new ArmFunctionCallExpression( 65 | (IArmString)Function.Instantiate(parameters), 66 | args); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Schema/Keyword/BicepKeywordSchemaBuilder.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Azure.Bicep.Types.Concrete; 6 | using PSArm.Types; 7 | using System; 8 | 9 | namespace PSArm.Schema.Keyword 10 | { 11 | internal class BicepKeywordSchemaBuilder : BicepArmTypeVisitor 12 | { 13 | private static readonly BicepKeywordParameterBuilder s_parameterBuilder = new BicepKeywordParameterBuilder(); 14 | 15 | private static BicepKeywordSchemaBuilder Value { get; } = new BicepKeywordSchemaBuilder(); 16 | 17 | public static DslKeywordSchema GetKeywordSchemaForBicepType(TypeBase bicepType) 18 | => Value.Visit(bicepType); 19 | 20 | private BicepKeywordSchemaBuilder() 21 | { 22 | } 23 | 24 | protected override DslKeywordSchema VisitArray(ArrayType armArray) 25 | { 26 | return Visit(armArray.ItemType.Type); 27 | } 28 | 29 | protected override DslKeywordSchema VisitBuiltin(BuiltInType armBuiltin) 30 | { 31 | return new OpenKeywordSchema(s_parameterBuilder.Visit(armBuiltin), useParametersForCompletions: false); 32 | } 33 | 34 | protected override DslKeywordSchema VisitDiscriminatedObject(DiscriminatedObjectType armDiscriminatedObject) 35 | { 36 | return new BicepDiscriminatedObjectKeywordSchema(armDiscriminatedObject); 37 | } 38 | 39 | protected override DslKeywordSchema VisitObject(ObjectType armObject) 40 | { 41 | return new BicepObjectKeywordSchema(armObject); 42 | } 43 | 44 | protected override DslKeywordSchema VisitResource(ResourceType armResource) 45 | { 46 | throw new ArgumentException($"Cannot generate schema for ARM Resource type"); 47 | } 48 | 49 | protected override DslKeywordSchema VisitString(StringLiteralType armString) 50 | { 51 | return new OpenKeywordSchema(s_parameterBuilder.Visit(armString), useParametersForCompletions: true); 52 | } 53 | 54 | protected override DslKeywordSchema VisitUnion(UnionType armUnion) 55 | { 56 | return new OpenKeywordSchema(s_parameterBuilder.Visit(armUnion), useParametersForCompletions: true); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/New-PSArmTemplate.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSArm.dll-Help.xml 3 | Module Name: PSArm 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # New-PSArmTemplate 9 | 10 | ## SYNOPSIS 11 | Define an Azure ARM template in script. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | New-PSArmTemplate [-Name ] [-Body] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Defines an Azure ARM template in the body scriptblock. 21 | This cmdlet is intended to be used in the form of the `Arm` keyword for more fluent reading. 22 | 23 | ## EXAMPLES 24 | 25 | ### Example 1 26 | ```powershell 27 | Arm { 28 | Resource $storageAccountName -Provider 'Microsoft.Storage' -Type 'storageAccounts' -apiVersion '2019-06-01' -kind 'StorageV2' -Location 'WestUS2' { 29 | ArmSku 'Standard_LRS' 30 | Properties { 31 | accessTier 'Hot' 32 | minimumTLSVersion 'TLS1_2' 33 | supportsHTTPSTrafficOnly 1 34 | allowBlobPublicAccess 1 35 | allowSharedKeyAccess 1 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | Declares a very simple ARM template that will deploy a new storage account. 42 | 43 | ## PARAMETERS 44 | 45 | ### -Body 46 | The ARM template declaration in the PSArm DSL. 47 | 48 | ```yaml 49 | Type: ScriptBlock 50 | Parameter Sets: (All) 51 | Aliases: 52 | 53 | Required: True 54 | Position: 0 55 | Default value: None 56 | Accept pipeline input: False 57 | Accept wildcard characters: False 58 | ``` 59 | 60 | ### -Name 61 | The name of the ARM template. 62 | If not provided, the name of the declaring file will be used. 63 | If multiple PSArm templates are declared in the same file, 64 | numbers will be added. 65 | 66 | ```yaml 67 | Type: String 68 | Parameter Sets: (All) 69 | Aliases: 70 | 71 | Required: False 72 | Position: Named 73 | Default value: None 74 | Accept pipeline input: False 75 | Accept wildcard characters: False 76 | ``` 77 | 78 | ### CommonParameters 79 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 80 | 81 | ## INPUTS 82 | 83 | ### None 84 | 85 | ## OUTPUTS 86 | 87 | ### PSArm.Templates.ArmTemplate 88 | ## NOTES 89 | 90 | ## RELATED LINKS 91 | -------------------------------------------------------------------------------- /src/Types/ArmTypeConversion.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using System; 6 | using System.Security; 7 | 8 | namespace PSArm.Types 9 | { 10 | public static class ArmTypeConversion 11 | { 12 | public static bool TryConvertToArmType(Type type, out ArmType? armType) 13 | { 14 | if (type is null) 15 | { 16 | armType = null; 17 | return false; 18 | } 19 | 20 | switch (Type.GetTypeCode(type)) 21 | { 22 | case TypeCode.Char: 23 | case TypeCode.String: 24 | armType = ArmType.String; 25 | return true; 26 | 27 | case TypeCode.Int16: 28 | case TypeCode.Int32: 29 | case TypeCode.Int64: 30 | case TypeCode.UInt16: 31 | case TypeCode.UInt32: 32 | case TypeCode.UInt64: 33 | case TypeCode.SByte: 34 | case TypeCode.Byte: 35 | armType = ArmType.Int; 36 | return true; 37 | 38 | case TypeCode.Boolean: 39 | armType = ArmType.Bool; 40 | return true; 41 | } 42 | 43 | if (type == typeof(object)) 44 | { 45 | armType = ArmType.Object; 46 | return true; 47 | } 48 | 49 | if (type == typeof(SecureString)) 50 | { 51 | armType = ArmType.SecureString; 52 | return true; 53 | } 54 | 55 | if (type == typeof(SecureObject)) 56 | { 57 | armType = ArmType.SecureObject; 58 | return true; 59 | } 60 | 61 | if (type == typeof(Array)) 62 | { 63 | armType = ArmType.Array; 64 | return true; 65 | } 66 | 67 | armType = null; 68 | return false; 69 | } 70 | 71 | public static bool TryConvertToArmType(string type, out ArmType? armType) 72 | { 73 | if (Enum.TryParse(type, out ArmType parsedType)) 74 | { 75 | armType = parsedType; 76 | return true; 77 | } 78 | 79 | armType = null; 80 | return false; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/wvd-backplane/wvd-backplane.psarm.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] 3 | $hostpoolName = 'myFirstHostpool', 4 | 5 | [string] 6 | $appgroupName = 'myFirstAppGroup', 7 | 8 | [string] 9 | $workspaceName = 'myFirstWorkspace' 10 | ) 11 | 12 | Arm { 13 | param( 14 | 15 | [ArmParameter[string]] 16 | $hostpoolFriendlyName = 'My Bicep created Host pool', 17 | 18 | [ArmParameter[string]] 19 | $appgroupNameFriendlyName = 'My Bicep created AppGroup', 20 | 21 | [ArmParameter[string]] 22 | $workspaceNameFriendlyName = 'My Bicep created Workspace', 23 | 24 | [ValidateSet('Desktop', 'RemoteApp')] 25 | [ArmParameter[string]] 26 | $applicationgrouptype = 'Desktop', 27 | 28 | [ValidateSet('Desktop', 'RailApplications')] 29 | [ArmParameter[string]] 30 | $preferredAppGroupType = 'Desktop', 31 | 32 | [ArmParameter[string]] 33 | $wvdbackplanelocation = 'eastus', 34 | 35 | [ArmParameter[string]] 36 | $hostPoolType = 'pooled', 37 | 38 | [ArmParameter[string]] 39 | $loadBalancerType = 'BreadthFirst' 40 | ) 41 | 42 | Resource $hostpoolName -Namespace 'Microsoft.DesktopVirtualization' -Type 'hostPools' -ApiVersion '2019-12-10-preview' -Location $wvdbackplanelocation { 43 | properties { 44 | friendlyName $hostpoolFriendlyName 45 | hostPoolType $hostPoolType 46 | loadBalancerType $loadBalancerType 47 | preferredAppGroupType $preferredAppGroupType 48 | } 49 | } 50 | 51 | Resource $appgroupName -Namespace 'Microsoft.DesktopVirtualization' -Type 'applicationGroups' -ApiVersion '2019-12-10-preview' -Location $wvdbackplanelocation { 52 | properties { 53 | friendlyName $appgroupNameFriendlyName 54 | applicationGroupType $applicationgrouptype 55 | hostPoolArmPath (resourceId 'Microsoft.DesktopVirtualization/hostPools' $hostpoolName) 56 | } 57 | DependsOn @( 58 | resourceId 'Microsoft.DesktopVirtualization/hostPools' $hostpoolName 59 | ) 60 | } 61 | 62 | Resource $workspaceName -Namespace 'Microsoft.DesktopVirtualization' -Type 'workspaces' -ApiVersion '2019-12-10-preview' -Location $wvdbackplanelocation { 63 | properties { 64 | friendlyName $workspaceNameFriendlyName 65 | applicationGroupReferences (resourceId 'Microsoft.DesktopVirtualization/applicationGroups' $appgroupName) 66 | } 67 | DependsOn @( 68 | resourceId 'Microsoft.DesktopVirtualization/applicationGroups' $appgroupName 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Types/ArmStringConverter.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates; 6 | using PSArm.Templates.Operations; 7 | using PSArm.Templates.Primitives; 8 | using System; 9 | using System.Management.Automation; 10 | 11 | namespace PSArm.Types 12 | { 13 | public class ArmStringConverter : PSTypeConverter 14 | { 15 | public override bool CanConvertFrom(object sourceValue, Type destinationType) 16 | { 17 | if (sourceValue is null) 18 | { 19 | return false; 20 | } 21 | 22 | if (!typeof(IArmString).IsAssignableFrom(destinationType)) 23 | { 24 | return false; 25 | } 26 | 27 | Type sourceType = sourceValue.GetType(); 28 | 29 | return sourceType == typeof(string) 30 | || sourceType == typeof(ArmStringLiteral) 31 | || sourceType == typeof(ArmParameter) 32 | || sourceType == typeof(ArmVariable) 33 | || typeof(ArmOperation).IsAssignableFrom(sourceType); 34 | } 35 | 36 | public override bool CanConvertTo(object sourceValue, Type destinationType) 37 | { 38 | return false; 39 | } 40 | 41 | public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) 42 | { 43 | switch (sourceValue) 44 | { 45 | case string str: 46 | return new ArmStringLiteral(str); 47 | 48 | case ArmStringLiteral armStr: 49 | return armStr; 50 | 51 | case ArmOperation armExpr: 52 | return armExpr; 53 | 54 | case ArmParameter armParameter: 55 | return (ArmParameterReferenceExpression)armParameter; 56 | 57 | case ArmVariable armVariable: 58 | return (ArmVariableReferenceExpression)armVariable; 59 | 60 | default: 61 | throw new InvalidCastException($"Unable to cast value '{sourceValue}' of type '{sourceValue.GetType()}' to type '{destinationType}'"); 62 | } 63 | } 64 | 65 | public override object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) 66 | { 67 | throw new NotImplementedException(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Templates/Builders/ArmBuilder.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | namespace PSArm.Templates.Builders 10 | { 11 | public class ArmBuilder where TObject : ArmObject 12 | { 13 | private readonly TObject _armObject; 14 | 15 | public ArmBuilder(TObject armObject) 16 | { 17 | _armObject = armObject; 18 | } 19 | 20 | public ArmBuilder AddEntry(ArmEntry entry) 21 | { 22 | return entry.IsArrayElement 23 | ? AddArrayElement(entry.Key, entry.Value) 24 | : AddSingleElement(entry.Key, entry.Value); 25 | } 26 | 27 | public ArmBuilder AddSingleElement(IArmString key, ArmElement value) 28 | { 29 | if (!_armObject.TryGetValue(key, out ArmElement existingValue)) 30 | { 31 | _armObject[key] = value; 32 | } 33 | else if (existingValue is ArmObject existingObject 34 | && value is ArmObject newObject) 35 | { 36 | foreach (KeyValuePair newEntry in newObject) 37 | { 38 | existingObject.Add(newEntry); 39 | } 40 | } 41 | else 42 | { 43 | // This will throw because the key already exists 44 | // for now we let the underlying dictionary do this, 45 | // since the error makes sense 46 | _armObject.Add(key, value); 47 | } 48 | 49 | return this; 50 | } 51 | 52 | public ArmBuilder AddArrayElement(IArmString key, ArmElement value) 53 | { 54 | if (_armObject.TryGetValue(key, out ArmElement existingElement)) 55 | { 56 | if (!(existingElement is IList existingArray)) 57 | { 58 | throw new InvalidOperationException($"Non-array entry already exists for key '{key}'"); 59 | } 60 | 61 | existingArray.Add(value); 62 | return this; 63 | } 64 | 65 | _armObject[key] = new ArmArray() 66 | { 67 | value 68 | }; 69 | return this; 70 | } 71 | 72 | public TObject Build() 73 | { 74 | return _armObject; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Schema/ArmResourceName.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Internal; 6 | using PSArm.Templates.Primitives; 7 | using System; 8 | 9 | namespace PSArm.Schema 10 | { 11 | public readonly struct ArmResourceName : IEquatable 12 | { 13 | public static ArmResourceName CreateFromFullyQualifiedName(string fullyQualifiedName) 14 | { 15 | int providerNameStartIndex = fullyQualifiedName.IndexOf('/') + 1; 16 | int apiVersionStartIndex = fullyQualifiedName.IndexOf('@', providerNameStartIndex) + 1; 17 | 18 | string providerNamespace = fullyQualifiedName.Substring(0, providerNameStartIndex - 1); 19 | string providerName = fullyQualifiedName.Substring(providerNameStartIndex, apiVersionStartIndex - providerNameStartIndex - 1); 20 | string providerApiVersion = fullyQualifiedName.Substring(apiVersionStartIndex); 21 | return new ArmResourceName(providerNamespace, providerName, providerApiVersion); 22 | } 23 | 24 | public static ArmResourceName CreateFromArmStrings(IArmString resourceNamespace, IArmString type, IArmString apiVersion) 25 | { 26 | return new ArmResourceName( 27 | (resourceNamespace as ArmStringLiteral)?.Value, 28 | (type as ArmStringLiteral)?.Value, 29 | (apiVersion as ArmStringLiteral)?.Value); 30 | } 31 | 32 | public ArmResourceName(string resourceNamespace, string type, string apiVersion) 33 | { 34 | Namespace = resourceNamespace; 35 | Type = type; 36 | ApiVersion = apiVersion; 37 | } 38 | 39 | public readonly string Namespace; 40 | 41 | public readonly string Type; 42 | 43 | public readonly string ApiVersion; 44 | 45 | public bool Equals(ArmResourceName other) 46 | { 47 | return string.Equals(Namespace, other.Namespace, StringComparison.OrdinalIgnoreCase) 48 | && string.Equals(Type, other.Type, StringComparison.OrdinalIgnoreCase) 49 | && string.Equals(ApiVersion, other.ApiVersion, StringComparison.OrdinalIgnoreCase); 50 | } 51 | 52 | public override int GetHashCode() 53 | { 54 | return HashCodeHelpers.CombineHashCodes(Namespace, Type, ApiVersion); 55 | } 56 | 57 | public override bool Equals(object obj) 58 | { 59 | return obj is ArmResourceName otherARN 60 | && Equals(otherARN); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Parameterization/PSArmParameterization.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Internal; 6 | using PSArm.Templates; 7 | using PSArm.Types; 8 | using System.Management.Automation.Language; 9 | 10 | namespace PSArm.Parameterization 11 | { 12 | internal static class PSArmParameterization 13 | { 14 | public static PSArmVarType GetPSArmVarType(ParameterAst parameterAst) 15 | { 16 | if (!TryGetTypeConstraint(parameterAst, out TypeConstraintAst typeConstraintAst)) 17 | { 18 | return PSArmVarType.None; 19 | } 20 | 21 | if (IsVariableType(typeConstraintAst.TypeName)) 22 | { 23 | return PSArmVarType.Variable; 24 | } 25 | 26 | if (IsParameterType(typeConstraintAst.TypeName)) 27 | { 28 | return PSArmVarType.Parameter; 29 | } 30 | 31 | return PSArmVarType.None; 32 | } 33 | 34 | private static bool TryGetTypeConstraint(ParameterAst parameter, out TypeConstraintAst typeConstraint) 35 | { 36 | if (parameter.Attributes is not null) 37 | { 38 | foreach (AttributeBaseAst attributeAst in parameter.Attributes) 39 | { 40 | if (attributeAst is TypeConstraintAst foundTypeConstraint) 41 | { 42 | typeConstraint = foundTypeConstraint; 43 | return true; 44 | } 45 | } 46 | } 47 | 48 | typeConstraint = null; 49 | return false; 50 | } 51 | 52 | private static bool IsVariableType(ITypeName typeName) 53 | { 54 | return typeName is TypeName simpleTypeName 55 | && (simpleTypeName.FullName.Is(ArmTypeAccelerators.ArmVariable) || simpleTypeName.GetReflectionType() == typeof(ArmVariable)); 56 | } 57 | 58 | private static bool IsParameterType(ITypeName typeName) 59 | { 60 | if (typeName is not GenericTypeName genericType) 61 | { 62 | return false; 63 | } 64 | 65 | if (genericType.FullName.Is(ArmTypeAccelerators.ArmParameter)) 66 | { 67 | return true; 68 | } 69 | 70 | return genericType.GetReflectionType()?.GetGenericTypeDefinition() == typeof(ArmParameter<>); 71 | } 72 | } 73 | 74 | internal enum PSArmVarType 75 | { 76 | None = 0, 77 | Variable, 78 | Parameter, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Parameterization/ReferenceCollectingArmVisitor.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Operations; 6 | using PSArm.Templates.Primitives; 7 | using PSArm.Templates.Visitors; 8 | using System.Collections.Generic; 9 | 10 | namespace PSArm.Parameterization 11 | { 12 | internal class ReferenceCollectingArmVisitor : ArmTravsersingVisitor 13 | { 14 | private readonly Dictionary> _variableReferences; 15 | 16 | private readonly Dictionary> _parameterReferences; 17 | 18 | public ReferenceCollectingArmVisitor() 19 | { 20 | _variableReferences = new Dictionary>(); 21 | _parameterReferences = new Dictionary>(); 22 | } 23 | 24 | public IReadOnlyDictionary> Variables => _variableReferences; 25 | 26 | public IReadOnlyDictionary> Parameters => _parameterReferences; 27 | 28 | public void Reset() 29 | { 30 | _variableReferences.Clear(); 31 | _parameterReferences.Clear(); 32 | } 33 | 34 | public override VisitAction VisitVariableReference(ArmVariableReferenceExpression variableReference) 35 | { 36 | if (_variableReferences.TryGetValue(variableReference.ReferenceName, out List references)) 37 | { 38 | references.Add(variableReference); 39 | return VisitAction.Continue; 40 | } 41 | 42 | references = new List { variableReference }; 43 | _variableReferences[variableReference.ReferenceName] = references; 44 | return VisitAction.Continue; 45 | } 46 | 47 | public override VisitAction VisitParameterReference(ArmParameterReferenceExpression parameterReference) 48 | { 49 | if (_parameterReferences.TryGetValue(parameterReference.ReferenceName, out List references)) 50 | { 51 | references.Add(parameterReference); 52 | return VisitAction.Continue; 53 | } 54 | 55 | references = new List { parameterReference }; 56 | _parameterReferences[parameterReference.ReferenceName] = references; 57 | return VisitAction.Continue; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "PowerShell Launch Current File", 9 | "type": "PowerShell", 10 | "request": "launch", 11 | "script": "${file}", 12 | "cwd": "${file}" 13 | }, 14 | { 15 | "name": ".NET Core Launch (console)", 16 | "type": "coreclr", 17 | "request": "launch", 18 | "WARNING01": "*********************************************************************************", 19 | "WARNING02": "The C# extension was unable to automatically decode projects in the current", 20 | "WARNING03": "workspace to create a runnable launch.json file. A template launch.json file has", 21 | "WARNING04": "been created as a placeholder.", 22 | "WARNING05": "", 23 | "WARNING06": "If OmniSharp is currently unable to load your project, you can attempt to resolve", 24 | "WARNING07": "this by restoring any missing project dependencies (example: run 'dotnet restore')", 25 | "WARNING08": "and by fixing any reported errors from building the projects in your workspace.", 26 | "WARNING09": "If this allows OmniSharp to now load your project then --", 27 | "WARNING10": " * Delete this file", 28 | "WARNING11": " * Open the Visual Studio Code command palette (View->Command Palette)", 29 | "WARNING12": " * run the command: '.NET: Generate Assets for Build and Debug'.", 30 | "WARNING13": "", 31 | "WARNING14": "If your project requires a more complex launch configuration, you may wish to delete", 32 | "WARNING15": "this configuration and pick a different template using the 'Add Configuration...'", 33 | "WARNING16": "button at the bottom of this file.", 34 | "WARNING17": "*********************************************************************************", 35 | "preLaunchTask": "build", 36 | "program": "${workspaceFolder}/bin/Debug//.dll", 37 | "args": [], 38 | "cwd": "${workspaceFolder}", 39 | "console": "internalConsole", 40 | "stopAtEntry": false 41 | }, 42 | { 43 | "name": ".NET Core Attach", 44 | "type": "coreclr", 45 | "request": "attach", 46 | "processId": "${command:pickProcess}" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /examples/network-interface/network-interface.psarm.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) Microsoft Corporation. 3 | 4 | # Specify the ARM template purely within PowerShell 5 | Arm { 6 | param( 7 | # ValidateSet is turned into "allowedValues" 8 | [ValidateSet('WestUS2', 'CentralUS')] 9 | [ArmParameter[string]] 10 | $rgLocation, 11 | 12 | [ArmParameter[string]] 13 | $namePrefix = 'my', 14 | 15 | [ArmVariable] 16 | $vnetNamespace = 'myVnet/' 17 | ) 18 | 19 | # Use existing PowerShell concepts to make ARM easier 20 | $PSDefaultParameterValues['Resource:Location'] = $rgLocation 21 | 22 | # Resources types, rather than being / have this broken into -Namespace -Type 23 | # Completions are available for Namespace and ApiVersion, and once these are specified, also for Type 24 | Resource (Concat $vnetNamespace $namePrefix '-subnet') -Namespace Microsoft.Network -ApiVersion 2019-11-01 -Type virtualNetworks/subnets { 25 | Properties { 26 | # Each resource defines its properties as commands within its own body 27 | AddressPrefix 10.0.0.0/24 28 | } 29 | } 30 | 31 | # Piping, looping and commands like ForEach-Object all work 32 | '-pip1','-pip2' | ForEach-Object { 33 | Resource (Concat $namePrefix $_) -ApiVersion 2019-11-01 -Namespace Microsoft.Network -Type publicIpAddresses { 34 | Properties { 35 | PublicIPAllocationMethod Dynamic 36 | } 37 | } 38 | } 39 | 40 | Resource (Concat $namePrefix '-nic') -ApiVersion 2019-11-01 -Namespace Microsoft.Network -Type networkInterfaces { 41 | Properties { 42 | # IpConfigurations is an array property, but PSArm knows this 43 | # All occurences of array properties will be collected into an array when the template is published 44 | IpConfigurations { 45 | Name 'myConfig' 46 | properties { 47 | PrivateIPAllocationMethod Dynamic 48 | 49 | # ARM expressions can be expressed in PowerShell 50 | # The subnet ID here is: [resourceId('Microsoft.Network/virtualNetworks/subnets', concat(variables('vnetNamespace'), variables('namePrefix'), '-subnet'))] 51 | Subnet { 52 | id (ResourceId 'Microsoft.Network/virtualNetworks/subnets' (Concat $vnetNamespace $namePrefix '-subnet')) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | Output 'nicResourceId' -Type 'string' -Value (ResourceId 'Microsoft.Network/networkInterfaces' (Concat $namePrefix '-nic')) 60 | } 61 | -------------------------------------------------------------------------------- /src/Types/ArmTypeAccelerators.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Management.Automation; 9 | using System.Reflection; 10 | 11 | namespace PSArm.Types 12 | { 13 | /// 14 | /// Lists and loads/unloads type accelerators for ARM. 15 | /// 16 | public class ArmTypeAccelerators : IModuleAssemblyInitializer, IModuleAssemblyCleanup 17 | { 18 | /// 19 | /// Type accelerator for an ARM variable. 20 | /// 21 | internal const string ArmVariable = "ArmVariable"; 22 | 23 | /// 24 | /// Type accelerator for an ARM parameter. 25 | /// 26 | internal const string ArmParameter = "ArmParameter"; 27 | 28 | internal const string SecureObject = "SecureObject"; 29 | 30 | private static Type s_psTypeAcceleratorsType = typeof(PSObject).Assembly 31 | .GetType("System.Management.Automation.TypeAccelerators"); 32 | 33 | private static MethodInfo s_psTypeAcceleratorsAddMethod = s_psTypeAcceleratorsType.GetMethod("Add"); 34 | 35 | private static MethodInfo s_psTypeAcceleratorsRemoveMethod = s_psTypeAcceleratorsType.GetMethod("Remove"); 36 | 37 | private static IReadOnlyDictionary s_armTypeAccelerators = new Dictionary 38 | { 39 | { ArmVariable, typeof(ArmVariable) }, 40 | { ArmParameter, typeof(ArmParameter<>) }, 41 | { SecureObject, typeof(SecureObject) }, 42 | }; 43 | 44 | /// 45 | /// Install the ARM type accelerators into PowerShell's type accelerator dictionary. 46 | /// 47 | public void OnImport() 48 | { 49 | var paramArray = new object[2]; 50 | foreach (KeyValuePair armAccelerator in s_armTypeAccelerators) 51 | { 52 | paramArray[0] = armAccelerator.Key; 53 | paramArray[1] = armAccelerator.Value; 54 | s_psTypeAcceleratorsAddMethod.Invoke(obj: null, paramArray); 55 | } 56 | } 57 | 58 | /// 59 | /// Remove the ARM type accelerators from PowerShell's type accelerator dictionary. 60 | /// 61 | public void OnRemove(PSModuleInfo module) 62 | { 63 | var paramArray = new object[1]; 64 | foreach (string accelerator in s_armTypeAccelerators.Keys) 65 | { 66 | paramArray[0] = accelerator; 67 | s_psTypeAcceleratorsRemoveMethod.Invoke(obj: null, paramArray); 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /docs/New-PSArmSku.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSArm.dll-Help.xml 3 | Module Name: PSArm 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # New-PSArmSku 9 | 10 | ## SYNOPSIS 11 | Declare the SKU of the given resource 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | New-PSArmSku [-Name] [-Tier ] [-Size ] [-Family ] 17 | [-Capacity ] [] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | The `ArmSku` keyword specifies what SKU the given resource is. 22 | Not all `ArmSku` parameters will apply to every resource 23 | 24 | ## EXAMPLES 25 | 26 | ### Example 1 27 | ```powershell 28 | ArmSku 'Basic' -Tier 'Regional' 29 | ``` 30 | 31 | Declares the resource to have a SKU with name `Basic` and tier `Regional` 32 | 33 | ## PARAMETERS 34 | 35 | ### -Capacity 36 | The SKU capacity required 37 | 38 | ```yaml 39 | Type: IArmString 40 | Parameter Sets: (All) 41 | Aliases: 42 | 43 | Required: False 44 | Position: Named 45 | Default value: None 46 | Accept pipeline input: False 47 | Accept wildcard characters: False 48 | ``` 49 | 50 | ### -Family 51 | The SKU family required 52 | 53 | ```yaml 54 | Type: IArmString 55 | Parameter Sets: (All) 56 | Aliases: 57 | 58 | Required: False 59 | Position: Named 60 | Default value: None 61 | Accept pipeline input: False 62 | Accept wildcard characters: False 63 | ``` 64 | 65 | ### -Name 66 | The SKU name required 67 | 68 | ```yaml 69 | Type: IArmString 70 | Parameter Sets: (All) 71 | Aliases: 72 | 73 | Required: True 74 | Position: 0 75 | Default value: None 76 | Accept pipeline input: False 77 | Accept wildcard characters: False 78 | ``` 79 | 80 | ### -Size 81 | The SKU size required 82 | 83 | ```yaml 84 | Type: IArmString 85 | Parameter Sets: (All) 86 | Aliases: 87 | 88 | Required: False 89 | Position: Named 90 | Default value: None 91 | Accept pipeline input: False 92 | Accept wildcard characters: False 93 | ``` 94 | 95 | ### -Tier 96 | The SKU tier required 97 | 98 | ```yaml 99 | Type: IArmString 100 | Parameter Sets: (All) 101 | Aliases: 102 | 103 | Required: False 104 | Position: Named 105 | Default value: None 106 | Accept pipeline input: False 107 | Accept wildcard characters: False 108 | ``` 109 | 110 | ### CommonParameters 111 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 112 | 113 | ## INPUTS 114 | 115 | ### None 116 | 117 | ## OUTPUTS 118 | 119 | ### PSArm.Templates.PRimitives.ArmEntry 120 | ## NOTES 121 | 122 | ## RELATED LINKS 123 | -------------------------------------------------------------------------------- /examples/wvd-backplane/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "psarm", 7 | "version": "0.1.0.0" 8 | } 9 | }, 10 | "resources": [ 11 | { 12 | "name": "wvd-backplane", 13 | "type": "Microsoft.Resources/deployments", 14 | "apiVersion": "2019-10-01", 15 | "properties": { 16 | "mode": "Incremental", 17 | "expressionEvaluationOptions": { 18 | "scope": "inner" 19 | }, 20 | "template": { 21 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 22 | "contentVersion": "1.0.0.0", 23 | "resources": [ 24 | { 25 | "name": "myFirstHostpool", 26 | "apiVersion": "2019-12-10-preview", 27 | "type": "Microsoft.DesktopVirtualization/hostPools", 28 | "location": "eastus", 29 | "properties": { 30 | "friendlyName": "My Bicep created Host pool", 31 | "hostPoolType": "pooled", 32 | "loadBalancerType": "BreadthFirst", 33 | "preferredAppGroupType": "Desktop" 34 | } 35 | }, 36 | { 37 | "name": "myFirstAppGroup", 38 | "apiVersion": "2019-12-10-preview", 39 | "type": "Microsoft.DesktopVirtualization/applicationGroups", 40 | "location": "eastus", 41 | "properties": { 42 | "friendlyName": "My Bicep created AppGroup", 43 | "applicationGroupType": "Desktop", 44 | "hostPoolArmPath": "[resourceId('Microsoft.DesktopVirtualization/hostPools', 'myFirstHostpool')]" 45 | }, 46 | "dependsOn": [ 47 | "[resourceId('Microsoft.DesktopVirtualization/hostPools', 'myFirstHostpool')]" 48 | ] 49 | }, 50 | { 51 | "name": "myFirstWorkspace", 52 | "apiVersion": "2019-12-10-preview", 53 | "type": "Microsoft.DesktopVirtualization/workspaces", 54 | "location": "eastus", 55 | "properties": { 56 | "friendlyName": "My Bicep created Workspace", 57 | "applicationGroupReferences": [ 58 | "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', 'myFirstAppGroup')]" 59 | ] 60 | }, 61 | "dependsOn": [ 62 | "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', 'myFirstAppGroup')]" 63 | ] 64 | } 65 | ] 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /examples/simple-parameterless/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "psarm", 7 | "version": "0.1.0.0" 8 | } 9 | }, 10 | "resources": [ 11 | { 12 | "name": "Parameterless", 13 | "type": "Microsoft.Resources/deployments", 14 | "apiVersion": "2019-10-01", 15 | "properties": { 16 | "mode": "Incremental", 17 | "expressionEvaluationOptions": { 18 | "scope": "inner" 19 | }, 20 | "template": { 21 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 22 | "contentVersion": "1.0.0.0", 23 | "resources": [ 24 | { 25 | "name": "myVnet/my-subnet", 26 | "apiVersion": "2019-11-01", 27 | "type": "Microsoft.Network/virtualNetworks/subnets", 28 | "properties": { 29 | "addressPrefix": "10.0.0.0/24" 30 | } 31 | }, 32 | { 33 | "name": "my-pip1", 34 | "apiVersion": "2019-11-01", 35 | "type": "Microsoft.Network/publicIPAddresses", 36 | "location": "WestUS2", 37 | "properties": { 38 | "publicIPAllocationMethod": "Dynamic" 39 | } 40 | }, 41 | { 42 | "name": "my-pip2", 43 | "apiVersion": "2019-11-01", 44 | "type": "Microsoft.Network/publicIPAddresses", 45 | "location": "WestUS2", 46 | "properties": { 47 | "publicIPAllocationMethod": "Dynamic" 48 | } 49 | }, 50 | { 51 | "name": "my-nic", 52 | "apiVersion": "2019-11-01", 53 | "type": "Microsoft.Network/networkInterfaces", 54 | "location": "WestUS2", 55 | "properties": { 56 | "ipConfigurations": [ 57 | { 58 | "name": "myConfig", 59 | "properties": { 60 | "privateIPAllocationMethod": "Dynamic", 61 | "subnet": { 62 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'my-nic-subnet')]" 63 | } 64 | } 65 | } 66 | ] 67 | } 68 | } 69 | ], 70 | "outputs": { 71 | "nicResourceId": { 72 | "type": "string", 73 | "value": "[resourceId('Microsoft.Network/networkInterfaces', 'my-nic')]" 74 | } 75 | } 76 | } 77 | } 78 | } 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /docs/ConvertTo-PSArm.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSArm.dll-Help.xml 3 | Module Name: PSArm 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # ConvertTo-PSArm 9 | 10 | ## SYNOPSIS 11 | Convert a PSArm template object to a PSArm script that can generate it. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | ConvertTo-PSArm -InputTemplate [-OutFile ] [-PassThru] [-Force] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Converts a PSArm template object into a PSArm PowerShell script 21 | that, when executed, will generate that template. 22 | This is most useful for converting from ARM JSON templates 23 | after `ConvertFrom-ArmTemplate`. 24 | 25 | ## EXAMPLES 26 | 27 | ### Example 1 28 | ```powershell 29 | PS C:\> ConvertFrom-ArmTemplate -Path ./examples/windows-vm/template.json | ConvertTo-PSArm -OutFile ./windows-vm.psarm.ps1 30 | ``` 31 | 32 | Converts a JSON ARM template file to a PSArm script. 33 | 34 | ## PARAMETERS 35 | 36 | ### -Force 37 | If a file exists in the `OutFile` location, overwrite it. 38 | 39 | ```yaml 40 | Type: SwitchParameter 41 | Parameter Sets: (All) 42 | Aliases: 43 | 44 | Required: False 45 | Position: Named 46 | Default value: None 47 | Accept pipeline input: False 48 | Accept wildcard characters: False 49 | ``` 50 | 51 | ### -InputTemplate 52 | The PSArm template object to convert to PSArm script. 53 | 54 | ```yaml 55 | Type: ArmTemplate[] 56 | Parameter Sets: (All) 57 | Aliases: 58 | 59 | Required: True 60 | Position: Named 61 | Default value: None 62 | Accept pipeline input: True (ByValue) 63 | Accept wildcard characters: False 64 | ``` 65 | 66 | ### -OutFile 67 | The file system location to write the converted template to. 68 | If a file already exists there, this will fail unless `-Force` is used. 69 | 70 | ```yaml 71 | Type: String 72 | Parameter Sets: (All) 73 | Aliases: 74 | 75 | Required: False 76 | Position: Named 77 | Default value: None 78 | Accept pipeline input: False 79 | Accept wildcard characters: False 80 | ``` 81 | 82 | ### -PassThru 83 | If set, the PSArm script will also be written out as a string. 84 | 85 | ```yaml 86 | Type: SwitchParameter 87 | Parameter Sets: (All) 88 | Aliases: 89 | 90 | Required: False 91 | Position: Named 92 | Default value: None 93 | Accept pipeline input: False 94 | Accept wildcard characters: False 95 | ``` 96 | 97 | ### CommonParameters 98 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 99 | 100 | ## INPUTS 101 | 102 | ### PSArm.Templates.ArmTemplate[] 103 | 104 | ## OUTPUTS 105 | 106 | ### System.String 107 | ## NOTES 108 | 109 | ## RELATED LINKS 110 | -------------------------------------------------------------------------------- /src/Parameterization/TemplateReferenceCollector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using PSArm.Internal; 5 | using PSArm.Templates; 6 | using PSArm.Templates.Operations; 7 | using PSArm.Templates.Primitives; 8 | using System.Collections.Generic; 9 | 10 | namespace PSArm.Parameterization 11 | { 12 | internal class TemplateReferenceCollector 13 | { 14 | private readonly ReferenceCollectingArmVisitor _referenceCollectingVisitor; 15 | 16 | public TemplateReferenceCollector() 17 | { 18 | _referenceCollectingVisitor = new ReferenceCollectingArmVisitor(); 19 | } 20 | 21 | public ReferenceCollectionResult CollectReferences(IEnumerable armVariables, IEnumerable armParameters) 22 | { 23 | var variables = armVariables is not null ? new Dictionary>>() : null; 24 | var parameters = armParameters is not null ? new Dictionary>>() : null; 25 | 26 | if (armParameters is not null) 27 | { 28 | foreach (ArmParameter parameter in armParameters) 29 | { 30 | _referenceCollectingVisitor.Reset(); 31 | parameter.DefaultValue?.RunVisit(_referenceCollectingVisitor); 32 | parameters[parameter] = _referenceCollectingVisitor.Parameters.ShallowClone(); 33 | } 34 | } 35 | 36 | if (armVariables is not null) 37 | { 38 | foreach (ArmVariable variable in armVariables) 39 | { 40 | _referenceCollectingVisitor.Reset(); 41 | variable.Value.RunVisit(_referenceCollectingVisitor); 42 | variables[variable] = _referenceCollectingVisitor.Variables.ShallowClone(); 43 | } 44 | } 45 | 46 | return new ReferenceCollectionResult(variables, parameters); 47 | } 48 | 49 | public readonly struct ReferenceCollectionResult 50 | { 51 | public ReferenceCollectionResult( 52 | IReadOnlyDictionary>> variables, 53 | IReadOnlyDictionary>> parameters) 54 | { 55 | Variables = variables; 56 | Parameters = parameters; 57 | } 58 | 59 | public IReadOnlyDictionary>> Variables { get; } 60 | public IReadOnlyDictionary>> Parameters { get; } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/tools/TestHelper.psm1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | function Assert-StructurallyEqual 5 | { 6 | param( 7 | [Parameter(Mandatory)] 8 | [Newtonsoft.Json.Linq.JToken] 9 | $JsonObject, 10 | 11 | [Parameter(Mandatory)] 12 | [object] 13 | $ComparisonObject, 14 | 15 | [Parameter()] 16 | [string] 17 | $Path = '/' 18 | ) 19 | 20 | if ($ComparisonObject -is [array]) 21 | { 22 | $JsonObject.GetType().FullName | Should -BeExactly 'Newtonsoft.Json.Linq.JArray' -Because "array expected at '$Path'" 23 | $JsonObject.Count | Should -BeExactly $ComparisonObject.Count -Because "array should have the correct number of elements at '$Path'" 24 | 25 | for ($i = 0; $i -lt $ComparisonObject.Count; $i++) 26 | { 27 | Assert-StructurallyEqual -JsonObject $JsonObject[$i] -ComparisonObject $ComparisonObject[$i] -Path "$Path.[$i]" 28 | } 29 | 30 | return 31 | } 32 | 33 | if ($JsonObject -is [Newtonsoft.Json.Linq.JObject]) 34 | { 35 | foreach ($key in Get-Member -InputObject $ComparisonObject -MemberType Properties) 36 | { 37 | $name = $key.Name 38 | $JsonObject.ContainsKey($name) | Should -BeTrue -Because "object should contain property '$name' at '$Path'" 39 | } 40 | 41 | foreach ($entry in $JsonObject.GetEnumerator()) 42 | { 43 | $key = $entry.Key 44 | $subObject = $ComparisonObject.$key 45 | 46 | $subObject | Should -Not -BeNullOrEmpty -Because "Property '$key' is present in the JSON object at '$Path.$key'" 47 | 48 | Assert-StructurallyEqual -JsonObject $entry.Value -ComparisonObject $subObject -Path "$Path.$key" 49 | } 50 | 51 | return 52 | } 53 | 54 | if ($JsonObject -is [Newtonsoft.Json.Linq.JValue]) 55 | { 56 | $JsonObject.Value | Should -Be $ComparisonObject -Because "$value should equal $ComparisonObject at '$Path'" 57 | return 58 | } 59 | 60 | Write-Error "$JsonObject is of unexpected type $($JsonObject.GetType().FullName)" 61 | } 62 | 63 | function Assert-EquivalentToTemplate 64 | { 65 | param( 66 | [Parameter(Mandatory)] 67 | [PSArm.Templates.Primitives.ArmElement] 68 | $GeneratedObject, 69 | 70 | [Parameter(Mandatory, ParameterSetName="Definition")] 71 | [string] 72 | $TemplateDefinition, 73 | 74 | [Parameter(Mandatory, ParameterSetName="Path")] 75 | [string] 76 | $TemplatePath 77 | ) 78 | 79 | $generatedJson = $GeneratedObject.ToJson() 80 | 81 | $template = if ($TemplateDefinition) 82 | { 83 | ConvertFrom-Json -InputObject $TemplateDefinition 84 | } 85 | else 86 | { 87 | ConvertFrom-Json -InputObject (Get-Content -Raw $TemplatePath) 88 | } 89 | 90 | Assert-StructurallyEqual -JsonObject $generatedJson -ComparisonObject $template 91 | } 92 | -------------------------------------------------------------------------------- /examples/network-interface/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "psarm", 7 | "version": "0.1.0.0" 8 | } 9 | }, 10 | "resources": [ 11 | { 12 | "name": "network-interface", 13 | "type": "Microsoft.Resources/deployments", 14 | "apiVersion": "2019-10-01", 15 | "properties": { 16 | "mode": "Incremental", 17 | "expressionEvaluationOptions": { 18 | "scope": "inner" 19 | }, 20 | "template": { 21 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 22 | "contentVersion": "1.0.0.0", 23 | "variables": { 24 | "vnetNamespace": "myVnet/" 25 | }, 26 | "resources": [ 27 | { 28 | "name": "[concat(variables('vnetNamespace'), 'my', '-subnet')]", 29 | "apiVersion": "2019-11-01", 30 | "type": "Microsoft.Network/virtualNetworks/subnets", 31 | "properties": { 32 | "addressPrefix": "10.0.0.0/24" 33 | } 34 | }, 35 | { 36 | "name": "[concat('my', '-pip1')]", 37 | "apiVersion": "2019-11-01", 38 | "type": "Microsoft.Network/publicIpAddresses", 39 | "location": "WestUS2", 40 | "properties": { 41 | "publicIPAllocationMethod": "Dynamic" 42 | } 43 | }, 44 | { 45 | "name": "[concat('my', '-pip2')]", 46 | "apiVersion": "2019-11-01", 47 | "type": "Microsoft.Network/publicIpAddresses", 48 | "location": "WestUS2", 49 | "properties": { 50 | "publicIPAllocationMethod": "Dynamic" 51 | } 52 | }, 53 | { 54 | "name": "[concat('my', '-nic')]", 55 | "apiVersion": "2019-11-01", 56 | "type": "Microsoft.Network/networkInterfaces", 57 | "location": "WestUS2", 58 | "properties": { 59 | "ipConfigurations": [ 60 | { 61 | "name": "myConfig", 62 | "properties": { 63 | "privateIPAllocationMethod": "Dynamic", 64 | "subnet": { 65 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', concat(variables('vnetNamespace'), 'my', '-subnet'))]" 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | } 72 | ], 73 | "outputs": { 74 | "nicResourceId": { 75 | "type": "string", 76 | "value": "[resourceId('Microsoft.Network/networkInterfaces', concat('my', '-nic'))]" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /src/Schema/Keyword/DiscriminatedResourceKeywordCache.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using Azure.Bicep.Types.Concrete; 6 | using PSArm.Completion; 7 | using System; 8 | using System.Collections.Concurrent; 9 | using System.Collections.Generic; 10 | 11 | namespace PSArm.Schema.Keyword 12 | { 13 | internal class DiscriminatedResourceKeywordCache : ResourceKeywordCache 14 | { 15 | private ConcurrentDictionary> _discriminatedKeywordTables; 16 | 17 | private Lazy> _commonKeywordsLazy; 18 | 19 | public DiscriminatedResourceKeywordCache(ResourceSchema resource) 20 | : base(resource) 21 | { 22 | _discriminatedKeywordTables = new ConcurrentDictionary>(); 23 | _commonKeywordsLazy = new Lazy>(BuildCommonKeywordDictionary); 24 | } 25 | 26 | public override IReadOnlyDictionary GetInnerKeywords(KeywordContextFrame context) 27 | { 28 | string discriminatorValue = context.GetDiscriminatorValue(Resource.Discriminator); 29 | 30 | if (discriminatorValue is null 31 | || !Resource.DiscriminatedSubtypes.ContainsKey(discriminatorValue)) 32 | { 33 | return null; 34 | } 35 | 36 | return _discriminatedKeywordTables.GetOrAdd(discriminatorValue, BuildDiscriminatedKeywordDictionary); 37 | } 38 | 39 | private Dictionary BuildCommonKeywordDictionary() 40 | { 41 | Dictionary dict = GetBaseKeywordDictionary(); 42 | foreach (KeyValuePair property in Resource.Properties) 43 | { 44 | dict[property.Key] = BicepKeywordSchemaBuilder.GetKeywordSchemaForBicepType(property.Value); 45 | } 46 | return dict; 47 | } 48 | 49 | private IReadOnlyDictionary BuildDiscriminatedKeywordDictionary(string discriminatorValue) 50 | { 51 | TypeBase discriminatedType = Resource.DiscriminatedSubtypes[discriminatorValue].Type; 52 | 53 | if (discriminatedType is not ObjectType objectType) 54 | { 55 | throw new ArgumentException($"Discriminated schema element has non-object type '{discriminatedType.GetType()}'"); 56 | } 57 | 58 | var dict = new Dictionary(_commonKeywordsLazy.Value, StringComparer.OrdinalIgnoreCase); 59 | foreach (KeyValuePair discriminatedProperty in objectType.Properties) 60 | { 61 | dict[discriminatedProperty.Key] = BicepKeywordSchemaBuilder.GetKeywordSchemaForBicepType(discriminatedProperty.Value.Type.Type); 62 | } 63 | return dict; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Templates/ArmParameter.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Operations; 6 | using PSArm.Templates.Primitives; 7 | using PSArm.Templates.Visitors; 8 | using PSArm.Types; 9 | using System; 10 | using System.Collections.Generic; 11 | 12 | namespace PSArm.Templates 13 | { 14 | public class ArmParameter : ArmObject, IArmReferenceable 15 | { 16 | public static explicit operator ArmParameterReferenceExpression(ArmParameter parameter) => parameter.GetReference(); 17 | 18 | public static explicit operator ArmExpression(ArmParameter parameter) => (ArmParameterReferenceExpression)parameter; 19 | 20 | public ArmParameter(IArmString name, IArmString type) 21 | { 22 | Name = name; 23 | Type = type; 24 | } 25 | 26 | public IArmString Name { get; } 27 | 28 | public IArmString Type 29 | { 30 | get => (IArmString)GetElementOrNull(ArmTemplateKeys.Type); 31 | private set => this[ArmTemplateKeys.Type] = (ArmElement)value; 32 | } 33 | 34 | public ArmElement DefaultValue 35 | { 36 | get => GetElementOrNull(ArmTemplateKeys.DefaultValue); 37 | set => this[ArmTemplateKeys.DefaultValue] = value; 38 | } 39 | 40 | public ArmArray AllowedValues 41 | { 42 | get => (ArmArray)GetElementOrNull(ArmTemplateKeys.AllowedValues); 43 | set => this[ArmTemplateKeys.AllowedValues] = value; 44 | } 45 | 46 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitParameterDeclaration(this); 47 | 48 | public ArmParameterReferenceExpression GetReference() 49 | { 50 | return new ArmParameterReferenceExpression(this); 51 | } 52 | 53 | IArmString IArmReferenceable.ReferenceName => Name; 54 | 55 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 56 | => InstantiateIntoCopy(new ArmParameter((IArmString)Name.Instantiate(parameters), (IArmString)Type.Instantiate(parameters)), parameters); 57 | 58 | public override string ToString() 59 | { 60 | return GetReference().ToString(); 61 | } 62 | } 63 | 64 | public class ArmParameter : ArmParameter 65 | { 66 | public ArmParameter(IArmString name) : base(name, GetArmType()) 67 | { 68 | } 69 | 70 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 71 | => InstantiateIntoCopy(new ArmParameter((IArmString)Name.Instantiate(parameters)), parameters); 72 | 73 | private static IArmString GetArmType() 74 | { 75 | if (!ArmTypeConversion.TryConvertToArmType(typeof(T), out ArmType? armType)) 76 | { 77 | throw new ArgumentException($"The type '{typeof(T)}' is not a valid ARM parameter type"); 78 | } 79 | 80 | return armType.Value.AsArmString(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Templates/Operations/ArmIndexAccessExpression.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | using System.Dynamic; 9 | using System.Linq.Expressions; 10 | 11 | 12 | namespace PSArm.Templates.Operations 13 | { 14 | public class ArmIndexAccessExpression : ArmOperation 15 | { 16 | internal static DynamicMetaObject CreateMetaObject( 17 | DynamicMetaObject originalExpressionMO, 18 | GetIndexBinder binder, 19 | DynamicMetaObject[] indexes) 20 | { 21 | // Generate an expression like this: 22 | // 23 | // new ArmIndexAccessExpression((ArmOperation)dynamicObject, new ArmIntegerLiteral(indexes[0])) 24 | // 25 | var expression = Expression.New( 26 | typeof(ArmIndexAccessExpression).GetConstructor(new[] { typeof(ArmOperation), typeof(ArmExpression) }), 27 | new Expression[] 28 | { 29 | Expression.Convert( 30 | // Note that here we must use the magic value of originalExpressionMO.Expression 31 | // Otherwise we'll get the value for the first metadata object that activates this method forever (it's cached) 32 | originalExpressionMO.Expression, 33 | typeof(ArmOperation)), 34 | Expression.New( 35 | typeof(ArmIntegerLiteral).GetConstructor(new[] { typeof(long) }), 36 | new Expression[] 37 | { 38 | Expression.Convert(indexes[0].Expression, typeof(long)) 39 | }) 40 | }); 41 | 42 | 43 | var restrictions = originalExpressionMO.Restrictions.Merge(binder.FallbackGetIndex(originalExpressionMO, indexes).Restrictions); 44 | 45 | return new DynamicMetaObject(expression, restrictions); 46 | } 47 | 48 | public ArmIndexAccessExpression() 49 | { 50 | } 51 | 52 | public ArmIndexAccessExpression(ArmOperation expression, ArmExpression index) 53 | : this() 54 | { 55 | InnerExpression = expression; 56 | Index = index; 57 | } 58 | 59 | public ArmOperation InnerExpression { get; set; } 60 | 61 | public ArmExpression Index { get; set; } 62 | 63 | public override string ToInnerExpressionString() 64 | { 65 | return $"{InnerExpression.ToInnerExpressionString()}[{Index.ToInnerExpressionString()}]"; 66 | } 67 | 68 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitIndexAccess(this); 69 | 70 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 71 | { 72 | return new ArmIndexAccessExpression( 73 | (ArmOperation)InnerExpression.Instantiate(parameters), 74 | (ArmExpression)Index.Instantiate(parameters)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Commands/Primitive/NewPSArmEntryCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Templates.Builders; 7 | using PSArm.Templates.Primitives; 8 | using System; 9 | using System.Management.Automation; 10 | 11 | namespace PSArm.Commands.Primitive 12 | { 13 | [OutputType(typeof(ArmEntry))] 14 | [Alias(KeywordName)] 15 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "Entry", DefaultParameterSetName = "Value")] 16 | public class NewPSArmEntryCommand : PSArmKeywordCommand 17 | { 18 | internal const string KeywordName = "RawEntry"; 19 | 20 | [Parameter(Mandatory = true, Position = 0)] 21 | public IArmString Key { get; set; } 22 | 23 | [Parameter(Mandatory = true, Position = 1, ParameterSetName = "Value")] 24 | public ArmElement Value { get; set; } 25 | 26 | [Parameter(Mandatory = true, Position = 1, ParameterSetName = "Body")] 27 | public ScriptBlock Body { get; set; } 28 | 29 | [Parameter] 30 | public SwitchParameter Array { get; set; } 31 | 32 | [Parameter(ParameterSetName = "Body")] 33 | public SwitchParameter ArrayBody { get; set; } 34 | 35 | [Parameter(ParameterSetName = "Body")] 36 | [ValidateNotNullOrEmpty] 37 | public string DiscriminatorKey { get; set; } 38 | 39 | [Parameter(ParameterSetName = "Body")] 40 | [ValidateNotNullOrEmpty] 41 | public string DiscriminatorValue { get; set; } 42 | 43 | protected override void EndProcessing() 44 | { 45 | if (Value != null) 46 | { 47 | WriteArmValueEntry(Key, Value, isArrayElement: Array); 48 | return; 49 | } 50 | 51 | if (ArrayBody) 52 | { 53 | WriteArmArrayEntry(Key, Body, isArrayElement: Array); 54 | return; 55 | } 56 | 57 | var armBuilder = new ConstructingArmBuilder(); 58 | 59 | if (DiscriminatorKey is not null || DiscriminatorValue is not null) 60 | { 61 | if (DiscriminatorKey is null) 62 | { 63 | ThrowArgumentError(nameof(DiscriminatorKey)); 64 | return; 65 | } 66 | 67 | if (DiscriminatorValue is null) 68 | { 69 | ThrowArgumentError(nameof(DiscriminatorValue)); 70 | return; 71 | } 72 | 73 | armBuilder.AddSingleElement(new ArmStringLiteral(DiscriminatorKey), new ArmStringLiteral(DiscriminatorValue)); 74 | } 75 | 76 | WriteArmObjectEntry(armBuilder, Key, Body, isArrayElement: Array); 77 | } 78 | 79 | private void ThrowArgumentError(string argumentName) 80 | { 81 | this.ThrowTerminatingError( 82 | new ArgumentException($"The parameter '{argumentName}' must be provided"), 83 | "MissingRequiredParameter", 84 | ErrorCategory.ObjectNotFound, 85 | target: this); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Templates/Operations/ArmMemberAccessExpression.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Templates.Primitives; 6 | using PSArm.Templates.Visitors; 7 | using System.Collections.Generic; 8 | using System.Dynamic; 9 | using System.Linq.Expressions; 10 | 11 | 12 | namespace PSArm.Templates.Operations 13 | { 14 | public class ArmMemberAccessExpression : ArmOperation 15 | { 16 | internal static DynamicMetaObject CreateMetaObject( 17 | DynamicMetaObject originalExpressionMO, 18 | GetMemberBinder binder) 19 | { 20 | // Generate an expression like this: 21 | // 22 | // new ArmMemberAccessExpression((ArmOperation)dynamicObject, new ArmStringLiteral(binderName)) 23 | // 24 | var expression = Expression.New( 25 | typeof(ArmMemberAccessExpression).GetConstructor(new[] { typeof(ArmOperation), typeof(IArmString) }), 26 | new Expression[] 27 | { 28 | Expression.Convert( 29 | // Note that here we must use the magic value of originalExpressionMO.Expression 30 | // Otherwise we'll get the value for the first metadata object that activates this method forever (it's cached) 31 | originalExpressionMO.Expression, 32 | typeof(ArmOperation)), 33 | Expression.New( 34 | typeof(ArmStringLiteral).GetConstructor(new[] { typeof(string) }), 35 | new Expression[] 36 | { 37 | Expression.Property( 38 | Expression.Constant(binder), 39 | nameof(binder.Name)) 40 | }) 41 | }); 42 | 43 | BindingRestrictions restrictions = originalExpressionMO.Restrictions.Merge(binder.FallbackGetMember(originalExpressionMO).Restrictions); 44 | return new DynamicMetaObject(expression, restrictions); 45 | } 46 | 47 | public ArmMemberAccessExpression() 48 | { 49 | } 50 | 51 | public ArmMemberAccessExpression(ArmOperation expression, IArmString member) 52 | { 53 | InnerExpression = expression; 54 | Member = member; 55 | } 56 | 57 | public ArmOperation InnerExpression { get; set; } 58 | 59 | public IArmString Member { get; set; } 60 | 61 | public override string ToInnerExpressionString() 62 | { 63 | return $"{InnerExpression.ToInnerExpressionString()}.{Member.ToIdentifierString()}"; 64 | } 65 | 66 | protected override TResult Visit(IArmVisitor visitor) => visitor.VisitMemberAccess(this); 67 | 68 | public override IArmElement Instantiate(IReadOnlyDictionary parameters) 69 | { 70 | return new ArmMemberAccessExpression( 71 | (ArmOperation)InnerExpression.Instantiate(parameters), 72 | (IArmString)Member.Instantiate(parameters)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Internal/KeywordPowerShellParameterDiscovery.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Schema.Keyword; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Management.Automation; 9 | using System.Reflection; 10 | 11 | namespace PSArm.Internal 12 | { 13 | internal static class KeywordPowerShellParameterDiscovery 14 | { 15 | private static readonly IReadOnlyDictionary s_typeAccelerators = GetPSTypeAccelerators(); 16 | 17 | public static IReadOnlyDictionary GetKeywordParametersFromCmdletType(Type type) 18 | { 19 | if (!typeof(Cmdlet).IsAssignableFrom(type)) 20 | { 21 | throw new ArgumentException($"Type '{type}' must describe a PowerShell cmdlet."); 22 | } 23 | 24 | var parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); 25 | 26 | foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) 27 | { 28 | var paramAttr = property.GetCustomAttribute(); 29 | 30 | if (paramAttr is null) 31 | { 32 | continue; 33 | } 34 | 35 | parameters[property.Name] = new DslParameterInfo(property.Name, GetPropertyType(property)); 36 | } 37 | 38 | return parameters; 39 | } 40 | 41 | private static string GetPropertyType(PropertyInfo property) 42 | { 43 | if (s_typeAccelerators is not null 44 | && s_typeAccelerators.TryGetValue(property.PropertyType, out string typeAccelerator)) 45 | { 46 | return typeAccelerator; 47 | } 48 | 49 | return property.PropertyType.ToString(); 50 | } 51 | 52 | private static IReadOnlyDictionary GetPSTypeAccelerators() 53 | { 54 | var typeAccelerators = typeof(PSObject).Assembly.GetType("System.Management.Automation.TypeAccelerators")? 55 | .GetMethod("get_Get")? 56 | .Invoke(obj: null, parameters: null) as Dictionary; 57 | 58 | if (typeAccelerators == null) 59 | { 60 | // Do our best 61 | return new Dictionary 62 | { 63 | { typeof(string), "string" }, 64 | { typeof(ScriptBlock), "scriptblock" }, 65 | { typeof(object), "object" }, 66 | { typeof(int), "int" }, 67 | { typeof(double), "double" }, 68 | { typeof(Type), "type" }, 69 | }; 70 | } 71 | 72 | var typeLookupDict = new Dictionary(typeAccelerators.Count); 73 | foreach (KeyValuePair typeAccelerator in typeAccelerators) 74 | { 75 | typeLookupDict[typeAccelerator.Value] = typeAccelerator.Key; 76 | } 77 | return typeLookupDict; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /docs/New-PSArmResource.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSArm.dll-Help.xml 3 | Module Name: PSArm 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # New-PSArmResource 9 | 10 | ## SYNOPSIS 11 | Declare an ARM resource in PSArm. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | New-PSArmResource [-Name] -ApiVersion -Provider -Type 17 | [-Body] [] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | The `Resource` keyword declares ARM resources in PSArm, 22 | to combine into templates for deployment. 23 | It is intended to be used in the body of the `Arm` keyword. 24 | 25 | ## EXAMPLES 26 | 27 | ### Example 1 28 | ```powershell 29 | Resource $storageAccountName -Provider 'Microsoft.Storage' -Type 'storageAccounts' -apiVersion '2019-06-01' -kind 'StorageV2' -Location 'WestUS2' { 30 | ArmSku 'Standard_LRS' 31 | Properties { 32 | accessTier 'Hot' 33 | minimumTLSVersion 'TLS1_2' 34 | supportsHTTPSTrafficOnly 1 35 | allowBlobPublicAccess 1 36 | allowSharedKeyAccess 1 37 | } 38 | } 39 | ``` 40 | 41 | Declares a storage account resource in the PSArm DSL. 42 | 43 | ## PARAMETERS 44 | 45 | ### -ApiVersion 46 | The API version of the declared ARM resource. 47 | 48 | ```yaml 49 | Type: IArmString 50 | Parameter Sets: (All) 51 | Aliases: 52 | 53 | Required: True 54 | Position: Named 55 | Default value: None 56 | Accept pipeline input: False 57 | Accept wildcard characters: False 58 | ``` 59 | 60 | ### -Body 61 | The definition of the ARM resource, given as a scriptblock in PSArm. 62 | 63 | ```yaml 64 | Type: ScriptBlock 65 | Parameter Sets: (All) 66 | Aliases: 67 | 68 | Required: True 69 | Position: 1 70 | Default value: None 71 | Accept pipeline input: False 72 | Accept wildcard characters: False 73 | ``` 74 | 75 | ### -Name 76 | The name of the resource. 77 | 78 | ```yaml 79 | Type: IArmString 80 | Parameter Sets: (All) 81 | Aliases: 82 | 83 | Required: True 84 | Position: 0 85 | Default value: None 86 | Accept pipeline input: False 87 | Accept wildcard characters: False 88 | ``` 89 | 90 | ### -Type 91 | The type of the resource being defined. 92 | 93 | ```yaml 94 | Type: IArmString 95 | Parameter Sets: (All) 96 | Aliases: 97 | 98 | Required: True 99 | Position: Named 100 | Default value: None 101 | Accept pipeline input: False 102 | Accept wildcard characters: False 103 | ``` 104 | 105 | ### -Provider 106 | {{ Fill Provider Description }} 107 | 108 | ```yaml 109 | Type: IArmString 110 | Parameter Sets: (All) 111 | Aliases: 112 | 113 | Required: True 114 | Position: Named 115 | Default value: None 116 | Accept pipeline input: False 117 | Accept wildcard characters: False 118 | ``` 119 | 120 | ### CommonParameters 121 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 122 | 123 | ## INPUTS 124 | 125 | ### None 126 | 127 | ## OUTPUTS 128 | 129 | ### PSArm.Templates.Primitives.ArmEntry 130 | ## NOTES 131 | 132 | ## RELATED LINKS 133 | -------------------------------------------------------------------------------- /src/Completion/ArmResourceArgumentCompleter.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Template; 6 | using PSArm.Schema; 7 | using PSArm.Schema.Keyword; 8 | using System; 9 | using System.Collections; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Management.Automation; 13 | using System.Management.Automation.Language; 14 | 15 | namespace PSArm.Completion 16 | { 17 | /// 18 | /// Argument completer for cmdlet arguments that take an ARM resource type, 19 | /// such as 'Microsoft.Network/publicIpAddresses'. 20 | /// 21 | public class ArmResourceArgumentCompleter : IArgumentCompleter 22 | { 23 | private static string[] s_locations = new [] 24 | { 25 | "WestUS", 26 | "WestUS2", 27 | "CentralUS" 28 | }; 29 | 30 | /// 31 | /// Complete an ARM resource "type" argument. 32 | /// 33 | /// The name of the invoked command whose argument this is. 34 | /// The name of the parameter for the argument, if any. 35 | /// The argument so far. 36 | /// The entirety of command AST in which this argument is being provided. 37 | /// The attempted parameter binding, for providing something similar to $PSBoundParameters. 38 | /// 39 | public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) 40 | { 41 | ArmResourceName resourceName = GetResourceNameFromParameters(fakeBoundParameters); 42 | IEnumerable completionStrings = ResourceKeywordSchema.Value.GetParameterValues(parameterName, resourceName.Namespace, resourceName.Type, resourceName.ApiVersion); 43 | 44 | if (!string.IsNullOrEmpty(wordToComplete)) 45 | { 46 | completionStrings = completionStrings.Where(s => s.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)); 47 | } 48 | 49 | return GetCompletionResultsFromStrings(completionStrings); 50 | } 51 | 52 | private ArmResourceName GetResourceNameFromParameters(IDictionary fakeBoundParameters) 53 | { 54 | var @namespace = fakeBoundParameters[nameof(NewPSArmResourceCommand.Namespace)] as string; 55 | var type = fakeBoundParameters[nameof(NewPSArmResourceCommand.Type)] as string; 56 | var apiVersion = fakeBoundParameters[nameof(NewPSArmResourceCommand.ApiVersion)] as string; 57 | 58 | return new ArmResourceName(@namespace, type, apiVersion); 59 | } 60 | 61 | private static IEnumerable GetCompletionResultsFromStrings(IEnumerable stringValues) 62 | { 63 | var completions = new List(); 64 | foreach (string str in stringValues) 65 | { 66 | completions.Add(new CompletionResult(str, str, CompletionResultType.ParameterValue, str)); 67 | } 68 | return completions; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/Commands/Template/NewPSArmTemplateCommand.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | 5 | using PSArm.Commands.Internal; 6 | using PSArm.Execution; 7 | using PSArm.Parameterization; 8 | using PSArm.Templates; 9 | using PSArm.Templates.Builders; 10 | using PSArm.Templates.Primitives; 11 | using System; 12 | using System.IO; 13 | using System.Management.Automation; 14 | 15 | namespace PSArm.Commands.Template 16 | { 17 | [OutputType(typeof(ArmTemplate))] 18 | [Alias(KeywordName)] 19 | [Cmdlet(VerbsCommon.New, ModuleConstants.ModulePrefix + "Template")] 20 | public class NewPSArmTemplateCommand : PSArmKeywordCommand 21 | { 22 | internal const string KeywordName = "Arm"; 23 | 24 | [Parameter] 25 | public string Name { get; set; } 26 | 27 | [Parameter(Mandatory = true, Position = 0)] 28 | public ScriptBlock Body { get; set; } 29 | 30 | protected override void EndProcessing() 31 | { 32 | string templateName = Name; 33 | if (templateName is null) 34 | { 35 | try 36 | { 37 | templateName = Path.GetFileName(MyInvocation.ScriptName); 38 | templateName = templateName.Substring(0, templateName.Length - PSArmTemplateExecutor.PSArmFileExtension.Length); 39 | } 40 | catch 41 | { 42 | // If we fail, just proceed with templateName = null 43 | } 44 | } 45 | 46 | ScriptBlock transformedBody; 47 | ArmObject armParameters; 48 | ArmObject armVariables; 49 | object[] psArgsArray; 50 | 51 | using (var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace)) 52 | { 53 | try 54 | { 55 | transformedBody = new TemplateScriptBlockTransformer(pwsh).GetDeparameterizedTemplateScriptBlock( 56 | Body, 57 | out armParameters, 58 | out armVariables, 59 | out psArgsArray); 60 | } 61 | catch (Exception e) 62 | { 63 | this.ThrowTerminatingError(e, "TemplateScriptBlockTransformationFailure", ErrorCategory.InvalidArgument, Body); 64 | return; 65 | } 66 | } 67 | 68 | var template = new ArmTemplate(templateName); 69 | 70 | if (armParameters is not null && armParameters.Count > 0) 71 | { 72 | template.Parameters = armParameters; 73 | } 74 | 75 | if (armVariables is not null && armVariables.Count > 0) 76 | { 77 | template.Variables = armVariables; 78 | } 79 | 80 | var templateBuilder = new ArmBuilder(template); 81 | foreach (PSObject output in InvokeCommand.InvokeScript(useLocalScope: true, transformedBody, input: null, psArgsArray)) 82 | { 83 | if (output.BaseObject is ArmEntry armEntry) 84 | { 85 | templateBuilder.AddEntry(armEntry); 86 | } 87 | } 88 | 89 | WriteObject(template); 90 | } 91 | } 92 | } 93 | --------------------------------------------------------------------------------