Skip to content

Commit

Permalink
importer-rest-api-specs - data workaround for AlertsManagement an…
Browse files Browse the repository at this point in the history
…d move call to `removeUnusedItems` (#4020)

* add workaround for alertsmanagement and run removeUnusedItems after all the processing has been done

* call removeUnusedItems in test helper to correctly replicate the parsing process for swagger files

* review comments
  • Loading branch information
stephybun authored Apr 2, 2024
1 parent 4235b69 commit 6580978
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dataworkarounds

import (
"fmt"
"github.com/hashicorp/go-azure-helpers/lang/pointer"

importerModels "github.com/hashicorp/pandora/tools/importer-rest-api-specs/models"
)

var _ workaround = workaroundAlertsManagement{}

// workaroundAlertsManagement works around the missing discriminator implementations for
// ActionRuleProperties. This workaround can be removed when v4.0 of the AzureRM Provider
// has been released.
type workaroundAlertsManagement struct {
}

func (workaroundAlertsManagement) IsApplicable(apiDefinition *importerModels.AzureApiDefinition) bool {
serviceMatches := apiDefinition.ServiceName == "AlertsManagement"
apiVersionMatches := apiDefinition.ApiVersion == "2019-05-05-preview"
return serviceMatches && apiVersionMatches
}

func (workaroundAlertsManagement) Name() string {
return "AlertsManagement"
}

func (workaroundAlertsManagement) Process(apiDefinition importerModels.AzureApiDefinition) (*importerModels.AzureApiDefinition, error) {
resource, ok := apiDefinition.Resources["ActionRules"]
if !ok {
return nil, fmt.Errorf("expected a Resource named `ActionRules`")
}
_, ok = resource.Models["ActionRuleProperties"]
if !ok {
return nil, fmt.Errorf("expected a Resource named `ActionRuleProperties`")
}

modelNames := []string{
"ActionGroup",
"Diagnostics",
"Suppression",
}

for _, name := range modelNames {
model, ok := resource.Models[name]
if !ok {
return nil, fmt.Errorf("expected a Model named `%s`", name)
}
model.DiscriminatedValue = pointer.To(name)
model.ParentTypeName = pointer.To("ActionRuleProperties")
model.FieldNameContainingDiscriminatedValue = pointer.To("Type")

resource.Models[name] = model
}
apiDefinition.Resources["ActionRules"] = resource

return &apiDefinition, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

var workarounds = []workaround{
// These workarounds are related to issues with the upstream API Definitions
workaroundAlertsManagement{},
workaroundAuthorization25080{},
workaroundDigitalTwins25120{},
workaroundAutomation25108{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func ParseSwaggerFileForTesting(t *testing.T, file string, serviceName *string)
t.Fatalf("parsing file %q: %+v", file, err)
}

// removeUnusedItems used to be called as we iterated through the swagger files
// it's now called once after all the processing for a service has been done so must be called here
// to replicate the entire parsing process for swagger files
out.Resources = removeUnusedItems(out.Resources)

return out, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func LoadAndParseFiles(directory string, fileNames []string, serviceName, apiVer

out = *output

for _, service := range out {
service.Resources = removeUnusedItems(service.Resources)
}

if len(out) > 1 {
return nil, fmt.Errorf("internal-error:the Swagger files for Service %q / API Version %q contained multiple resources (%d total)", serviceName, apiVersion, len(out))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,70 @@ import (

"github.com/hashicorp/pandora/tools/data-api-sdk/v1/helpers"
"github.com/hashicorp/pandora/tools/data-api-sdk/v1/models"
"github.com/hashicorp/pandora/tools/importer-rest-api-specs/components/parser/internal"
importerModels "github.com/hashicorp/pandora/tools/importer-rest-api-specs/models"
)

func removeUnusedItems(operations map[string]models.SDKOperation, resourceIds map[string]models.ResourceID, result internal.ParseResult) (internal.ParseResult, map[string]models.ResourceID) {
func removeUnusedItems(resources map[string]importerModels.AzureApiResource) map[string]importerModels.AzureApiResource {
// The ordering matters here, we need to remove the ResourceIDs first since
// they contain references to Constants - as do Models, so remove unused
// Resource IDs, then Models, then Constants else we can have orphaned
// constants within a package

resourceIdsForThisResource := make(map[string]models.ResourceID)
for k, v := range resourceIds {
resourceIdsForThisResource[k] = v
}
unusedResourceIds := findUnusedResourceIds(operations, resourceIdsForThisResource)
for len(unusedResourceIds) > 0 {
for _, resourceIdName := range unusedResourceIds {
delete(resourceIdsForThisResource, resourceIdName)
for resource, details := range resources {
resourceIdsForThisResource := make(map[string]models.ResourceID)
for k, v := range details.ResourceIds {
resourceIdsForThisResource[k] = v
}
unusedResourceIds := findUnusedResourceIds(details.Operations, resourceIdsForThisResource)
for len(unusedResourceIds) > 0 {
for _, resourceIdName := range unusedResourceIds {
delete(resourceIdsForThisResource, resourceIdName)
}

// then go around again
unusedResourceIds = findUnusedResourceIds(operations, resourceIdsForThisResource)
}
// then go around again
unusedResourceIds = findUnusedResourceIds(details.Operations, resourceIdsForThisResource)
}

unusedModels := findUnusedModels(operations, result)
for len(unusedModels) > 0 {
// remove those models
for _, modelName := range unusedModels {
delete(result.Models, modelName)
unusedModels := findUnusedModels(details.Operations, details.Models)
for len(unusedModels) > 0 {
// remove those models
for _, modelName := range unusedModels {
delete(details.Models, modelName)
}

// then go around again
unusedModels = findUnusedModels(details.Operations, details.Models)
}

// then go around again
unusedModels = findUnusedModels(operations, result)
}
unusedConstants := findUnusedConstants(details.Operations, resourceIdsForThisResource, details.Models, details.Constants)
for len(unusedConstants) > 0 {
// remove those constants
for _, constantName := range unusedConstants {
delete(details.Constants, constantName)
}

unusedConstants := findUnusedConstants(operations, resourceIdsForThisResource, result)
for len(unusedConstants) > 0 {
// remove those constants
for _, constantName := range unusedConstants {
delete(result.Constants, constantName)
// then go around again
unusedConstants = findUnusedConstants(details.Operations, resourceIdsForThisResource, details.Models, details.Constants)
}

// then go around again
unusedConstants = findUnusedConstants(operations, resourceIdsForThisResource, result)
resources[resource] = importerModels.AzureApiResource{
Constants: details.Constants,
Models: details.Models,
Operations: details.Operations,
ResourceIds: resourceIdsForThisResource,
Terraform: details.Terraform,
}
}

return result, resourceIdsForThisResource
return resources
}

func findUnusedConstants(operations map[string]models.SDKOperation, resourceIds map[string]models.ResourceID, result internal.ParseResult) []string {
func findUnusedConstants(operations map[string]models.SDKOperation, resourceIds map[string]models.ResourceID, resourceModels map[string]models.SDKModel, resourceConstants map[string]models.SDKConstant) []string {
unusedConstants := make(map[string]struct{}, 0)
for constantName := range result.Constants {
for constantName := range resourceConstants {
// constants are either housed inside a Model
usedInAModel := false
for _, model := range result.Models {
for _, model := range resourceModels {
for _, field := range model.Fields {
definition := helpers.InnerMostSDKObjectDefinition(field.ObjectDefinition)
if definition.Type != models.ReferenceSDKObjectDefinitionType {
Expand Down Expand Up @@ -154,9 +164,9 @@ func topLevelOptionsObjectDefinition(input models.SDKOperationOptionObjectDefini
return input
}

func findUnusedModels(operations map[string]models.SDKOperation, result internal.ParseResult) []string {
func findUnusedModels(operations map[string]models.SDKOperation, resourceModels map[string]models.SDKModel) []string {
unusedModels := make(map[string]struct{})
for modelName, model := range result.Models {
for modelName, model := range resourceModels {
if modelName == "" {
// if the model name is empty this is an unused model (so is safe to remove) - but is a bug
// TODO: track down empty models and then remove this
Expand Down Expand Up @@ -201,7 +211,7 @@ func findUnusedModels(operations map[string]models.SDKOperation, result internal

// or on other models
usedInAModel := false
for thisModelName, thisModel := range result.Models {
for thisModelName, thisModel := range resourceModels {
if thisModelName == modelName {
continue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,16 @@ func (d *SwaggerDefinition) parseResourcesWithinSwaggerTag(tag *string, resource
// then switch out any custom types (e.g. Identity)
result = switchOutCustomTypesAsNeeded(result)

// finally remove any models and constants which aren't referenced / have been replaced
constantsAndModels, resourceIdNamesToUris := removeUnusedItems(*operations, resourceIds.NamesToResourceIDs, result)

// if there's nothing here, there's no point generating a package
if len(*operations) == 0 {
return nil, nil
}

resource := importerModels.AzureApiResource{
Constants: constantsAndModels.Constants,
Models: constantsAndModels.Models,
Constants: result.Constants,
Models: result.Models,
Operations: *operations,
ResourceIds: resourceIdNamesToUris,
ResourceIds: resourceIds.NamesToResourceIDs,
}

// first Normalize the names, meaning `foo` -> `Foo` for consistency
Expand Down

0 comments on commit 6580978

Please sign in to comment.