Skip to content

Commit

Permalink
Lexically scoped child resources (#1516)
Browse files Browse the repository at this point in the history
Fixes: #1363

This change add support for lexical scoping/nesting of child resources.
That it, for a resource type: `My.RP/someType@2020-01-01` there's now a
simplified syntax for including a resource:
`My.RP/someType/childType@2020-01-01` inside the body of the parent.

This syntax also allows simplication of the type name of the child,
limits its scope/visibility and implies an automatic `dependsOn` to the
parent resource. The goal is to be the most idiomatic way to declare
multiple resources to be deployed with a parent/child/grandchild/etc
relationship.

This also includes the new "nested resource access" operator which
allows lookup of a nested resource:

```
output someOutput string = parent:child.properties.size
``
  • Loading branch information
rynowak authored Mar 6, 2021
1 parent 3be0bfe commit 04999a1
Show file tree
Hide file tree
Showing 103 changed files with 21,962 additions and 9,331 deletions.
6 changes: 3 additions & 3 deletions docs/examples/101/azure-automation-account/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
{
"type": "Microsoft.OperationalInsights/workspaces",
"apiVersion": "2020-03-01-preview",
"location": "[parameters('location')]",
"name": "[variables('workspaceName')]",
"location": "[parameters('location')]",
"properties": {
"sku": {
"name": "[parameters('sku')]"
Expand All @@ -41,8 +41,8 @@
{
"type": "Microsoft.Automation/automationAccounts",
"apiVersion": "2015-10-31",
"location": "[parameters('location')]",
"name": "[variables('automationaccountName')]",
"location": "[parameters('location')]",
"properties": {
"sku": {
"name": "Basic"
Expand Down Expand Up @@ -73,7 +73,7 @@
"_generator": {
"name": "bicep",
"version": "dev",
"templateHash": "17352343791615047991"
"templateHash": "12561374295951696718"
}
}
}
4 changes: 2 additions & 2 deletions docs/examples/101/deployment-script-no-auth/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
{
"type": "Microsoft.Resources/deploymentScripts",
"apiVersion": "2020-10-01",
"kind": "AzurePowerShell",
"name": "[parameters('dsName')]",
"kind": "AzurePowerShell",
"location": "[parameters('location')]",
"properties": {
"azPowerShellVersion": "3.0",
Expand All @@ -41,7 +41,7 @@
"_generator": {
"name": "bicep",
"version": "dev",
"templateHash": "9459865245622089966"
"templateHash": "5388873385114824013"
}
}
}
8 changes: 4 additions & 4 deletions docs/examples/201/cdn-with-storage-account/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2020-08-01-preview",
"location": "[parameters('location')]",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS"
Expand All @@ -27,17 +27,17 @@
{
"type": "Microsoft.Cdn/profiles",
"apiVersion": "2020-04-15",
"location": "[parameters('location')]",
"name": "[variables('profileName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_Akamai"
}
},
{
"type": "Microsoft.Cdn/profiles/endpoints",
"apiVersion": "2020-04-15",
"location": "[parameters('location')]",
"name": "[variables('endPointName')]",
"location": "[parameters('location')]",
"properties": {
"originHostHeader": "[replace(replace(reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))).primaryEndpoints.blob, 'https://', ''), '/', '')]",
"isHttpAllowed": true,
Expand Down Expand Up @@ -79,7 +79,7 @@
"_generator": {
"name": "bicep",
"version": "dev",
"templateHash": "8124028730334468476"
"templateHash": "15844009537662063652"
}
}
}
1 change: 1 addition & 0 deletions docs/grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ memberExpression ->
memberExpression "[" expression "]" |
memberExpression "." IDENTIFIER(property) |
memberExpression "." functionCall
memberExpression ":" IDENTIFIER(name)
primaryExpression ->
functionCall |
Expand Down
35 changes: 34 additions & 1 deletion docs/spec/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The operators below are listed in descending order of precedence (the higher the

| Symbol | Type of Operation | Associativity |
|:-|:-|:-|
| `(` `)` `[` `]` `.` | Parentheses, property accessors and array indexers | Left to right |
| `(` `)` `[` `]` `.` `:` | Parentheses, array indexers, property accessors, and nested-resource accessor | Left to right |
| `!` `-` | Unary | Right to left |
| `%` `*` `/` | Multiplicative | Left to right |
| `+` `-` | Additive | Left to right |
Expand Down Expand Up @@ -106,6 +106,39 @@ Given the above declaration, the expression `x.y.z` would evaluate to the litera

Property accessors can be used with any object. This includes parameters and variables of object types and object literals. Using a property accessor on an expression of non-object type is an error.

## Nested resource accessors
Nested resource accessors are used to access resources that are declared inside another resource. The symbolic name declared by a nested resource can normally only be referenced within the body of the containing resource. To reference a nested resource outside the containing resource, it must be qualified with the containing resource name and the `:` operator. Other resources declared within the same containing resource may used the name without qualification.

```
resource myParent 'My.Rp/parentType@2020-01-01' = {
name: 'myParent'
location: 'West US'
// declares a nested resource inside 'myParent'
resource myChild 'childType' = {
name: 'myChild'
properties: {
displayName: 'Child Resource'
}
}
// 'myChild' can be referened inside the body of 'myParent'
resource mySibling 'childType' = {
name: 'mySibling'
properties: {
displayName: 'Sibling of ${mychild.properties.displayName}'
}
}
}
// accessing 'myChild' here requires the resource access operator
output displayName string = myParent:myChild.properties.displayName
```

Since the declaration of `myChild` is contained within `myParent` the access to `myChild`'s properties must be qualified with `myParent:`.

The `:` must be parenthesized when it appears inside a ternary expression such as: `useChildValue ? (myParent:myChild.properties.displayName) : 'defaultValue'`.

## Array indexers
Array indexers serve two purposes. Most commonly, they are used to access items in an array. However, they can also be used to access properties of objects via expressions or string literals.

Expand Down
43 changes: 43 additions & 0 deletions docs/spec/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,35 @@ resource dnsZone 'Microsoft.Network/dnszones@2018-05-01' = {
}
```

## Resource nesting

A resource declaration may appear inside another resource declaration when the inner resource is a child type of the containing resource. A nested resource is considered to have an implicit dependency on its containing resource for creation order.

```
resource myParent 'My.Rp/parentType@2020-01-01' = {
name: 'myParent'
location: 'West US'
// declares a resource of type 'My.Rp/parentType/childType@2020-01-01'
resource myChild 'childType' = {
name: 'myChild'
properties: {
displayName: 'child in ${parent.location}'
}
}
}
```

A nested resource must specify a single type segment to declare its type. The full type of the nested resource is the containing resource's type with the additional segment appended to the list of types. In the example above `My.Rp/parentType@2020-01-01` is combined with `childType` resulting in `My.Rp/parentType/childType@2020-01-01`. The nested resource may optionally declare an API version using the syntax `<segment>@<version>`. If the nested resource omits the API version the the API version of the containing resource is used. If the nested resource specifies an API version then the API version specified will be used. If the example above were modified so that the nested resource declared its type as `childType@2020-20-20` then the fully-qualified type would be `My.Rp/parentType/childType@2020-20-20`.

Nested resource declarations should specify their `name` property with a single segment. The the example above the nested resource declares its `name` property with the value `myChild`. In ARM-JSON, child resources must declared their `name` property as a `/`-separated string containing multiple segments like: `myParent/myChild` - this is not required with nested resources.

A nested resource declaration must appear at the top level of syntax of the containing resource. Declarations may be nested arbirarily deep, as long as each level is a child type of its containing resource.

The symbolic name of a nested resource is only accessible inside the body of its containing resource.

A nested resource may access properties of its containing resource. Other resources declared inside the body of the same containing resource may reference each other and the typical rules about cyclic-dependencies apply. A containing resource may not access properties of the resources it contains, this would cause a cyclic-dependency.

## Resource dependencies
All declared resources will be deployed concurrently in the compiled template. Order of resource deployment can be influenced in the following ways:
### Explicit dependency
Expand Down Expand Up @@ -78,6 +107,20 @@ resource otherResource 'Microsoft.Example/examples@2020-06-01' = {
}
```

A nested resource has an implicit dependency on its containing resource.

```
resource myParent 'My.Rp/parentType@2020-01-01' = {
name: 'myParent'
location: 'West US'
// depends on 'myParent' implicitly
resource myChild 'childType' = {
name: 'myChild'
}
}
```

## Conditions

> Requires Bicep CLI v0.2.212 or later
Expand Down
Loading

0 comments on commit 04999a1

Please sign in to comment.