From 78394c512b38e9e353f0e3e140b385060b331aec Mon Sep 17 00:00:00 2001 From: Anton Tayanovskyy Date: Thu, 4 Apr 2024 18:50:05 -0400 Subject: [PATCH] Reduce tfgen failing example warning verbosity (#1843) Before this change we emitted a lot of duplicated warnings for examples that failed to convert. This change removes the duplication and aggregates similar errors so that when every language conversion fails with the same error, which is common when the failure pertains to the HCL->PCL pass of the conversion, we get one warning only not many. --- pkg/tfgen/convert_cli_test.go | 62 +++++++++++++++++++++++++++++++++ pkg/tfgen/docs.go | 64 +++++++++++++++++++++++------------ 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/pkg/tfgen/convert_cli_test.go b/pkg/tfgen/convert_cli_test.go index 8bfac615d..551874926 100644 --- a/pkg/tfgen/convert_cli_test.go +++ b/pkg/tfgen/convert_cli_test.go @@ -287,6 +287,68 @@ resource "azurerm_web_pubsub_custom_certificate" "test" { require.NoError(t, err) }) + t.Run("broken-hcl-warnings", func(t *testing.T) { + md := []byte(strings.ReplaceAll(` +# azurerm_web_pubsub_custom_certificate + +Manages an Azure Web PubSub Custom Certificate. + +## Example Usage + +%%%hcl + +This is some intentionally broken HCL that should not convert. +%%%`, "%%%", "```")) + p := &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "azurerm_web_pubsub_custom_certificate": { + Schema: map[string]*schema.Schema{"name": { + Type: schema.TypeString, + Optional: true, + }}, + }, + }, + } + pi := tfbridge.ProviderInfo{ + P: shimv2.NewProvider(p), + Name: "azurerm", + Version: "0.0.1", + Resources: map[string]*tfbridge.ResourceInfo{ + "azurerm_web_pubsub_custom_certificate": { + Tok: "azure:webpubsub/customCertificate:CustomCertificate", + Docs: &tfbridge.DocInfo{Markdown: md}, + }, + }, + } + + var stdout bytes.Buffer + var stderr bytes.Buffer + + g, err := NewGenerator(GeneratorOptions{ + Package: "azure", + Version: "0.0.1", + PluginHost: &testPluginHost{}, + Language: Schema, + ProviderInfo: pi, + Root: afero.NewBasePathFs(afero.NewOsFs(), t.TempDir()), + Sink: diag.DefaultSink(&stdout, &stderr, diag.FormatOptions{ + Color: colors.Never, + }), + }) + require.NoError(t, err) + + err = g.Generate() + require.NoError(t, err) + + autogold.Expect("").Equal(t, stdout.String()) + //nolint:lll + autogold.Expect(`warning: unable to convert HCL example for Pulumi entity '#/resources/azure:webpubsub/customCertificate:CustomCertificate'. The example will be dropped from any generated docs or SDKs: 1 error occurred: +* [csharp, go, java, python, typescript, yaml] : unexpected HCL snippet in Convert "\nThis is some intentionally broken HCL that should not convert.\n{}"; + + +`).Equal(t, stderr.String()) + }) + t.Run("regress-1839", func(t *testing.T) { mdPath := filepath.Join( "test_data", diff --git a/pkg/tfgen/docs.go b/pkg/tfgen/docs.go index 0896998bf..5a73bbc6c 100644 --- a/pkg/tfgen/docs.go +++ b/pkg/tfgen/docs.go @@ -1603,7 +1603,6 @@ func (g *Generator) convertHCLToString(e *Example, hclCode, path, languageName s // fileName starts with a "/" which is not present in the resulting error, so we need to skip the first rune. errMsg := strings.ReplaceAll(diags.Error(), fileName[1:], "") - g.warn("failed to convert HCL for %s to %v: %v", path, languageName, errMsg) g.coverageTracker.languageConversionFailure(e, languageName, diags) return errors.New(errMsg) } @@ -1746,7 +1745,6 @@ func (g *Generator) convertHCL(e *Example, hcl, path string, languages []string) hclConversions := map[string]string{} var result strings.Builder - var err error failedLangs := map[string]error{} @@ -1755,39 +1753,61 @@ func (g *Generator) convertHCL(e *Example, hcl, path string, languages []string) hclConversions[lang], convertErr = g.convertHCLToString(e, hcl, path, lang) if convertErr != nil { failedLangs[lang] = convertErr - err = multierror.Append(err, convertErr) } } result.WriteString(hclConversionsToString(hclConversions)) - if len(failedLangs) == 0 { + + switch { + // Success + case len(failedLangs) == 0: + return result.String(), nil + // Complete failure - every language conversion failed; error + case len(failedLangs) == len(languages): + err := g.warnUnableToConvertHCLExample(path, failedLangs) + return "", err + // Partial failure - not returning an error but still emit the warning + default: + err := g.warnUnableToConvertHCLExample(path, failedLangs) + contract.IgnoreError(err) return result.String(), nil } +} - isCompleteFailure := len(failedLangs) == len(languages) - - if isCompleteFailure { - g.warn(fmt.Sprintf("unable to convert HCL example for Pulumi entity '%s': %v. The example will be dropped "+ - "from any generated docs or SDKs.", path, err)) - - return "", err +func (g *Generator) warnUnableToConvertHCLExample(path string, failedLangs map[string]error) error { + // Index sets of languages by error message to avoid emitting similar errors for each language. + languagesByErrMsg := map[string]map[string]struct{}{} + for lang, convertErr := range failedLangs { + errMsg := convertErr.Error() + if _, ok := languagesByErrMsg[errMsg]; !ok { + languagesByErrMsg[errMsg] = map[string]struct{}{} + } + languagesByErrMsg[errMsg][lang] = struct{}{} } - // Log the results when an example fails to convert to some languages, but not all - var failedLangsStrings []string + var err error - for lang := range failedLangs { - failedLangsStrings = append(failedLangsStrings, lang) - g.warn(fmt.Sprintf("unable to convert HCL example for Pulumi entity '%s' in the following language(s): "+ - "%s. Examples for these languages will be dropped from any generated docs or SDKs.", - path, strings.Join(failedLangsStrings, ", "))) + seen := map[string]struct{}{} + for _, convertErr := range failedLangs { + if _, dup := seen[convertErr.Error()]; dup { + continue + } + errMsg := convertErr.Error() + seen[errMsg] = struct{}{} - // At least one language out of the given set has been generated, which is considered a success - //nolint:ineffassign - err = nil + langs := []string{} + for l := range languagesByErrMsg[errMsg] { + langs = append(langs, l) + } + sort.Strings(langs) + ls := strings.Join(langs, ", ") // all languages that have this error + err = multierror.Append(err, fmt.Errorf("[%s] %w", ls, convertErr)) } - return result.String(), nil + g.warn("unable to convert HCL example for Pulumi entity '%s'. The example will be dropped "+ + "from any generated docs or SDKs: %v", path, err) + + return err } // genLanguageToSlice maps a Language on a Generator to a slice of strings suitable to pass to HCL conversion.