Skip to content

Commit

Permalink
Custom resource schema modifications (#3571)
Browse files Browse the repository at this point in the history
Add a more understandable and unified approach to modifying existing
parts of the schema.

Use a function to define a resource's schema–resource & types in both
the schema and metadata. This is given any existing declaration which it
is allowed to modify.

- If newly added types conflict with an existing type in the schema, we
will raise an error.
- Allows modifying any of the types associated with the custom resource.
- Allow modification of both the schema and the metadata in one
abstration.
- Allows creation of brand new resources - the existing resource will
just be nil.

This allows the removal of the custom resource `Types`, `Schema` and
`Meta` fields along with the associated `SchemaMixins`,
`SchemaTypeMixins` and `MetaMixins` functions.

## Dashboard Fix

Re-implements #3123
after it was reverted in
#3500 due to breaking
our ability to release.

Fixes #3562
Fixes pulumi/customer-support#1737

This then re-implements the portal Dashboard customisations, but rather
than blindly overwriting the existing schema elements, it manipulates
specific fields as required, giving opportunity for error handling if
the expected types or fields don't match.
  • Loading branch information
danielrbradley authored Sep 30, 2024
2 parents 1bd7037 + 10d7d04 commit 3610e47
Show file tree
Hide file tree
Showing 24 changed files with 91,669 additions and 328,305 deletions.
1,386 changes: 1,386 additions & 0 deletions provider/pkg/gen/__snapshots__/gen_dashboard_test.snap

Large diffs are not rendered by default.

415,261 changes: 87,132 additions & 328,129 deletions provider/pkg/gen/__snapshots__/gen_vnet_test.snap

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions provider/pkg/gen/gen_dashboard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package gen

import (
"os"
"path"
"testing"

"github.com/gkampitakis/go-snaps/snaps"
"github.com/pulumi/pulumi-azure-native/v2/provider/pkg/openapi"
"github.com/stretchr/testify/assert"
)

func TestPortalDashboardGen(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
// We take a snapshot of the schema as we're focusing on testing how we parse and modify the spec
// To update the specifications run in the repo root:
// rm -rf provider/pkg/gen/test-data/dashboard/azure-rest-api-specs/specification/portal/resource-manager/Microsoft.Portal/preview/2020-09-01-preview/
// rm -rf provider/pkg/gen/test-data/dashboard/azure-rest-api-specs/specification/common-types/resource-management/v5/
// cp -r azure-rest-api-specs/specification/portal/resource-manager/Microsoft.Portal/preview/2020-09-01-preview/ provider/pkg/gen/test-data/dashboard/azure-rest-api-specs/specification/portal/resource-manager/Microsoft.Portal/preview/2020-09-01-preview/
// cp -r azure-rest-api-specs/specification/common-types/resource-management/v5/ provider/pkg/gen/test-data/dashboard/azure-rest-api-specs/specification/common-types/resource-management/v5/
// rm -rf provider/pkg/gen/test-data/dashboard/azure-rest-api-specs/specification/portal/resource-manager/Microsoft.Portal/preview/2020-09-01-preview/examples/
rootDir := path.Join(wd, "test-data", "dashboard")

providers, _, err := openapi.ReadAzureProviders(path.Join(rootDir, "azure-rest-api-specs"), "Portal", "2020-09-01-preview")
if err != nil {
t.Fatal(err)
}
providers = openapi.ApplyProvidersTransformations(providers, map[string]map[string]string{
"Portal": {
"Dashboard": "2020-09-01-preview",
},
}, map[string]map[string]string{}, map[string][]string{}, map[string][]string{})
generationResult, err := PulumiSchema(rootDir, providers, versioningStub{})
if err != nil {
t.Fatal(err)
}

// Ensure the dashboard resource is present in schema and metadata
// and snapshot the generation result so we can see the impact of future refactors.
assert.NotNil(t, generationResult.Schema.Resources["azure-native:portal:Dashboard"])
snaps.MatchJSON(t, generationResult.Schema.Resources["azure-native:portal:Dashboard"])

assert.NotNil(t, generationResult.Metadata.Resources["azure-native:portal:Dashboard"])
snaps.MatchJSON(t, generationResult.Metadata.Resources["azure-native:portal:Dashboard"])

snaps.MatchJSON(t, generationResult.Schema.Types)
snaps.MatchJSON(t, generationResult.Metadata.Types)
}
5 changes: 2 additions & 3 deletions provider/pkg/gen/gen_vnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ func TestVnetGen(t *testing.T) {
if err != nil {
t.Fatal(err)
}
snaps.MatchSnapshot(t, generationResult.Schema)

snaps.MatchSnapshot(t, generationResult.Metadata)
snaps.MatchJSON(t, generationResult.Schema)
snaps.MatchJSON(t, generationResult.Metadata)
}
8 changes: 7 additions & 1 deletion provider/pkg/gen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,12 @@ func genMixins(pkg *pschema.PackageSpec, metadata *resources.AzureAPIMetadata) e
metadata.Types[tok] = r
}

// Apply custom resource modifications to the schema and metadata.
err := customresources.ApplySchemas(pkg, metadata)
if err != nil {
return err
}

// Add a note regarding WorkspaceSqlAadAdmin creation.
workspaceSqlAadAdmin := pkg.Resources["azure-native:synapse:WorkspaceSqlAadAdmin"]
workspaceSqlAadAdmin.Description += "\n\nNote: SQL AAD Admin is configured automatically during workspace creation and assigned to the current user. One can't add more admins with this resource unless you manually delete the current SQL AAD Admin."
Expand All @@ -586,7 +592,7 @@ func normalizePackage(pkg *pschema.PackageSpec, metadata *resources.AzureAPIMeta
visitor := func(t string, _ pschema.ComplexTypeSpec) {
usedTypes[t] = true
}
VisitPackageSpecTypes(pkg, visitor)
resources.VisitPackageSpecTypes(pkg, visitor)

// Elide unused types.
allTypeNames := codegen.SortedKeys(pkg.Types)
Expand Down
12 changes: 12 additions & 0 deletions provider/pkg/gen/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,29 @@ func TestTypeAliasFormatting(t *testing.T) {
var _ Versioning = (*versioningStub)(nil)

type versioningStub struct {
shouldInclude func(provider string, version string, typeName, token string) bool
getDeprecations func(token string) (ResourceDeprecation, bool)
getAllVersions func(provider, resource string) []string
}

func (v versioningStub) ShouldInclude(provider string, version string, typeName, token string) bool {
if v.shouldInclude != nil {
return v.shouldInclude(provider, version, typeName, token)
}
return true
}

func (v versioningStub) GetDeprecation(token string) (ResourceDeprecation, bool) {
if v.getDeprecations != nil {
return v.getDeprecations(token)
}
return ResourceDeprecation{}, false
}

func (v versioningStub) GetAllVersions(provider, resource string) []string {
if v.getAllVersions != nil {
return v.getAllVersions(provider, resource)
}
return []string{}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"swagger": "2.0",
"info": {
"version": "5.0",
"title": "Common types"
},
"paths": {},
"definitions": {
"encryption": {
"type": "object",
"description": "All encryption configuration for a resource.",
"properties": {
"infrastructureEncryption": {
"type": "string",
"enum": [
"enabled",
"disabled"
],
"x-ms-enum": {
"name": "InfrastructureEncryption",
"modelAsString": true
},
"description": "(Optional) Discouraged to include in resource definition. Only needed where it is possible to disable platform (AKA infrastructure) encryption. Azure SQL TDE is an example of this. Values are enabled and disabled."
},
"customerManagedKeyEncryption": {
"type": "object",
"description": "All Customer-managed key encryption properties for the resource.",
"properties": {
"keyEncryptionKeyIdentity": {
"type": "object",
"description": "All identity configuration for Customer-managed key settings defining which identity should be used to auth to Key Vault.",
"properties": {
"identityType": {
"type": "string",
"enum": [
"systemAssignedIdentity",
"userAssignedIdentity",
"delegatedResourceIdentity"
],
"description": "The type of identity to use. Values can be systemAssignedIdentity, userAssignedIdentity, or delegatedResourceIdentity."
},
"userAssignedIdentityResourceId": {
"type": "string",
"format": "arm-id",
"description": "User assigned identity to use for accessing key encryption key Url. Ex: /subscriptions/fa5fc227-a624-475e-b696-cdd604c735bc/resourceGroups/<resource group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myId. Mutually exclusive with identityType systemAssignedIdentity."
},
"federatedClientId": {
"type": "string",
"format": "uuid",
"description": "application client identity to use for accessing key encryption key Url in a different tenant. Ex: f83c6b1b-4d34-47e4-bb34-9d83df58b540"
},
"delegatedIdentityClientId": {
"type": "string",
"format": "uuid",
"description": "delegated identity to use for accessing key encryption key Url. Ex: /subscriptions/fa5fc227-a624-475e-b696-cdd604c735bc/resourceGroups/<resource group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myId. Mutually exclusive with identityType systemAssignedIdentity and userAssignedIdentity - internal use only."
}
}
},
"keyEncryptionKeyUrl": {
"type": "string",
"description": "key encryption key Url, versioned or unversioned. Ex: https://contosovault.vault.azure.net/keys/contosokek/562a4bb76b524a1493a6afe8e536ee78 or https://contosovault.vault.azure.net/keys/contosokek."
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{
"swagger": "2.0",
"info": {
"version": "5.0",
"title": "Common types"
},
"paths": {},
"definitions": {
"UserAssignedIdentities": {
"title": "User-Assigned Identities",
"description": "The set of user assigned identities associated with the resource. The userAssignedIdentities dictionary keys will be ARM resource ids in the form: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}. The dictionary values can be empty objects ({}) in requests.",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/UserAssignedIdentity",
"x-nullable": true
}
},
"UserAssignedIdentity": {
"type": "object",
"description": "User assigned identity properties",
"properties": {
"principalId": {
"description": "The principal ID of the assigned identity.",
"format": "uuid",
"type": "string",
"readOnly": true
},
"clientId": {
"description": "The client ID of the assigned identity.",
"format": "uuid",
"type": "string",
"readOnly": true
}
}
},
"ManagedServiceIdentityType": {
"description": "Type of managed service identity (where both SystemAssigned and UserAssigned types are allowed).",
"enum": [
"None",
"SystemAssigned",
"UserAssigned",
"SystemAssigned,UserAssigned"
],
"type": "string",
"x-ms-enum": {
"name": "ManagedServiceIdentityType",
"modelAsString": true
}
},
"ManagedServiceIdentity": {
"description": "Managed service identity (system assigned and/or user assigned identities)",
"type": "object",
"properties": {
"principalId": {
"readOnly": true,
"format": "uuid",
"type": "string",
"description": "The service principal ID of the system assigned identity. This property will only be provided for a system assigned identity."
},
"tenantId": {
"readOnly": true,
"format": "uuid",
"type": "string",
"description": "The tenant ID of the system assigned identity. This property will only be provided for a system assigned identity."
},
"type": {
"$ref": "#/definitions/ManagedServiceIdentityType"
},
"userAssignedIdentities": {
"$ref": "#/definitions/UserAssignedIdentities"
}
},
"required": [
"type"
]
},
"SystemAssignedServiceIdentityType": {
"description": "Type of managed service identity (either system assigned, or none).",
"enum": [
"None",
"SystemAssigned"
],
"type": "string",
"x-ms-enum": {
"name": "SystemAssignedServiceIdentityType",
"modelAsString": true
}
},
"SystemAssignedServiceIdentity": {
"description": "Managed service identity (either system assigned, or none)",
"type": "object",
"properties": {
"principalId": {
"readOnly": true,
"format": "uuid",
"type": "string",
"description": "The service principal ID of the system assigned identity. This property will only be provided for a system assigned identity."
},
"tenantId": {
"readOnly": true,
"format": "uuid",
"type": "string",
"description": "The tenant ID of the system assigned identity. This property will only be provided for a system assigned identity."
},
"type": {
"$ref": "#/definitions/SystemAssignedServiceIdentityType"
}
},
"required": [
"type"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"swagger": "2.0",
"info": {
"version": "5.0",
"title": "Common types"
},
"paths": {},
"definitions": {
"ManagedServiceIdentityWithDelegation": {
"description": "Managed service identity (system assigned and/or user assigned identities and/or delegated identities) - internal use only.",
"type": "object",
"allOf": [
{
"$ref": "managedidentity.json#/definitions/ManagedServiceIdentity"
}
],
"properties": {
"delegatedResources": {
"$ref": "#/definitions/DelegatedResources"
}
}
},
"DelegatedResources": {
"description": "The set of delegated resources. The delegated resources dictionary keys will be source resource internal ids - internal use only.",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DelegatedResource"
}
},
"DelegatedResource": {
"type": "object",
"description": "Delegated resource properties - internal use only.",
"properties": {
"resourceId": {
"description": "The ARM resource id of the delegated resource - internal use only.",
"type": "string"
},
"tenantId": {
"description": "The tenant id of the delegated resource - internal use only.",
"format": "uuid",
"type": "string"
},
"referralResource": {
"description": "The delegation id of the referral delegation (optional) - internal use only.",
"type": "string"
},
"location": {
"description": "The source resource location - internal use only.",
"type": "string"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"swagger": "2.0",
"info": {
"version": "5.0",
"title": "Common types"
},
"paths": {},
"definitions": {
"ManagedOnBehalfOfConfiguration": {
"type": "object",
"description": "Managed-On-Behalf-Of configuration properties. This configuration exists for the resources where a resource provider manages those resources on behalf of the resource owner.",
"properties": {
"moboBrokerResources": {
"type": "array",
"description": "Managed-On-Behalf-Of broker resources",
"items": {
"$ref": "#/definitions/MoboBrokerResource"
},
"x-ms-identifiers": [
"id"
]
}
},
"readOnly": true
},
"MoboBrokerResource": {
"type": "object",
"description": "Managed-On-Behalf-Of broker resource. This resource is created by the Resource Provider to manage some resources on behalf of the user.",
"properties": {
"id": {
"type": "string",
"format": "arm-id",
"description": "Resource identifier of a Managed-On-Behalf-Of broker resource"
}
}
}
}
}
Loading

0 comments on commit 3610e47

Please sign in to comment.