Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow referencing existing resources with the 'existing' keyword #1339

Merged
merged 14 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'))]",
anthony-c-martin marked this conversation as resolved.
Show resolved Hide resolved
"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