From 4b174a03ca54672da1f2b2d3aa1957068c3bebb1 Mon Sep 17 00:00:00 2001 From: guineveresaenger Date: Thu, 9 Jan 2025 14:35:38 -0800 Subject: [PATCH 1/6] Add check to see if any one example converted successfully. If so, write code chooser. Remove source replacement for all conversion errors. --- pkg/tfgen/convert_cli.go | 3 +- pkg/tfgen/installation_docs.go | 12 ++++- pkg/tfgen/installation_docs_test.go | 69 +++++++++++++++++++---------- 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/pkg/tfgen/convert_cli.go b/pkg/tfgen/convert_cli.go index 41d6e014f..2f6be5cf5 100644 --- a/pkg/tfgen/convert_cli.go +++ b/pkg/tfgen/convert_cli.go @@ -651,8 +651,7 @@ func (cc *cliConverter) singleExampleFromPCLToLanguage(example translatedExample source, diags, _ := cc.convertPCL(example.PCL, lang) diags = cc.postProcessDiagnostics(diags.Extend(example.Diagnostics)) if diags.HasErrors() { - source = "Example currently unavailable in this language\n" - err = fmt.Errorf("failed to convert an example: %s", diags.Error()) + err = fmt.Errorf("conversion errors: %s", diags.Error()) } source = "```" + lang + "\n" + source + "```" return source, err diff --git a/pkg/tfgen/installation_docs.go b/pkg/tfgen/installation_docs.go index 42d6c9773..57108d4e1 100644 --- a/pkg/tfgen/installation_docs.go +++ b/pkg/tfgen/installation_docs.go @@ -260,6 +260,7 @@ func convertExample(g *Generator, code string, exampleNumber int) (string, error choosableEnd = "\n{{% /choosable %}}\n" ) exampleContent := chooserStart + successfulConversion := false // Generate each language in turn and mark up the output with the correct Hugo shortcodes. for _, lang := range langs { @@ -273,10 +274,17 @@ func convertExample(g *Generator, code string, exampleNumber int) (string, error if err != nil { g.warn(err.Error()) } + if convertedLang != "" { + successfulConversion = true + } + exampleContent += choosableStart + pulumiYAML + convertedLang + choosableEnd } - exampleContent += chooserEnd - return exampleContent, nil + + if successfulConversion { + return exampleContent + chooserEnd, nil + } + return "", nil } type titleRemover struct{} diff --git a/pkg/tfgen/installation_docs_test.go b/pkg/tfgen/installation_docs_test.go index 9b3c8b44b..38f360366 100644 --- a/pkg/tfgen/installation_docs_test.go +++ b/pkg/tfgen/installation_docs_test.go @@ -449,32 +449,55 @@ func TestTranslateCodeBlocks(t *testing.T) { } pclsMap := make(map[string]translatedExample) - tc := testCase{ - name: "Translates HCL from examples ", - contentStr: readfile(t, "test_data/installation-docs/configuration.md"), - expected: readfile(t, "test_data/installation-docs/configuration-expected.md"), - g: &Generator{ - sink: mockSink{}, - cliConverterState: &cliConverter{ - info: p, - pcls: pclsMap, + testCases := []testCase{ + { + name: "Translates HCL from examples ", + contentStr: readfile(t, "test_data/installation-docs/configuration.md"), + expected: readfile(t, "test_data/installation-docs/configuration-expected.md"), + g: &Generator{ + sink: mockSink{}, + cliConverterState: &cliConverter{ + info: p, + pcls: pclsMap, + }, + language: RegistryDocs, + }, + }, + { + name: "Does not translate an invalid example and leaves example block blank", + contentStr: readfile(t, "test_data/installation-docs/invalid-example.md"), + expected: readfile(t, "test_data/installation-docs/invalid-example-expected.md"), + g: &Generator{ + sink: mockSink{}, + cliConverterState: &cliConverter{ + info: p, + pcls: pclsMap, + }, + language: RegistryDocs, }, - language: RegistryDocs, }, } - t.Run(tc.name, func(t *testing.T) { - if runtime.GOOS == "windows" { - // Currently there is a test issue in CI/test setup: - // - // convertViaPulumiCLI: failed to clean up temp bridge-examples.json file: The - // process cannot access the file because it is being used by another process. - t.Skipf("Skipping on Windows due to a test setup issue") - } - t.Setenv("PULUMI_CONVERT", "1") - actual, err := translateCodeBlocks(tc.contentStr, tc.g) - require.NoError(t, err) - require.Equal(t, tc.expected, actual) - }) + + for _, tt := range testCases { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + if runtime.GOOS == "windows" { + // Currently there is a test issue in CI/test setup: + // + // convertViaPulumiCLI: failed to clean up temp bridge-examples.json file: The + // process cannot access the file because it is being used by another process. + t.Skipf("Skipping on Windows due to a test setup issue") + } + t.Setenv("PULUMI_CONVERT", "1") + actual, err := translateCodeBlocks(tt.contentStr, tt.g) + if tt.name == "Does not translate an invalid example and leaves example block blank" { + writefile(t, "test_data/installation-docs/invalid-example-actual", []byte(actual)) + } + require.NoError(t, err) + require.Equal(t, tt.expected, actual) + }) + } } func TestSkipSectionHeadersByContent(t *testing.T) { From d31e03992699e8627e5d475f88800ad1fcc018ce Mon Sep 17 00:00:00 2001 From: guineveresaenger Date: Thu, 9 Jan 2025 14:36:31 -0800 Subject: [PATCH 2/6] Add test files --- .../invalid-example-expected.md | 9 +++++++ .../installation-docs/invalid-example.md | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 pkg/tfgen/test_data/installation-docs/invalid-example-expected.md create mode 100644 pkg/tfgen/test_data/installation-docs/invalid-example.md diff --git a/pkg/tfgen/test_data/installation-docs/invalid-example-expected.md b/pkg/tfgen/test_data/installation-docs/invalid-example-expected.md new file mode 100644 index 000000000..86320c991 --- /dev/null +++ b/pkg/tfgen/test_data/installation-docs/invalid-example-expected.md @@ -0,0 +1,9 @@ +This example is invalid and should not be translated for this test to pass + +## Example Usage + + + +## Configuration Reference + +The following configuration inputs are supported: diff --git a/pkg/tfgen/test_data/installation-docs/invalid-example.md b/pkg/tfgen/test_data/installation-docs/invalid-example.md new file mode 100644 index 000000000..9b4704c21 --- /dev/null +++ b/pkg/tfgen/test_data/installation-docs/invalid-example.md @@ -0,0 +1,27 @@ +This example is invalid and should not be translated for this test to pass + +## Example Usage + +```hcl +# Configure the OpenStack Provider +proider "simple-provider" { + user_name = "admin" + tenant_name = "admin" + password = "pwd" + auth_url = "http://myauthurl:5000/v3" + region = "RegionOne" +} +## Define a resource +resource "simple_resource" "a_resource" { + input_one = "hello" + input_two = true +} + +output "some_output" { + value = simple_resource.a_resource.result +} +``` + +## Configuration Reference + +The following configuration inputs are supported: From 6ff82b278d7f022fc028e529caa5b29c27892e04 Mon Sep 17 00:00:00 2001 From: guineveresaenger Date: Thu, 9 Jan 2025 16:36:14 -0800 Subject: [PATCH 3/6] Walk the document to see if the Example Usage section is empty. Skip that section if so. --- pkg/tfgen/installation_docs.go | 42 +++++++++++++++++++ pkg/tfgen/installation_docs_test.go | 25 +++++++++-- .../test_data/skip-empty-examples/expected.md | 9 ++++ .../test_data/skip-empty-examples/input.md | 13 ++++++ 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 pkg/tfgen/test_data/skip-empty-examples/expected.md create mode 100644 pkg/tfgen/test_data/skip-empty-examples/input.md diff --git a/pkg/tfgen/installation_docs.go b/pkg/tfgen/installation_docs.go index 57108d4e1..afae33695 100644 --- a/pkg/tfgen/installation_docs.go +++ b/pkg/tfgen/installation_docs.go @@ -66,6 +66,12 @@ func plainDocsParser(docFile *DocFile, g *Generator) ([]byte, error) { return nil, err } + // If the code translation resulted in an empty examples section, remove it + contentStr, err = removeEmptyExamples(contentStr) + if err != nil { + return nil, err + } + // Apply post-code translation edit rules. This applies all default edit rules and provider-supplied edit rules in // the post-code translation phase. contentBytes, err = g.editRules.apply(docFile.FileName, []byte(contentStr), info.PostCodeTranslation) @@ -485,3 +491,39 @@ func getProviderDisplayName(g *Generator) string { capitalize := cases.Title(language.English) return capitalize.String(providerName) } + +func removeEmptyExamples(contentStr string) (string, error) { + if checkExamplesEmpty(contentStr) { + mybytes, err := SkipSectionByHeaderContent([]byte(contentStr), func(headerText string) bool { + return headerText == "Example Usage" + }) + contentStr = string(mybytes) + return contentStr, err + } + return contentStr, nil + +} + +func checkExamplesEmpty(contentStr string) bool { + + gm := goldmark.New(goldmark.WithExtensions(parse.TFRegistryExtension)) + astNode := gm.Parser().Parse(text.NewReader([]byte(contentStr))) + + isEmpty := false + + err := ast.Walk(astNode, func(n ast.Node, entering bool) (ast.WalkStatus, error) { + if section, ok := n.(*section.Section); ok && entering { + sectionText := section.Text([]byte(contentStr)) + // A little confusingly, we check if the section text _only_ contains the title, "Example Usage". + // Non-empty sections contain the title + content, so if we see only the title, the section is empty. + if string(sectionText) == "Example Usage" { + isEmpty = true + return ast.WalkStop, nil + } + } + return ast.WalkContinue, nil + }) + contract.AssertNoErrorf(err, "impossible: ast.Walk should never error") + + return isEmpty +} diff --git a/pkg/tfgen/installation_docs_test.go b/pkg/tfgen/installation_docs_test.go index 38f360366..bb04ab15d 100644 --- a/pkg/tfgen/installation_docs_test.go +++ b/pkg/tfgen/installation_docs_test.go @@ -491,9 +491,6 @@ func TestTranslateCodeBlocks(t *testing.T) { } t.Setenv("PULUMI_CONVERT", "1") actual, err := translateCodeBlocks(tt.contentStr, tt.g) - if tt.name == "Does not translate an invalid example and leaves example block blank" { - writefile(t, "test_data/installation-docs/invalid-example-actual", []byte(actual)) - } require.NoError(t, err) require.Equal(t, tt.expected, actual) }) @@ -640,3 +637,25 @@ func assertEqualHTML(t *testing.T, expected, actual string) bool { } return assert.Equal(t, expectedBuf.String(), outputBuf.String()) } + +func TestRemoveEmptyExamples(t *testing.T) { + t.Parallel() + type testCase struct { + name string + input string + expected string + } + + tc := testCase{ + name: "An empty Example Usage section is skipped", + input: readTestFile(t, "skip-empty-examples/input.md"), + expected: readTestFile(t, "skip-empty-examples/expected.md"), + } + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + actual, err := removeEmptyExamples(tc.input) + require.NoError(t, err) + assertEqualHTML(t, tc.expected, actual) + }) +} diff --git a/pkg/tfgen/test_data/skip-empty-examples/expected.md b/pkg/tfgen/test_data/skip-empty-examples/expected.md new file mode 100644 index 000000000..612284f2f --- /dev/null +++ b/pkg/tfgen/test_data/skip-empty-examples/expected.md @@ -0,0 +1,9 @@ +# PagerDuty Provider + +[PagerDuty](https://www.pagerduty.com/) is an incident management platform that provides reliable notifications, automatic escalations, on-call scheduling, and other functionality to help teams detect and address unplanned work in real-time. + +Use the navigation to the left to read about the available resources. + +## Perfectly allowed header for a helpful section + +This section should not be skipped \ No newline at end of file diff --git a/pkg/tfgen/test_data/skip-empty-examples/input.md b/pkg/tfgen/test_data/skip-empty-examples/input.md new file mode 100644 index 000000000..5b68e0497 --- /dev/null +++ b/pkg/tfgen/test_data/skip-empty-examples/input.md @@ -0,0 +1,13 @@ +# PagerDuty Provider + +[PagerDuty](https://www.pagerduty.com/) is an incident management platform that provides reliable notifications, automatic escalations, on-call scheduling, and other functionality to help teams detect and address unplanned work in real-time. + +Use the navigation to the left to read about the available resources. + +## Example Usage + + + +## Perfectly allowed header for a helpful section + +This section should not be skipped \ No newline at end of file From c1ee08d79d7f694fad7760d5a6117ccdc2e9115f Mon Sep 17 00:00:00 2001 From: guineveresaenger Date: Thu, 9 Jan 2025 17:31:35 -0800 Subject: [PATCH 4/6] Check if PCL exists and return early. Create exampleUnavailable output. --- pkg/tfgen/convert_cli.go | 11 ++++++++--- pkg/tfgen/installation_docs.go | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/tfgen/convert_cli.go b/pkg/tfgen/convert_cli.go index 2f6be5cf5..fee442e87 100644 --- a/pkg/tfgen/convert_cli.go +++ b/pkg/tfgen/convert_cli.go @@ -45,6 +45,10 @@ import ( "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen/internal/autofill" ) +const ( + exampleUnavailable = "Example currently unavailable in this language\n" +) + func cliConverterEnabled() bool { return cmdutil.IsTruthy(os.Getenv("PULUMI_CONVERT")) } @@ -645,14 +649,15 @@ func (cc *cliConverter) singleExampleFromHCLToPCL(path, hclCode string) (transla func (cc *cliConverter) singleExampleFromPCLToLanguage(example translatedExample, lang string) (string, error) { var err error - if example.PCL == "" { - return "", nil - } source, diags, _ := cc.convertPCL(example.PCL, lang) diags = cc.postProcessDiagnostics(diags.Extend(example.Diagnostics)) if diags.HasErrors() { err = fmt.Errorf("conversion errors: %s", diags.Error()) } + + if source == "" { + source = exampleUnavailable + } source = "```" + lang + "\n" + source + "```" return source, err } diff --git a/pkg/tfgen/installation_docs.go b/pkg/tfgen/installation_docs.go index afae33695..e44cb5e1f 100644 --- a/pkg/tfgen/installation_docs.go +++ b/pkg/tfgen/installation_docs.go @@ -259,6 +259,10 @@ func convertExample(g *Generator, code string, exampleNumber int) (string, error return "", err } + if pclExample.PCL == "" { + return "", nil + } + langs := genLanguageToSlice(g.language) const ( chooserStart = `{{< chooser language "typescript,python,go,csharp,java,yaml" >}}` + "\n" @@ -280,10 +284,9 @@ func convertExample(g *Generator, code string, exampleNumber int) (string, error if err != nil { g.warn(err.Error()) } - if convertedLang != "" { + if convertedLang != exampleUnavailable { successfulConversion = true } - exampleContent += choosableStart + pulumiYAML + convertedLang + choosableEnd } From 5fb65af6d6a45a34b83b65dd8e026199ef47d2d1 Mon Sep 17 00:00:00 2001 From: guineveresaenger Date: Thu, 9 Jan 2025 18:40:18 -0800 Subject: [PATCH 5/6] Handle empty config YAML and PCL conditions explicitly and show tests for all possible combinations --- pkg/tfgen/installation_docs.go | 21 ++- pkg/tfgen/installation_docs_test.go | 26 ++++ .../example-only-expected.md | 132 ++++++++++++++++++ .../installation-docs/example-only.md | 19 +++ .../installation-docs/invalid-example.md | 2 +- .../provider-config-only-expected.md | 26 ++++ .../installation-docs/provider-config-only.md | 18 +++ 7 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 pkg/tfgen/test_data/installation-docs/example-only-expected.md create mode 100644 pkg/tfgen/test_data/installation-docs/example-only.md create mode 100644 pkg/tfgen/test_data/installation-docs/provider-config-only-expected.md create mode 100644 pkg/tfgen/test_data/installation-docs/provider-config-only.md diff --git a/pkg/tfgen/installation_docs.go b/pkg/tfgen/installation_docs.go index e44cb5e1f..18d547bb0 100644 --- a/pkg/tfgen/installation_docs.go +++ b/pkg/tfgen/installation_docs.go @@ -227,9 +227,6 @@ func translateCodeBlocks(contentStr string, g *Generator) (string, error) { // This function renders the Pulumi.yaml config file for a given language if configuration is included in the example. func processConfigYaml(pulumiYAML, lang string) string { - if pulumiYAML == "" { - return pulumiYAML - } // Replace the project name from the default `/` to a more descriptive name nameRegex := regexp.MustCompile(`name: /*`) pulumiYAMLFile := nameRegex.ReplaceAllString(pulumiYAML, "name: configuration-example") @@ -259,10 +256,19 @@ func convertExample(g *Generator, code string, exampleNumber int) (string, error return "", err } - if pclExample.PCL == "" { + // If both PCL and PulumiYAML fields are empty, we can return. + if pclExample.PulumiYAML == "" && pclExample.PCL == "" { return "", nil } + // If we have a valid provider config but no additional code, we only render a YAML configuration block + // with no choosers and an empty language runtime field + if pclExample.PulumiYAML != "" && pclExample.PCL == "" { + if pclExample.PCL == "" { + return processConfigYaml(pclExample.PulumiYAML, ""), nil + } + } + langs := genLanguageToSlice(g.language) const ( chooserStart = `{{< chooser language "typescript,python,go,csharp,java,yaml" >}}` + "\n" @@ -277,8 +283,11 @@ func convertExample(g *Generator, code string, exampleNumber int) (string, error choosableStart := fmt.Sprintf("{{%% choosable language %s %%}}\n", lang) // Generate the Pulumi.yaml config file for each language - configFile := pclExample.PulumiYAML - pulumiYAML := processConfigYaml(configFile, lang) + var pulumiYAML string + if pclExample.PulumiYAML != "" { + pulumiYAML = processConfigYaml(pclExample.PulumiYAML, lang) + } + // Generate language example convertedLang, err := converter.singleExampleFromPCLToLanguage(pclExample, lang) if err != nil { diff --git a/pkg/tfgen/installation_docs_test.go b/pkg/tfgen/installation_docs_test.go index bb04ab15d..d39a0eeaf 100644 --- a/pkg/tfgen/installation_docs_test.go +++ b/pkg/tfgen/installation_docs_test.go @@ -476,6 +476,32 @@ func TestTranslateCodeBlocks(t *testing.T) { language: RegistryDocs, }, }, + { + name: "Translates standalone provider config into Pulumi config YAML", + contentStr: readfile(t, "test_data/installation-docs/provider-config-only.md"), + expected: readfile(t, "test_data/installation-docs/provider-config-only-expected.md"), + g: &Generator{ + sink: mockSink{}, + cliConverterState: &cliConverter{ + info: p, + pcls: pclsMap, + }, + language: RegistryDocs, + }, + }, + { + name: "Translates standalone example into languages", + contentStr: readfile(t, "test_data/installation-docs/example-only.md"), + expected: readfile(t, "test_data/installation-docs/example-only-expected.md"), + g: &Generator{ + sink: mockSink{}, + cliConverterState: &cliConverter{ + info: p, + pcls: pclsMap, + }, + language: RegistryDocs, + }, + }, } for _, tt := range testCases { diff --git a/pkg/tfgen/test_data/installation-docs/example-only-expected.md b/pkg/tfgen/test_data/installation-docs/example-only-expected.md new file mode 100644 index 000000000..0247fc3b3 --- /dev/null +++ b/pkg/tfgen/test_data/installation-docs/example-only-expected.md @@ -0,0 +1,132 @@ +This example is invalid and should not be translated for this test to pass + +## Example Usage + +{{< chooser language "typescript,python,go,csharp,java,yaml" >}} +{{% choosable language typescript %}} +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as simple from "@pulumi/simple"; + +//# Define a resource +const aResource = new simple.index.Resource("a_resource", { + renamedInput1: "hello", + inputTwo: true, +}); +export const someOutput = aResource.result; +``` +{{% /choosable %}} +{{% choosable language python %}} +```python +import pulumi +import pulumi_simple as simple + +## Define a resource +a_resource = simple.index.Resource("a_resource", + renamed_input1=hello, + input_two=True) +pulumi.export("someOutput", a_resource["result"]) +``` +{{% /choosable %}} +{{% choosable language csharp %}} +```csharp +using System.Collections.Generic; +using System.Linq; +using Pulumi; +using Simple = Pulumi.Simple; + +return await Deployment.RunAsync(() => +{ + //# Define a resource + var aResource = new Simple.Index.Resource("a_resource", new() + { + RenamedInput1 = "hello", + InputTwo = true, + }); + + return new Dictionary + { + ["someOutput"] = aResource.Result, + }; +}); + +``` +{{% /choosable %}} +{{% choosable language go %}} +```go +package main + +import ( + "github.com/pulumi/pulumi-simple/sdk/go/simple" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + // # Define a resource + aResource, err := simple.NewResource(ctx, "a_resource", &simple.ResourceArgs{ + RenamedInput1: "hello", + InputTwo: true, + }) + if err != nil { + return err + } + ctx.Export("someOutput", aResource.Result) + return nil + }) +} +``` +{{% /choosable %}} +{{% choosable language yaml %}} +```yaml +resources: + ## Define a resource + aResource: + type: simple:resource + name: a_resource + properties: + renamedInput1: hello + inputTwo: true +outputs: + someOutput: ${aResource.result} +``` +{{% /choosable %}} +{{% choosable language java %}} +```java +package generated_program; + +import com.pulumi.Context; +import com.pulumi.Pulumi; +import com.pulumi.core.Output; +import com.pulumi.simple.resource; +import com.pulumi.simple.ResourceArgs; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class App { + public static void main(String[] args) { + Pulumi.run(App::stack); + } + + public static void stack(Context ctx) { + //# Define a resource + var aResource = new Resource("aResource", ResourceArgs.builder() + .renamedInput1("hello") + .inputTwo(true) + .build()); + + ctx.export("someOutput", aResource.result()); + } +} +``` +{{% /choosable %}} +{{< /chooser >}} + + +## Configuration Reference + +The following configuration inputs are supported: diff --git a/pkg/tfgen/test_data/installation-docs/example-only.md b/pkg/tfgen/test_data/installation-docs/example-only.md new file mode 100644 index 000000000..9b4cfd62d --- /dev/null +++ b/pkg/tfgen/test_data/installation-docs/example-only.md @@ -0,0 +1,19 @@ +This example is invalid and should not be translated for this test to pass + +## Example Usage + +```hcl +## Define a resource +resource "simple_resource" "a_resource" { + input_one = "hello" + input_two = true +} + +output "some_output" { + value = simple_resource.a_resource.result +} +``` + +## Configuration Reference + +The following configuration inputs are supported: diff --git a/pkg/tfgen/test_data/installation-docs/invalid-example.md b/pkg/tfgen/test_data/installation-docs/invalid-example.md index 9b4704c21..bdec24a2d 100644 --- a/pkg/tfgen/test_data/installation-docs/invalid-example.md +++ b/pkg/tfgen/test_data/installation-docs/invalid-example.md @@ -4,7 +4,7 @@ This example is invalid and should not be translated for this test to pass ```hcl # Configure the OpenStack Provider -proider "simple-provider" { +misspelledprovider "simple-provider" { user_name = "admin" tenant_name = "admin" password = "pwd" diff --git a/pkg/tfgen/test_data/installation-docs/provider-config-only-expected.md b/pkg/tfgen/test_data/installation-docs/provider-config-only-expected.md new file mode 100644 index 000000000..2c3ad6751 --- /dev/null +++ b/pkg/tfgen/test_data/installation-docs/provider-config-only-expected.md @@ -0,0 +1,26 @@ +This example should translate at least the Pulumi config + +## Example Usage + +```yaml +# Pulumi.yaml provider configuration file +name: configuration-example +runtime: +config: + simple-provider:authUrl: + value: http://myauthurl:5000/v3 + simple-provider:password: + value: pwd + simple-provider:region: + value: RegionOne + simple-provider:tenantName: + value: admin + simple-provider:userName: + value: admin + +``` + + +## Configuration Reference + +The following configuration inputs are supported: diff --git a/pkg/tfgen/test_data/installation-docs/provider-config-only.md b/pkg/tfgen/test_data/installation-docs/provider-config-only.md new file mode 100644 index 000000000..a5c621f8c --- /dev/null +++ b/pkg/tfgen/test_data/installation-docs/provider-config-only.md @@ -0,0 +1,18 @@ +This example should translate at least the Pulumi config + +## Example Usage + +```hcl +# Configure the OpenStack Provider +provider "simple-provider" { + user_name = "admin" + tenant_name = "admin" + password = "pwd" + auth_url = "http://myauthurl:5000/v3" + region = "RegionOne" +} +``` + +## Configuration Reference + +The following configuration inputs are supported: From eb4af281963ce49a3e0a97e08a0bd5041e9241f5 Mon Sep 17 00:00:00 2001 From: guineveresaenger Date: Fri, 10 Jan 2025 09:57:53 -0800 Subject: [PATCH 6/6] Address review comments --- pkg/tfgen/installation_docs.go | 28 ++++++------- pkg/tfgen/installation_docs_test.go | 42 +++++++++---------- .../example-only-expected.md | 2 +- .../installation-docs/example-only.md | 2 +- 4 files changed, 35 insertions(+), 39 deletions(-) diff --git a/pkg/tfgen/installation_docs.go b/pkg/tfgen/installation_docs.go index 18d547bb0..0fe40067a 100644 --- a/pkg/tfgen/installation_docs.go +++ b/pkg/tfgen/installation_docs.go @@ -67,14 +67,14 @@ func plainDocsParser(docFile *DocFile, g *Generator) ([]byte, error) { } // If the code translation resulted in an empty examples section, remove it - contentStr, err = removeEmptyExamples(contentStr) + content, err = removeEmptySection("Example Usage", []byte(contentStr)) if err != nil { return nil, err } // Apply post-code translation edit rules. This applies all default edit rules and provider-supplied edit rules in // the post-code translation phase. - contentBytes, err = g.editRules.apply(docFile.FileName, []byte(contentStr), info.PostCodeTranslation) + contentBytes, err = g.editRules.apply(docFile.FileName, content, info.PostCodeTranslation) if err != nil { return nil, err } @@ -504,31 +504,27 @@ func getProviderDisplayName(g *Generator) string { return capitalize.String(providerName) } -func removeEmptyExamples(contentStr string) (string, error) { - if checkExamplesEmpty(contentStr) { - mybytes, err := SkipSectionByHeaderContent([]byte(contentStr), func(headerText string) bool { - return headerText == "Example Usage" - }) - contentStr = string(mybytes) - return contentStr, err +func removeEmptySection(title string, contentBytes []byte) ([]byte, error) { + if !isMarkdownSectionEmpty(title, contentBytes) { + return contentBytes, nil } - return contentStr, nil - + return SkipSectionByHeaderContent(contentBytes, func(headerText string) bool { + return headerText == title + }) } -func checkExamplesEmpty(contentStr string) bool { - +func isMarkdownSectionEmpty(title string, contentBytes []byte) bool { gm := goldmark.New(goldmark.WithExtensions(parse.TFRegistryExtension)) - astNode := gm.Parser().Parse(text.NewReader([]byte(contentStr))) + astNode := gm.Parser().Parse(text.NewReader(contentBytes)) isEmpty := false err := ast.Walk(astNode, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if section, ok := n.(*section.Section); ok && entering { - sectionText := section.Text([]byte(contentStr)) + sectionText := section.Text(contentBytes) // A little confusingly, we check if the section text _only_ contains the title, "Example Usage". // Non-empty sections contain the title + content, so if we see only the title, the section is empty. - if string(sectionText) == "Example Usage" { + if string(sectionText) == title { isEmpty = true return ast.WalkStop, nil } diff --git a/pkg/tfgen/installation_docs_test.go b/pkg/tfgen/installation_docs_test.go index d39a0eeaf..da658897f 100644 --- a/pkg/tfgen/installation_docs_test.go +++ b/pkg/tfgen/installation_docs_test.go @@ -645,25 +645,6 @@ func TestSkipDefaultSectionHeaders(t *testing.T) { } } -// Helper func to determine if the HTML rendering is equal. -// This helps in cases where the processed Markdown is slightly different from the expected Markdown -// due to goldmark making some (insignificant to the final HTML) changes when parsing and rendering. -// We convert the expected Markdown and the actual test Markdown output to HTML and verify if they are equal. -func assertEqualHTML(t *testing.T, expected, actual string) bool { - mdRenderer := goldmark.New() - var expectedBuf bytes.Buffer - err := mdRenderer.Convert([]byte(expected), &expectedBuf) - if err != nil { - panic(err) - } - var outputBuf bytes.Buffer - err = mdRenderer.Convert([]byte(actual), &outputBuf) - if err != nil { - panic(err) - } - return assert.Equal(t, expectedBuf.String(), outputBuf.String()) -} - func TestRemoveEmptyExamples(t *testing.T) { t.Parallel() type testCase struct { @@ -680,8 +661,27 @@ func TestRemoveEmptyExamples(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - actual, err := removeEmptyExamples(tc.input) + actual, err := removeEmptySection("Example Usage", []byte(tc.input)) require.NoError(t, err) - assertEqualHTML(t, tc.expected, actual) + assertEqualHTML(t, tc.expected, string(actual)) }) } + +// Helper func to determine if the HTML rendering is equal. +// This helps in cases where the processed Markdown is slightly different from the expected Markdown +// due to goldmark making some (insignificant to the final HTML) changes when parsing and rendering. +// We convert the expected Markdown and the actual test Markdown output to HTML and verify if they are equal. +func assertEqualHTML(t *testing.T, expected, actual string) bool { + mdRenderer := goldmark.New() + var expectedBuf bytes.Buffer + err := mdRenderer.Convert([]byte(expected), &expectedBuf) + if err != nil { + panic(err) + } + var outputBuf bytes.Buffer + err = mdRenderer.Convert([]byte(actual), &outputBuf) + if err != nil { + panic(err) + } + return assert.Equal(t, expectedBuf.String(), outputBuf.String()) +} diff --git a/pkg/tfgen/test_data/installation-docs/example-only-expected.md b/pkg/tfgen/test_data/installation-docs/example-only-expected.md index 0247fc3b3..d31c827a2 100644 --- a/pkg/tfgen/test_data/installation-docs/example-only-expected.md +++ b/pkg/tfgen/test_data/installation-docs/example-only-expected.md @@ -1,4 +1,4 @@ -This example is invalid and should not be translated for this test to pass +This example will only translate the resource code. It has no configuration file. ## Example Usage diff --git a/pkg/tfgen/test_data/installation-docs/example-only.md b/pkg/tfgen/test_data/installation-docs/example-only.md index 9b4cfd62d..e645676aa 100644 --- a/pkg/tfgen/test_data/installation-docs/example-only.md +++ b/pkg/tfgen/test_data/installation-docs/example-only.md @@ -1,4 +1,4 @@ -This example is invalid and should not be translated for this test to pass +This example will only translate the resource code. It has no configuration file. ## Example Usage