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

Introduce schema and metadata overlays for custom resources #3086

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
45 changes: 45 additions & 0 deletions provider/pkg/gen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,22 @@ func genMixins(pkg *pschema.PackageSpec, metadata *resources.AzureAPIMetadata) e
metadata.Resources[tok] = r
}

for tok, overlay := range customresources.SchemaOverlays() {
res, has := pkg.Resources[tok]
if !has {
return errors.Errorf("Resource %q is not defined", tok)
}
pkg.Resources[tok] = mergeSchemaOverlay(res, overlay)
}

for tok, overlay := range customresources.MetaOverlays() {
meta, has := metadata.Resources[tok]
if !has {
return errors.Errorf("Metadata %q is not defined", tok)
}
metadata.Resources[tok] = mergeMetaOverlay(meta, overlay)
}

// 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 @@ -556,6 +572,35 @@ func genMixins(pkg *pschema.PackageSpec, metadata *resources.AzureAPIMetadata) e
return nil
}

func mergeSchemaOverlay(res, overlay pschema.ResourceSpec) pschema.ResourceSpec {
// merge the additional properties into the existing resource
for propName, prop := range overlay.Properties {
res.Properties[propName] = prop
}
for propName, prop := range overlay.InputProperties {
res.InputProperties[propName] = prop
}
return res
}

func mergeMetaOverlay(meta, overlay resources.AzureAPIResource) resources.AzureAPIResource {
for _, param := range overlay.PutParameters {
if param.Location != "body" {
meta.PutParameters = append(meta.PutParameters, param)
} else {
body, ok := meta.BodyParameter()
if !ok {
meta.PutParameters = append(meta.PutParameters, param)
} else {
for propName, prop := range param.Body.Properties {
body.Body.Properties[propName] = prop
}
}
}
}
return meta
}

// Microsoft-specific API extension constants.
const (
extensionClientFlatten = "x-ms-client-flatten"
Expand Down
236 changes: 236 additions & 0 deletions provider/pkg/gen/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/go-openapi/spec"
"github.com/pulumi/pulumi-azure-native/v2/provider/pkg/openapi"
"github.com/pulumi/pulumi-azure-native/v2/provider/pkg/resources"
"github.com/pulumi/pulumi/pkg/v3/codegen"
pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -145,3 +146,238 @@ func TestFindResourcesBodyRefs(t *testing.T) {
expected := []string{"#/definitions/Subnet"}
assert.Equal(t, expected, actual)
}

func TestMergeSchemaOverlays(t *testing.T) {
strProp := pschema.PropertySpec{
TypeSpec: pschema.TypeSpec{
Type: "string",
},
}
intProp := pschema.PropertySpec{
TypeSpec: pschema.TypeSpec{
Type: "integer",
},
}

t.Run("addition", func(t *testing.T) {
res := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": strProp,
},
}

overlay := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"bar": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"barInput": strProp,
},
}

expected := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": strProp,
"bar": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": strProp,
"barInput": strProp,
},
}

assert.Equal(t, expected, mergeSchemaOverlay(res, overlay))
})

t.Run("overwrite", func(t *testing.T) {
res := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": strProp,
},
}

overlay := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": intProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": intProp,
},
}

expected := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": intProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": intProp,
},
}

assert.Equal(t, expected, mergeSchemaOverlay(res, overlay))
})

t.Run("combined", func(t *testing.T) {
res := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": strProp,
},
}

overlay := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": intProp,
"bar": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"barInput": strProp,
"anotherInput": strProp,
},
}

expected := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": intProp,
"bar": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": strProp,
"barInput": strProp,
"anotherInput": strProp,
},
}

assert.Equal(t, expected, mergeSchemaOverlay(res, overlay))
})

t.Run("no-op", func(t *testing.T) {
res := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Properties: map[string]pschema.PropertySpec{
"foo": strProp,
},
},
InputProperties: map[string]pschema.PropertySpec{
"fooInput": strProp,
},
}

overlay := pschema.ResourceSpec{
InputProperties: map[string]pschema.PropertySpec{
"fooInput": strProp,
},
}

assert.Equal(t, res, mergeSchemaOverlay(res, overlay))
})
}

func TestMergeMetaOverlays(t *testing.T) {
t.Run("addition", func(t *testing.T) {
meta := resources.AzureAPIResource{
PutParameters: []resources.AzureAPIParameter{
{
Location: "path",
Name: "foo",
},
},
}

overlay := resources.AzureAPIResource{
PutParameters: []resources.AzureAPIParameter{
{
Location: "path",
Name: "bar",
},
},
}

merged := mergeMetaOverlay(meta, overlay)
assert.Len(t, merged.PutParameters, 2)
assert.Contains(t, merged.PutParameters, meta.PutParameters[0])
assert.Contains(t, merged.PutParameters, overlay.PutParameters[0])
})

t.Run("addition in body", func(t *testing.T) {
meta := resources.AzureAPIResource{
PutParameters: []resources.AzureAPIParameter{
{
Location: "body",
Name: "properties",
Body: &resources.AzureAPIType{
Properties: map[string]resources.AzureAPIProperty{
"foo": {
Type: "string",
},
},
},
},
},
}

overlay := resources.AzureAPIResource{
PutParameters: []resources.AzureAPIParameter{
{
Location: "body",
Name: "properties",
Body: &resources.AzureAPIType{
Properties: map[string]resources.AzureAPIProperty{
"bar": {
Type: "string",
},
},
},
},
},
}

merged := mergeMetaOverlay(meta, overlay)
assert.Len(t, merged.PutParameters, 1)
assert.Len(t, merged.PutParameters[0].Body.Properties, 2)
assert.Contains(t, merged.PutParameters[0].Body.Properties, "foo")
assert.Contains(t, merged.PutParameters[0].Body.Properties, "bar")
})

t.Run("no-op", func(t *testing.T) {
meta := resources.AzureAPIResource{
PutParameters: []resources.AzureAPIParameter{},
}

overlay := resources.AzureAPIResource{
PutParameters: []resources.AzureAPIParameter{},
}

expected := resources.AzureAPIResource{
PutParameters: []resources.AzureAPIParameter{},
}

assert.Equal(t, expected, mergeMetaOverlay(meta, overlay))
})
}
32 changes: 29 additions & 3 deletions provider/pkg/resources/customresources/customresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ type CustomResource struct {
// Auxiliary types defined for this resource. Optional.
Types map[string]schema.ComplexTypeSpec
// Resource schema. Optional, by default the schema is assumed to be included in Azure Open API specs.
Schema *schema.ResourceSpec
Schema *schema.ResourceSpec
SchemaOverlay *schema.ResourceSpec
// Resource metadata. Defines the parameters and properties that are used for diff calculation and validation.
// Optional, by default the metadata is assumed to be derived from Azure Open API specs.
Meta *AzureAPIResource
Meta *AzureAPIResource
MetaOverlay *AzureAPIResource
// Create a new resource from a map of input values. Returns a map of resource outputs that match the schema shape.
Create func(context.Context, resource.PropertyMap) (map[string]interface{}, error)
// Read the state of an existing resource. Constructs the resource ID based on input values. Returns a map of
Expand Down Expand Up @@ -106,6 +108,18 @@ func SchemaMixins() map[string]schema.ResourceSpec {
return specs
}

// SchemaOverlays returns the map of custom resource schema definitions per resource token, to be
// merged with the definitions from the OpenAPI spec.
func SchemaOverlays() map[string]schema.ResourceSpec {
overlays := map[string]schema.ResourceSpec{}
for _, r := range featureLookup {
if r.tok != "" && r.SchemaOverlay != nil {
overlays[r.tok] = *r.SchemaOverlay
}
}
return overlays
}

// SchemaTypeMixins returns the map of custom resource schema definitions per resource token.
func SchemaTypeMixins() map[string]schema.ComplexTypeSpec {
types := map[string]schema.ComplexTypeSpec{}
Expand All @@ -117,7 +131,7 @@ func SchemaTypeMixins() map[string]schema.ComplexTypeSpec {
return types
}

// SchemaMixins returns the map of custom resource metadata definitions per resource token.
// MetaMixins returns the map of custom resource metadata definitions per resource token.
func MetaMixins() map[string]AzureAPIResource {
meta := map[string]AzureAPIResource{}
for _, r := range featureLookup {
Expand All @@ -127,3 +141,15 @@ func MetaMixins() map[string]AzureAPIResource {
}
return meta
}

// MetaOverlays returns the map of custom resource metadata definitions per resource token, to be
// merged with the generated definition.
func MetaOverlays() map[string]AzureAPIResource {
overlays := map[string]AzureAPIResource{}
for _, r := range featureLookup {
if r.tok != "" && r.MetaOverlay != nil {
overlays[r.tok] = *r.MetaOverlay
}
}
return overlays
}
Loading