Skip to content

Commit

Permalink
Reduce tfgen failing example warning verbosity (#1843)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
t0yv0 committed Apr 4, 2024
1 parent 88fafd0 commit 78394c5
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 22 deletions.
62 changes: 62 additions & 0 deletions pkg/tfgen/convert_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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] <nil>: 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",
Expand Down
64 changes: 42 additions & 22 deletions pkg/tfgen/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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{}

Expand All @@ -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.
Expand Down

0 comments on commit 78394c5

Please sign in to comment.