diff --git a/provider/pkg/resources/parseResourceId.go b/provider/pkg/resources/parseResourceId.go index 40ab3abadb58..870ea326eb23 100644 --- a/provider/pkg/resources/parseResourceId.go +++ b/provider/pkg/resources/parseResourceId.go @@ -14,10 +14,18 @@ import ( func ParseResourceID(id, path string) (map[string]string, error) { pathParts := strings.Split(path, "/") regexParts := make([]string, len(pathParts)) + // Track the names of the regex matches so we can map them back to the original names. + regexNames := make(map[string]string, len(pathParts)) + regexMatchGroupNameReplacer := regexp.MustCompile(`[^a-zA-Z0-9]`) for i, s := range pathParts { if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") { name := s[1 : len(s)-1] - regexParts[i] = fmt.Sprintf("(?P<%s>.*?)", name) + regexName := regexMatchGroupNameReplacer.ReplaceAllString(name, "") + if clashingPathName, ok := regexNames[regexName]; ok { + return nil, errors.Errorf("clashing path names: '%s' and '%s' while parsing resource ID for path '%s", clashingPathName, name, path) + } + regexNames[regexName] = name + regexParts[i] = fmt.Sprintf("(?P<%s>.*?)", regexName) } else { regexParts[i] = pathParts[i] } @@ -35,9 +43,10 @@ func ParseResourceID(id, path string) (map[string]string, error) { } result := map[string]string{} - for i, name := range pattern.SubexpNames() { - if i > 0 && name != "" { - result[name] = match[i] + for i, regexpGroupName := range pattern.SubexpNames() { + if i > 0 && regexpGroupName != "" { + originalName := regexNames[regexpGroupName] + result[originalName] = match[i] } } return result, nil diff --git a/provider/pkg/resources/parseResourceId_test.go b/provider/pkg/resources/parseResourceId_test.go index 7abbdd54ecbe..9bf0240aecd0 100644 --- a/provider/pkg/resources/parseResourceId_test.go +++ b/provider/pkg/resources/parseResourceId_test.go @@ -11,43 +11,59 @@ type resourceIDCase struct { path string } -func TestParseInvalidResourceID(t *testing.T) { - cases := []resourceIDCase{ - // ID shorter than Path - {"/resourceGroup/myrg", "/resourceGroup/{resourceGroup}/subResource"}, - // ID longer than Path - {"/resourceGroup/myrg/cdn/mycdn", "/resourceGroup/{resourceGroup}/cdn"}, - // Segment names don't match - {"/resourceGroup/myrg/foo/mycdn", "/resourceGroup/{resourceGroup}/bar/{cdn}"}, - } - for _, testCase := range cases { - _, err := ParseResourceID(testCase.id, testCase.path) - assert.Error(t, err) - } -} +func TestParseResourceID(t *testing.T) { + t.Run("invalid", func(t *testing.T) { + cases := []resourceIDCase{ + // ID shorter than Path + {"/resourceGroup/myrg", "/resourceGroup/{resourceGroup}/subResource"}, + // ID longer than Path + {"/resourceGroup/myrg/cdn/mycdn", "/resourceGroup/{resourceGroup}/cdn"}, + // Segment names don't match + {"/resourceGroup/myrg/foo/mycdn", "/resourceGroup/{resourceGroup}/bar/{cdn}"}, + } + for _, testCase := range cases { + _, err := ParseResourceID(testCase.id, testCase.path) + assert.Error(t, err) + } + }) -func TestParseFullResourceID(t *testing.T) { - id := "/subscriptions/0282681f-7a9e-123b-40b2-96babd57a8a1/resourcegroups/pulumi-name/providers/Microsoft.Network/networkInterfaces/pulumi-nic/ipConfigurations/ipconfig1" - path := "/subscriptions/{subscriptionID}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkInterfaces/{networkInterfaceName}/ipConfigurations/{ipConfigurationName}" - actual, err := ParseResourceID(id, path) - assert.NoError(t, err) - expected := map[string]string{ - "subscriptionID": "0282681f-7a9e-123b-40b2-96babd57a8a1", - "resourceGroupName": "pulumi-name", - "networkInterfaceName": "pulumi-nic", - "ipConfigurationName": "ipconfig1", - } - assert.Equal(t, expected, actual) -} + t.Run("full", func(t *testing.T) { + id := "/subscriptions/0282681f-7a9e-123b-40b2-96babd57a8a1/resourcegroups/pulumi-name/providers/Microsoft.Network/networkInterfaces/pulumi-nic/ipConfigurations/ipconfig1" + path := "/subscriptions/{subscriptionID}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkInterfaces/{networkInterfaceName}/ipConfigurations/{ipConfigurationName}" + actual, err := ParseResourceID(id, path) + assert.NoError(t, err) + expected := map[string]string{ + "subscriptionID": "0282681f-7a9e-123b-40b2-96babd57a8a1", + "resourceGroupName": "pulumi-name", + "networkInterfaceName": "pulumi-nic", + "ipConfigurationName": "ipconfig1", + } + assert.Equal(t, expected, actual) + }) + + t.Run("scoped", func(t *testing.T) { + id := "/subscriptions/1200b1c8-3c58-42db-b33a-304a75913333/resourceGroups/devops-dev/providers/Microsoft.Authorization/roleAssignments/2a88abc7-f599-0eba-a21f-a1817e597115" + path := "/{scope}/providers/Microsoft.Authorization/roleAssignments/{roleAssignmentName}" + actual, err := ParseResourceID(id, path) + assert.NoError(t, err) + expected := map[string]string{ + "scope": "subscriptions/1200b1c8-3c58-42db-b33a-304a75913333/resourceGroups/devops-dev", + "roleAssignmentName": "2a88abc7-f599-0eba-a21f-a1817e597115", + } + assert.Equal(t, expected, actual) + }) -func TestParseScopedResourceID(t *testing.T) { - id := "/subscriptions/1200b1c8-3c58-42db-b33a-304a75913333/resourceGroups/devops-dev/providers/Microsoft.Authorization/roleAssignments/2a88abc7-f599-0eba-a21f-a1817e597115" - path := "/{scope}/providers/Microsoft.Authorization/roleAssignments/{roleAssignmentName}" - actual, err := ParseResourceID(id, path) - assert.NoError(t, err) - expected := map[string]string{ - "scope": "subscriptions/1200b1c8-3c58-42db-b33a-304a75913333/resourceGroups/devops-dev", - "roleAssignmentName": "2a88abc7-f599-0eba-a21f-a1817e597115", - } - assert.Equal(t, expected, actual) + t.Run("nested identifier", func(t *testing.T) { + id := "/subscriptions/1200b1c8-3c58-42db-b33a-304a75913333/resourceGroups/resource-group/providers/Microsoft.KeyVault/vaults/vault-name/accessPolicy/policy-object-id" + path := "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{vaultName}/accessPolicy/{policy.objectId}" + actual, err := ParseResourceID(id, path) + assert.NoError(t, err) + expected := map[string]string{ + "subscriptionId": "1200b1c8-3c58-42db-b33a-304a75913333", + "resourceGroupName": "resource-group", + "vaultName": "vault-name", + "policy.objectId": "policy-object-id", + } + assert.Equal(t, expected, actual) + }) }