Skip to content

Commit

Permalink
Allow referencing existing resources with the 'existing' keyword (#1339)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-c-martin authored Feb 2, 2021
1 parent cb3145f commit e4d670a
Show file tree
Hide file tree
Showing 94 changed files with 9,232 additions and 5,927 deletions.
13 changes: 13 additions & 0 deletions docs/examples/101/resource-with-lock-existing/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
param accountName string

resource storageAcc 'Microsoft.Storage/storageAccounts@2019-06-01' existing = {
name: accountName
}

resource lockResource 'Microsoft.Authorization/locks@2016-09-01' = {
name: 'DontDelete'
scope: storageAcc
properties: {
level: 'CanNotDelete'
}
}
22 changes: 22 additions & 0 deletions docs/examples/101/resource-with-lock-existing/main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"accountName": {
"type": "string"
}
},
"functions": [],
"resources": [
{
"type": "Microsoft.Authorization/locks",
"apiVersion": "2016-09-01",
"scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('accountName'))]",
"name": "DontDelete",
"properties": {
"level": "CanNotDelete"
},
"dependsOn": []
}
]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
targetScope = 'subscription'

param listOfAllowedLocations array = [
'norwayeast'
'westeurope'
Expand Down
8 changes: 4 additions & 4 deletions docs/examples/201/policy-definition-with-assignment/main.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"listOfAllowedLocations": {
Expand Down Expand Up @@ -66,7 +66,7 @@
"apiVersion": "2020-09-01",
"name": "Resource-location-restriction",
"properties": {
"policyDefinitionId": "[resourceId('Microsoft.Authorization/policyDefinitions', 'custom-allowed-location')]",
"policyDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'custom-allowed-location')]",
"displayName": "Restrict location for Azure resources",
"description": "Policy will either Audit or Deny resources being deployed in other locations",
"parameters": {
Expand All @@ -79,8 +79,8 @@
}
},
"dependsOn": [
"[resourceId('Microsoft.Authorization/policyDefinitions', 'custom-allowed-location')]"
"[subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'custom-allowed-location')]"
]
}
]
}
}
4 changes: 4 additions & 0 deletions docs/examples/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@
"filePath": "101/resource-with-lock/main.bicep",
"description": "101/resource-with-lock"
},
{
"filePath": "101/resource-with-lock-existing/main.bicep",
"description": "101/resource-with-lock-existing"
},
{
"filePath": "101/sql-database/main.bicep",
"description": "101/sql-database"
Expand Down
2 changes: 1 addition & 1 deletion docs/grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ parameterDefaultValue -> "=" expression
variableDecl -> decorator* "variable" IDENTIFIER(name) "=" expression NL
resourceDecl -> decorator* "resource" IDENTIFIER(name) interpString(type) "=" (ifCondition | object) NL
resourceDecl -> decorator* "resource" IDENTIFIER(name) interpString(type) "existing"? "=" (ifCondition | object) NL
moduleDecl -> decorator* "module" IDENTIFIER(name) interpString(type) "=" (ifCondition | object) NL
Expand Down
26 changes: 26 additions & 0 deletions docs/spec/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,32 @@ resource dnsZone 'Microsoft.Network/dnszones@2018-05-01' = if (deployZone) {

Conditions may be used with dependency declarations. If the identifier of conditional resource is specified in `dependsOn` of another resource (explicit dependency), the dependency will be ignored if the condition evaluates to `false` at template deployment time. If the condition evaluates to `true`, the dependency will be respected. Referencing a property of a conditional resource (implicit dependency) is allowed but may produce a runtime error in some cases.

## Referencing existing resources

> Requires Bicep CLI v0.3 or later
You may add references and access runtime properties from resources outside of the current file by using the `existing` keyword in a resource declaration. This is equivalent to using the ARM Template `reference()` function.

When using the `existing` keyword, you must provide the `name` of the resource, and may optionally also set the `scope` property to access a resource in a different scope. See [Resource Scopes](./resource-scopes.md) for more information on using the `scope` property.

```bicep
// this resource will not be deployed by this file, but the declaration provides access to properties on the existing resource.
resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' existing = {
name: 'myacc'
}
// the 'stg' symbolic name may now be used to access properties on the storage account.
output blobEndpoint string = stg.properties.primaryEndpoints.blob
```

```bicep
// example of referencing a resource at a different scope (resource group myRg under subscription mySub)
resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' existing = {
name: 'myacc'
scope: resourceGroup(mySub, myRg)
}
```

## Other Examples

### Storage Account
Expand Down
137 changes: 0 additions & 137 deletions src/Bicep.Core.IntegrationTests/Emit/TemplateEmitterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,143 +106,6 @@ public void InvalidBicep_TemplateEmiterShouldNotProduceAnyTemplate(DataSet dataS
result.Diagnostics.Should().NotBeEmpty();
}

private const string ExpectedTenantSchema = "https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json#";
private const string ExpectedMgSchema = "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#";
private const string ExpectedSubSchema = "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#";
private const string ExpectedRgSchema = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#";

[DataRow("tenant", "tenant()", "tenant", ExpectedTenantSchema, "[reference(tenantResourceId('Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[tenantResourceId('Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("tenant", "managementGroup('abc')", "managementGroup", ExpectedTenantSchema, "[reference(extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', 'abc'), 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', 'abc'), 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("tenant", "subscription('abc')", "subscription", ExpectedTenantSchema, "[reference(subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("tenant", "resourceGroup('abc', 'def')", "resourceGroup", ExpectedTenantSchema, "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', 'abc', 'def'), 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', 'abc', 'def'), 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("managementGroup", "managementGroup()", "managementGroup", ExpectedMgSchema, "[reference(format('Microsoft.Resources/deployments/{0}', 'myMod'), '2019-10-01').outputs.hello.value]", "[format('Microsoft.Resources/deployments/{0}', 'myMod')]")]
[DataRow("managementGroup", "subscription('abc')", "subscription", ExpectedMgSchema, "[reference(subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("managementGroup", "resourceGroup('abc', 'def')", "resourceGroup", ExpectedMgSchema, "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', 'abc', 'def'), 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', 'abc', 'def'), 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("subscription", "subscription()", "subscription", ExpectedSubSchema, "[reference(subscriptionResourceId('Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[subscriptionResourceId('Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("subscription", "subscription('abc')", "subscription", ExpectedSubSchema, "[reference(subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("subscription", "resourceGroup('abc')", "resourceGroup", ExpectedSubSchema, "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'abc'), 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'abc'), 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("subscription", "tenant()", "tenant", ExpectedSubSchema, "[reference(tenantResourceId('Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[tenantResourceId('Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("resourceGroup", "subscription()", "subscription", ExpectedRgSchema, "[reference(subscriptionResourceId('Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[subscriptionResourceId('Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("resourceGroup", "subscription('abc')", "subscription", ExpectedRgSchema, "[reference(subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[subscriptionResourceId('abc', 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("resourceGroup", "resourceGroup()", "resourceGroup", ExpectedRgSchema, "[reference(extensionResourceId(resourceGroup().id, 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[extensionResourceId(resourceGroup().id, 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("resourceGroup", "resourceGroup('abc')", "resourceGroup", ExpectedRgSchema, "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'abc'), 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'abc'), 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("resourceGroup", "resourceGroup('abc', 'def')", "resourceGroup", ExpectedRgSchema, "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', 'abc', 'def'), 'Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', 'abc', 'def'), 'Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("resourceGroup", "tenant()", "tenant", ExpectedRgSchema, "[reference(tenantResourceId('Microsoft.Resources/deployments', 'myMod'), '2019-10-01').outputs.hello.value]", "[tenantResourceId('Microsoft.Resources/deployments', 'myMod')]")]
[DataTestMethod]
public void Emitter_should_generate_correct_module_output_scope_strings(string targetScope, string moduleScope, string moduleTargetScope, string expectedSchema, string expectedOutput, string expectedResourceDependsOn)
{
var (json, diags) = CompilationHelper.Compile(
("main.bicep", @"
targetScope = '$targetScope'
module myMod './module.bicep' = {
name: 'myMod'
scope: $moduleScope
}
resource resourceB 'My.Rp/myResource@2020-01-01' = {
name: 'resourceB'
dependsOn: [
myMod
]
}
output hello string = myMod.outputs.hello
".Replace("$targetScope", targetScope).Replace("$moduleScope", moduleScope)),
("module.bicep", @"
targetScope = '$moduleTargetScope'
output hello string = 'hello!'
".Replace("$moduleTargetScope", moduleTargetScope)));

json.Should().NotBeNull();
var template = JObject.Parse(json!);

using (new AssertionScope())
{
template.SelectToken("$.['$schema']")!.ToString().Should().Be(expectedSchema);
template.SelectToken("$.outputs.hello.value")!.ToString().Should().Be(expectedOutput);
template.SelectToken("$.resources[?(@.name == 'resourceB')].dependsOn[0]")!.ToString().Should().Be(expectedResourceDependsOn);
}
}

[DataRow("tenant", "[tenantResourceId('My.Rp/myResource', 'resourceA')]", "[tenantResourceId('Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("managementGroup", "[format('My.Rp/myResource/{0}', 'resourceA')]", "[format('Microsoft.Resources/deployments/{0}', 'myMod')]")]
[DataRow("subscription", "[subscriptionResourceId('My.Rp/myResource', 'resourceA')]", "[subscriptionResourceId('Microsoft.Resources/deployments', 'myMod')]")]
[DataRow("resourceGroup", "[resourceId('My.Rp/myResource', 'resourceA')]", "[extensionResourceId(resourceGroup().id, 'Microsoft.Resources/deployments', 'myMod')]")]
[DataTestMethod]
public void Emitter_should_generate_correct_dependsOn_resourceIds(string targetScope, string expectedModuleDependsOn, string expectedResourceDependsOn)
{
var (json, diags) = CompilationHelper.Compile(
("main.bicep", @"
targetScope = '$targetScope'
resource resourceA 'My.Rp/myResource@2020-01-01' = {
name: 'resourceA'
}
module myMod './module.bicep' = {
name: 'myMod'
params: {
dependency: resourceA.id
}
}
resource resourceB 'My.Rp/myResource@2020-01-01' = {
name: 'resourceB'
dependsOn: [
myMod
]
}
".Replace("$targetScope", targetScope)),
("module.bicep", @"
targetScope = '$targetScope'
param dependency string
".Replace("$targetScope", targetScope))
);

json.Should().NotBeNull();
var template = JObject.Parse(json!);

using (new AssertionScope())
{
template.SelectToken("$.resources[?(@.name == 'resourceB')].dependsOn[0]")!.ToString().Should().Be(expectedResourceDependsOn);
template.SelectToken("$.resources[?(@.name == 'myMod')].dependsOn[0]")!.ToString().Should().Be(expectedModuleDependsOn);
}
}

[TestMethod]
public void Emitter_should_generate_correct_extension_scope_property_and_correct_dependsOn()
{
var (json, diags) = CompilationHelper.Compile(@"
resource resourceA 'My.Rp/myResource@2020-01-01' = {
name: 'resourceA'
}
resource resourceB 'My.Rp/myResource@2020-01-01' = {
scope: resourceA
name: 'resourceB'
}
resource resourceC 'My.Rp/myResource@2020-01-01' = {
scope: resourceB
name: 'resourceC'
}");

json.Should().NotBeNull();
var template = JObject.Parse(json!);

using (new AssertionScope())
{
template.SelectToken("$.resources[?(@.name == 'resourceB')].scope")!.ToString().Should().Be("[format('My.Rp/myResource/{0}', 'resourceA')]");
template.SelectToken("$.resources[?(@.name == 'resourceB')].dependsOn[0]")!.ToString().Should().Be("[resourceId('My.Rp/myResource', 'resourceA')]");

template.SelectToken("$.resources[?(@.name == 'resourceC')].scope")!.ToString().Should().Be("[extensionResourceId(format('My.Rp/myResource/{0}', 'resourceA'), 'My.Rp/myResource', 'resourceB')]");
template.SelectToken("$.resources[?(@.name == 'resourceC')].dependsOn[0]")!.ToString().Should().Be("[extensionResourceId(format('My.Rp/myResource/{0}', 'resourceA'), 'My.Rp/myResource', 'resourceB')]");
}
}

private EmitResult EmitTemplate(SyntaxTreeGrouping syntaxTreeGrouping, string filePath)
{
var compilation = new Compilation(TestResourceTypeProvider.Create(), syntaxTreeGrouping);
Expand Down
Loading

0 comments on commit e4d670a

Please sign in to comment.