From 0ef59a9b8f08a347187e63a7698edf3273eb1474 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 8 Nov 2023 16:31:49 -0500 Subject: [PATCH 01/11] Add acceptance tests for `generate` command --- cmd/build/version.go | 18 ++++++++++++++++++ cmd/tfplugindocs/main_test.go | 3 --- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 cmd/build/version.go diff --git a/cmd/build/version.go b/cmd/build/version.go new file mode 100644 index 00000000..01ded843 --- /dev/null +++ b/cmd/build/version.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package build + +var ( + // These vars will be set by goreleaser. + version string = `dev` + commit string = `` +) + +func GetVersion() string { + version := "tfplugindocs" + " Version " + version + if commit != "" { + version += " from commit " + commit + } + return version +} diff --git a/cmd/tfplugindocs/main_test.go b/cmd/tfplugindocs/main_test.go index b8799966..7633f846 100644 --- a/cmd/tfplugindocs/main_test.go +++ b/cmd/tfplugindocs/main_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( From 3cd0fb5cb472ce8b236c6b4b81d66fc42a836e15 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 8 Nov 2023 17:34:15 -0500 Subject: [PATCH 02/11] Add copyright headers --- cmd/tfplugindocs/main_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/tfplugindocs/main_test.go b/cmd/tfplugindocs/main_test.go index 7633f846..b8799966 100644 --- a/cmd/tfplugindocs/main_test.go +++ b/cmd/tfplugindocs/main_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package main import ( From b4cb53d28b98614e09de2b56233b598f43e93d4e Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Fri, 10 Nov 2023 15:04:41 -0500 Subject: [PATCH 03/11] Move `build` package and update goreleaser `idflags` --- cmd/build/version.go | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 cmd/build/version.go diff --git a/cmd/build/version.go b/cmd/build/version.go deleted file mode 100644 index 01ded843..00000000 --- a/cmd/build/version.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package build - -var ( - // These vars will be set by goreleaser. - version string = `dev` - commit string = `` -) - -func GetVersion() string { - version := "tfplugindocs" + " Version " + version - if commit != "" { - version += " from commit " + commit - } - return version -} From d160911618e96921b9f683545a4e6003247b7e56 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 14 Nov 2023 13:59:14 -0500 Subject: [PATCH 04/11] Add context to errors --- internal/provider/generate.go | 150 ++++++++++++++-------------------- 1 file changed, 61 insertions(+), 89 deletions(-) diff --git a/internal/provider/generate.go b/internal/provider/generate.go index a303a57a..cd1e0c64 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -85,7 +85,6 @@ type generator struct { providerDir string providerName string - providersSchemaPath string renderedProviderName string renderedWebsiteDir string examplesDir string @@ -103,7 +102,7 @@ func (g *generator) warnf(format string, a ...interface{}) { g.ui.Warn(fmt.Sprintf(format, a...)) } -func Generate(ui cli.Ui, providerDir, providerName, providersSchemaPath, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, templatesDir, tfVersion string, ignoreDeprecated bool) error { +func Generate(ui cli.Ui, providerDir, providerName, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, templatesDir, tfVersion string, ignoreDeprecated bool) error { // Ensure provider directory is resolved absolute path if providerDir == "" { wd, err := os.Getwd() @@ -140,7 +139,6 @@ func Generate(ui cli.Ui, providerDir, providerName, providersSchemaPath, rendere providerDir: providerDir, providerName: providerName, - providersSchemaPath: providersSchemaPath, renderedProviderName: renderedProviderName, renderedWebsiteDir: renderedWebsiteDir, examplesDir: examplesDir, @@ -158,15 +156,16 @@ func Generate(ui cli.Ui, providerDir, providerName, providersSchemaPath, rendere func (g *generator) Generate(ctx context.Context) error { var err error + providerName := g.providerName if g.providerName == "" { - g.providerName = filepath.Base(g.providerDir) + providerName = filepath.Base(g.providerDir) } if g.renderedProviderName == "" { - g.renderedProviderName = g.providerName + g.renderedProviderName = providerName } - g.infof("rendering website for provider %q (as %q)", g.providerName, g.renderedProviderName) + g.infof("rendering website for provider %q (as %q)", providerName, g.renderedProviderName) switch { case g.websiteTmpDir == "": @@ -207,30 +206,20 @@ func (g *generator) Generate(ctx context.Context) error { } } - var providerSchema *tfjson.ProviderSchema - - if g.providersSchemaPath == "" { - g.infof("exporting schema from Terraform") - providerSchema, err = g.terraformProviderSchemaFromTerraform(ctx) - if err != nil { - return fmt.Errorf("error exporting provider schema from Terraform: %w", err) - } - } else { - g.infof("exporting schema from JSON file") - providerSchema, err = g.terraformProviderSchemaFromFile() - if err != nil { - return fmt.Errorf("error exporting provider schema from JSON file: %w", err) - } + g.infof("exporting schema from Terraform") + providerSchema, err := g.terraformProviderSchema(ctx, providerName) + if err != nil { + return fmt.Errorf("error exporting provider schema from Terraform: %w", err) } g.infof("rendering missing docs") - err = g.renderMissingDocs(providerSchema) + err = g.renderMissingDocs(providerName, providerSchema) if err != nil { return fmt.Errorf("error rendering missing docs: %w", err) } g.infof("rendering static website") - err = g.renderStaticWebsite(providerSchema) + err = g.renderStaticWebsite(providerName, providerSchema) if err != nil { return fmt.Errorf("error rendering static website: %w", err) } @@ -240,55 +229,55 @@ func (g *generator) Generate(ctx context.Context) error { // ProviderDocsDir returns the absolute path to the joined provider and // given website documentation directory, which defaults to "docs". -func (g *generator) ProviderDocsDir() string { +func (g generator) ProviderDocsDir() string { return filepath.Join(g.providerDir, g.renderedWebsiteDir) } // ProviderExamplesDir returns the absolute path to the joined provider and // given examples directory, which defaults to "examples". -func (g *generator) ProviderExamplesDir() string { +func (g generator) ProviderExamplesDir() string { return filepath.Join(g.providerDir, g.examplesDir) } // ProviderTemplatesDir returns the absolute path to the joined provider and // given templates directory, which defaults to "templates". -func (g *generator) ProviderTemplatesDir() string { +func (g generator) ProviderTemplatesDir() string { return filepath.Join(g.providerDir, g.templatesDir) } // TempTemplatesDir returns the absolute path to the joined temporary and -// hardcoded "templates" subdirectory, which is where provider templates are +// hardcoded "templates" sub-directory, which is where provider templates are // copied. -func (g *generator) TempTemplatesDir() string { +func (g generator) TempTemplatesDir() string { return filepath.Join(g.websiteTmpDir, "templates") } -func (g *generator) renderMissingResourceDoc(resourceName, typeName string, schema *tfjson.Schema, websiteFileTemplate resourceFileTemplate, fallbackWebsiteFileTemplate resourceFileTemplate, websiteStaticCandidateTemplates []resourceFileTemplate, examplesFileTemplate resourceFileTemplate, examplesImportTemplate *resourceFileTemplate) error { - tmplPath, err := websiteFileTemplate.Render(g.providerDir, resourceName, g.providerName) +func (g *generator) renderMissingResourceDoc(providerName, name, typeName string, schema *tfjson.Schema, websiteFileTemplate resourceFileTemplate, fallbackWebsiteFileTemplate resourceFileTemplate, websiteStaticCandidateTemplates []resourceFileTemplate, examplesFileTemplate resourceFileTemplate, examplesImportTemplate *resourceFileTemplate) error { + tmplPath, err := websiteFileTemplate.Render(g.providerDir, name, providerName) if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) + return fmt.Errorf("unable to render path for resource %q: %w", name, err) } tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) if fileExists(tmplPath) { - g.infof("resource %q template exists, skipping", resourceName) + g.infof("resource %q template exists, skipping", name) return nil } for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(g.providerDir, resourceName, g.providerName) + candidatePath, err := candidate.Render(g.providerDir, name, providerName) if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) + return fmt.Errorf("unable to render path for resource %q: %w", name, err) } candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) if fileExists(candidatePath) { - g.infof("resource %q static file exists, skipping", resourceName) + g.infof("resource %q static file exists, skipping", name) return nil } } - examplePath, err := examplesFileTemplate.Render(g.providerDir, resourceName, g.providerName) + examplePath, err := examplesFileTemplate.Render(g.providerDir, name, providerName) if err != nil { - return fmt.Errorf("unable to render example file path for %q: %w", resourceName, err) + return fmt.Errorf("unable to render example file path for %q: %w", name, err) } if examplePath != "" { examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) @@ -299,9 +288,9 @@ func (g *generator) renderMissingResourceDoc(resourceName, typeName string, sche importPath := "" if examplesImportTemplate != nil { - importPath, err = examplesImportTemplate.Render(g.providerDir, resourceName, g.providerName) + importPath, err = examplesImportTemplate.Render(g.providerDir, name, providerName) if err != nil { - return fmt.Errorf("unable to render example import file path for %q: %w", resourceName, err) + return fmt.Errorf("unable to render example import file path for %q: %w", name, err) } if importPath != "" { importPath = filepath.Join(g.ProviderExamplesDir(), importPath) @@ -313,13 +302,13 @@ func (g *generator) renderMissingResourceDoc(resourceName, typeName string, sche targetResourceTemplate := defaultResourceTemplate - fallbackTmplPath, err := fallbackWebsiteFileTemplate.Render(g.providerDir, resourceName, g.providerName) + fallbackTmplPath, err := fallbackWebsiteFileTemplate.Render(g.providerDir, name, providerName) if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) + return fmt.Errorf("unable to render path for resource %q: %w", name, err) } fallbackTmplPath = filepath.Join(g.TempTemplatesDir(), fallbackTmplPath) if fileExists(fallbackTmplPath) { - g.infof("resource %q fallback template exists", resourceName) + g.infof("resource %q fallback template exists", name) tmplData, err := os.ReadFile(fallbackTmplPath) if err != nil { return fmt.Errorf("unable to read file %q: %w", fallbackTmplPath, err) @@ -327,10 +316,10 @@ func (g *generator) renderMissingResourceDoc(resourceName, typeName string, sche targetResourceTemplate = resourceTemplate(tmplData) } - g.infof("generating template for %q", resourceName) - md, err := targetResourceTemplate.Render(g.providerDir, resourceName, g.providerName, g.renderedProviderName, typeName, examplePath, importPath, schema) + g.infof("generating template for %q", name) + md, err := targetResourceTemplate.Render(g.providerDir, name, providerName, g.renderedProviderName, typeName, examplePath, importPath, schema) if err != nil { - return fmt.Errorf("unable to render template for %q: %w", resourceName, err) + return fmt.Errorf("unable to render template for %q: %w", name, err) } err = writeFile(tmplPath, md) @@ -341,32 +330,32 @@ func (g *generator) renderMissingResourceDoc(resourceName, typeName string, sche return nil } -func (g *generator) renderMissingProviderDoc(schema *tfjson.Schema, websiteFileTemplate providerFileTemplate, websiteStaticCandidateTemplates []providerFileTemplate, examplesFileTemplate providerFileTemplate) error { - tmplPath, err := websiteFileTemplate.Render(g.providerDir, g.providerName) +func (g *generator) renderMissingProviderDoc(providerName string, schema *tfjson.Schema, websiteFileTemplate providerFileTemplate, websiteStaticCandidateTemplates []providerFileTemplate, examplesFileTemplate providerFileTemplate) error { + tmplPath, err := websiteFileTemplate.Render(g.providerDir, providerName) if err != nil { - return fmt.Errorf("unable to render path for provider %q: %w", g.providerName, err) + return fmt.Errorf("unable to render path for provider %q: %w", providerName, err) } tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) if fileExists(tmplPath) { - g.infof("provider %q template exists, skipping", g.providerName) + g.infof("provider %q template exists, skipping", providerName) return nil } for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(g.providerDir, g.providerName) + candidatePath, err := candidate.Render(g.providerDir, providerName) if err != nil { - return fmt.Errorf("unable to render path for provider %q: %w", g.providerName, err) + return fmt.Errorf("unable to render path for provider %q: %w", providerName, err) } candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) if fileExists(candidatePath) { - g.infof("provider %q static file exists, skipping", g.providerName) + g.infof("provider %q static file exists, skipping", providerName) return nil } } - examplePath, err := examplesFileTemplate.Render(g.providerDir, g.providerName) + examplePath, err := examplesFileTemplate.Render(g.providerDir, providerName) if err != nil { - return fmt.Errorf("unable to render example file path for %q: %w", g.providerName, err) + return fmt.Errorf("unable to render example file path for %q: %w", providerName, err) } if examplePath != "" { examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) @@ -375,10 +364,10 @@ func (g *generator) renderMissingProviderDoc(schema *tfjson.Schema, websiteFileT examplePath = "" } - g.infof("generating template for %q", g.providerName) - md, err := defaultProviderTemplate.Render(g.providerDir, g.providerName, g.renderedProviderName, examplePath, schema) + g.infof("generating template for %q", providerName) + md, err := defaultProviderTemplate.Render(g.providerDir, providerName, g.renderedProviderName, examplePath, schema) if err != nil { - return fmt.Errorf("unable to render template for %q: %w", g.providerName, err) + return fmt.Errorf("unable to render template for %q: %w", providerName, err) } err = writeFile(tmplPath, md) @@ -389,14 +378,14 @@ func (g *generator) renderMissingProviderDoc(schema *tfjson.Schema, websiteFileT return nil } -func (g *generator) renderMissingDocs(providerSchema *tfjson.ProviderSchema) error { +func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjson.ProviderSchema) error { g.infof("generating missing resource content") for name, schema := range providerSchema.ResourceSchemas { if g.ignoreDeprecated && schema.Block.Deprecated { continue } - err := g.renderMissingResourceDoc(name, "Resource", schema, + err := g.renderMissingResourceDoc(providerName, name, "Resource", schema, websiteResourceFileTemplate, websiteResourceFallbackFileTemplate, websiteResourceFileStatic, @@ -413,7 +402,7 @@ func (g *generator) renderMissingDocs(providerSchema *tfjson.ProviderSchema) err continue } - err := g.renderMissingResourceDoc(name, "Data Source", schema, + err := g.renderMissingResourceDoc(providerName, name, "Data Source", schema, websiteDataSourceFileTemplate, websiteDataSourceFallbackFileTemplate, websiteDataSourceFileStatic, @@ -425,7 +414,7 @@ func (g *generator) renderMissingDocs(providerSchema *tfjson.ProviderSchema) err } g.infof("generating missing provider content") - err := g.renderMissingProviderDoc(providerSchema.ConfigSchema, + err := g.renderMissingProviderDoc(providerName, providerSchema.ConfigSchema, websiteProviderFileTemplate, websiteProviderFileStatic, examplesProviderFileTemplate, @@ -437,7 +426,7 @@ func (g *generator) renderMissingDocs(providerSchema *tfjson.ProviderSchema) err return nil } -func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) error { +func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfjson.ProviderSchema) error { g.infof("cleaning rendered website dir") dirEntry, err := os.ReadDir(g.ProviderDocsDir()) if err != nil && !os.IsNotExist(err) { @@ -467,7 +456,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e } } - shortName := providerShortName(g.providerName) + shortName := providerShortName(providerName) g.infof("rendering templated website to static markdown") @@ -524,7 +513,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", resSchema) + render, err := tmpl.Render(g.providerDir, resName, providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", resSchema) if err != nil { return fmt.Errorf("unable to render data source template %q: %w", rel, err) } @@ -542,7 +531,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Resource", exampleFilePath, importFilePath, resSchema) + render, err := tmpl.Render(g.providerDir, resName, providerName, g.renderedProviderName, "Resource", exampleFilePath, importFilePath, resSchema) if err != nil { return fmt.Errorf("unable to render resource template %q: %w", rel, err) } @@ -557,7 +546,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e if relFile == "index.md.tmpl" { tmpl := providerTemplate(tmplData) exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "provider", "provider.tf") - render, err := tmpl.Render(g.providerDir, g.providerName, g.renderedProviderName, exampleFilePath, providerSchema.ConfigSchema) + render, err := tmpl.Render(g.providerDir, providerName, g.renderedProviderName, exampleFilePath, providerSchema.ConfigSchema) if err != nil { return fmt.Errorf("unable to render provider template %q: %w", rel, err) } @@ -583,10 +572,10 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e return nil } -func (g *generator) terraformProviderSchemaFromTerraform(ctx context.Context) (*tfjson.ProviderSchema, error) { +func (g *generator) terraformProviderSchema(ctx context.Context, providerName string) (*tfjson.ProviderSchema, error) { var err error - shortName := providerShortName(g.providerName) + shortName := providerShortName(providerName) tmpDir, err := os.MkdirTemp("", "tfws") if err != nil { @@ -594,6 +583,11 @@ func (g *generator) terraformProviderSchemaFromTerraform(ctx context.Context) (* } defer os.RemoveAll(tmpDir) + // tmpDir := "/tmp/tftmp" + // os.RemoveAll(tmpDir) + // os.MkdirAll(tmpDir, 0755) + // fmt.Printf("[DEBUG] tmpdir %q\n", tmpDir) + g.infof("compiling provider %q", shortName) providerPath := fmt.Sprintf("plugins/registry.terraform.io/hashicorp/%s/0.0.1/%s_%s", shortName, runtime.GOOS, runtime.GOARCH) outFile := filepath.Join(tmpDir, providerPath, fmt.Sprintf("terraform-provider-%s", shortName)) @@ -673,25 +667,3 @@ provider %[1]q { return nil, fmt.Errorf("unable to find schema in JSON for provider %q", shortName) } - -func (g *generator) terraformProviderSchemaFromFile() (*tfjson.ProviderSchema, error) { - var err error - - shortName := providerShortName(g.providerName) - - g.infof("getting provider schema") - schemas, err := extractSchemaFromFile(g.providersSchemaPath) - if err != nil { - return nil, fmt.Errorf("unable to retrieve provider schema from JSON file: %w", err) - } - - if ps, ok := schemas.Schemas[shortName]; ok { - return ps, nil - } - - if ps, ok := schemas.Schemas["registry.terraform.io/hashicorp/"+shortName]; ok { - return ps, nil - } - - return nil, fmt.Errorf("unable to find schema in JSON for provider %q", shortName) -} From 60b33c067750e9fb2cc07df500b75e5917c855ee Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 15 Nov 2023 17:28:20 -0500 Subject: [PATCH 05/11] Refactor to use `g.providerName` instead of passing the provider name as a parameter. --- internal/provider/generate.go | 108 ++++++++++++++++------------------ 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/internal/provider/generate.go b/internal/provider/generate.go index cd1e0c64..f376d9f6 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -156,16 +156,15 @@ func Generate(ui cli.Ui, providerDir, providerName, renderedProviderName, render func (g *generator) Generate(ctx context.Context) error { var err error - providerName := g.providerName if g.providerName == "" { - providerName = filepath.Base(g.providerDir) + g.providerName = filepath.Base(g.providerDir) } if g.renderedProviderName == "" { - g.renderedProviderName = providerName + g.renderedProviderName = g.providerName } - g.infof("rendering website for provider %q (as %q)", providerName, g.renderedProviderName) + g.infof("rendering website for provider %q (as %q)", g.providerName, g.renderedProviderName) switch { case g.websiteTmpDir == "": @@ -207,19 +206,19 @@ func (g *generator) Generate(ctx context.Context) error { } g.infof("exporting schema from Terraform") - providerSchema, err := g.terraformProviderSchema(ctx, providerName) + providerSchema, err := g.terraformProviderSchema(ctx) if err != nil { return fmt.Errorf("error exporting provider schema from Terraform: %w", err) } g.infof("rendering missing docs") - err = g.renderMissingDocs(providerName, providerSchema) + err = g.renderMissingDocs(providerSchema) if err != nil { return fmt.Errorf("error rendering missing docs: %w", err) } g.infof("rendering static website") - err = g.renderStaticWebsite(providerName, providerSchema) + err = g.renderStaticWebsite(providerSchema) if err != nil { return fmt.Errorf("error rendering static website: %w", err) } @@ -229,55 +228,55 @@ func (g *generator) Generate(ctx context.Context) error { // ProviderDocsDir returns the absolute path to the joined provider and // given website documentation directory, which defaults to "docs". -func (g generator) ProviderDocsDir() string { +func (g *generator) ProviderDocsDir() string { return filepath.Join(g.providerDir, g.renderedWebsiteDir) } // ProviderExamplesDir returns the absolute path to the joined provider and // given examples directory, which defaults to "examples". -func (g generator) ProviderExamplesDir() string { +func (g *generator) ProviderExamplesDir() string { return filepath.Join(g.providerDir, g.examplesDir) } // ProviderTemplatesDir returns the absolute path to the joined provider and // given templates directory, which defaults to "templates". -func (g generator) ProviderTemplatesDir() string { +func (g *generator) ProviderTemplatesDir() string { return filepath.Join(g.providerDir, g.templatesDir) } // TempTemplatesDir returns the absolute path to the joined temporary and -// hardcoded "templates" sub-directory, which is where provider templates are +// hardcoded "templates" subdirectory, which is where provider templates are // copied. -func (g generator) TempTemplatesDir() string { +func (g *generator) TempTemplatesDir() string { return filepath.Join(g.websiteTmpDir, "templates") } -func (g *generator) renderMissingResourceDoc(providerName, name, typeName string, schema *tfjson.Schema, websiteFileTemplate resourceFileTemplate, fallbackWebsiteFileTemplate resourceFileTemplate, websiteStaticCandidateTemplates []resourceFileTemplate, examplesFileTemplate resourceFileTemplate, examplesImportTemplate *resourceFileTemplate) error { - tmplPath, err := websiteFileTemplate.Render(g.providerDir, name, providerName) +func (g *generator) renderMissingResourceDoc(resourceName, typeName string, schema *tfjson.Schema, websiteFileTemplate resourceFileTemplate, fallbackWebsiteFileTemplate resourceFileTemplate, websiteStaticCandidateTemplates []resourceFileTemplate, examplesFileTemplate resourceFileTemplate, examplesImportTemplate *resourceFileTemplate) error { + tmplPath, err := websiteFileTemplate.Render(g.providerDir, resourceName, g.providerName) if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", name, err) + return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) } tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) if fileExists(tmplPath) { - g.infof("resource %q template exists, skipping", name) + g.infof("resource %q template exists, skipping", resourceName) return nil } for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(g.providerDir, name, providerName) + candidatePath, err := candidate.Render(g.providerDir, resourceName, g.providerName) if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", name, err) + return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) } candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) if fileExists(candidatePath) { - g.infof("resource %q static file exists, skipping", name) + g.infof("resource %q static file exists, skipping", resourceName) return nil } } - examplePath, err := examplesFileTemplate.Render(g.providerDir, name, providerName) + examplePath, err := examplesFileTemplate.Render(g.providerDir, resourceName, g.providerName) if err != nil { - return fmt.Errorf("unable to render example file path for %q: %w", name, err) + return fmt.Errorf("unable to render example file path for %q: %w", resourceName, err) } if examplePath != "" { examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) @@ -288,9 +287,9 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string importPath := "" if examplesImportTemplate != nil { - importPath, err = examplesImportTemplate.Render(g.providerDir, name, providerName) + importPath, err = examplesImportTemplate.Render(g.providerDir, resourceName, g.providerName) if err != nil { - return fmt.Errorf("unable to render example import file path for %q: %w", name, err) + return fmt.Errorf("unable to render example import file path for %q: %w", resourceName, err) } if importPath != "" { importPath = filepath.Join(g.ProviderExamplesDir(), importPath) @@ -302,13 +301,13 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string targetResourceTemplate := defaultResourceTemplate - fallbackTmplPath, err := fallbackWebsiteFileTemplate.Render(g.providerDir, name, providerName) + fallbackTmplPath, err := fallbackWebsiteFileTemplate.Render(g.providerDir, resourceName, g.providerName) if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", name, err) + return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) } fallbackTmplPath = filepath.Join(g.TempTemplatesDir(), fallbackTmplPath) if fileExists(fallbackTmplPath) { - g.infof("resource %q fallback template exists", name) + g.infof("resource %q fallback template exists", resourceName) tmplData, err := os.ReadFile(fallbackTmplPath) if err != nil { return fmt.Errorf("unable to read file %q: %w", fallbackTmplPath, err) @@ -316,10 +315,10 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string targetResourceTemplate = resourceTemplate(tmplData) } - g.infof("generating template for %q", name) - md, err := targetResourceTemplate.Render(g.providerDir, name, providerName, g.renderedProviderName, typeName, examplePath, importPath, schema) + g.infof("generating template for %q", resourceName) + md, err := targetResourceTemplate.Render(g.providerDir, resourceName, g.providerName, g.renderedProviderName, typeName, examplePath, importPath, schema) if err != nil { - return fmt.Errorf("unable to render template for %q: %w", name, err) + return fmt.Errorf("unable to render template for %q: %w", resourceName, err) } err = writeFile(tmplPath, md) @@ -330,32 +329,32 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string return nil } -func (g *generator) renderMissingProviderDoc(providerName string, schema *tfjson.Schema, websiteFileTemplate providerFileTemplate, websiteStaticCandidateTemplates []providerFileTemplate, examplesFileTemplate providerFileTemplate) error { - tmplPath, err := websiteFileTemplate.Render(g.providerDir, providerName) +func (g *generator) renderMissingProviderDoc(schema *tfjson.Schema, websiteFileTemplate providerFileTemplate, websiteStaticCandidateTemplates []providerFileTemplate, examplesFileTemplate providerFileTemplate) error { + tmplPath, err := websiteFileTemplate.Render(g.providerDir, g.providerName) if err != nil { - return fmt.Errorf("unable to render path for provider %q: %w", providerName, err) + return fmt.Errorf("unable to render path for provider %q: %w", g.providerName, err) } tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) if fileExists(tmplPath) { - g.infof("provider %q template exists, skipping", providerName) + g.infof("provider %q template exists, skipping", g.providerName) return nil } for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(g.providerDir, providerName) + candidatePath, err := candidate.Render(g.providerDir, g.providerName) if err != nil { - return fmt.Errorf("unable to render path for provider %q: %w", providerName, err) + return fmt.Errorf("unable to render path for provider %q: %w", g.providerName, err) } candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) if fileExists(candidatePath) { - g.infof("provider %q static file exists, skipping", providerName) + g.infof("provider %q static file exists, skipping", g.providerName) return nil } } - examplePath, err := examplesFileTemplate.Render(g.providerDir, providerName) + examplePath, err := examplesFileTemplate.Render(g.providerDir, g.providerName) if err != nil { - return fmt.Errorf("unable to render example file path for %q: %w", providerName, err) + return fmt.Errorf("unable to render example file path for %q: %w", g.providerName, err) } if examplePath != "" { examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) @@ -364,10 +363,10 @@ func (g *generator) renderMissingProviderDoc(providerName string, schema *tfjson examplePath = "" } - g.infof("generating template for %q", providerName) - md, err := defaultProviderTemplate.Render(g.providerDir, providerName, g.renderedProviderName, examplePath, schema) + g.infof("generating template for %q", g.providerName) + md, err := defaultProviderTemplate.Render(g.providerDir, g.providerName, g.renderedProviderName, examplePath, schema) if err != nil { - return fmt.Errorf("unable to render template for %q: %w", providerName, err) + return fmt.Errorf("unable to render template for %q: %w", g.providerName, err) } err = writeFile(tmplPath, md) @@ -378,14 +377,14 @@ func (g *generator) renderMissingProviderDoc(providerName string, schema *tfjson return nil } -func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjson.ProviderSchema) error { +func (g *generator) renderMissingDocs(providerSchema *tfjson.ProviderSchema) error { g.infof("generating missing resource content") for name, schema := range providerSchema.ResourceSchemas { if g.ignoreDeprecated && schema.Block.Deprecated { continue } - err := g.renderMissingResourceDoc(providerName, name, "Resource", schema, + err := g.renderMissingResourceDoc(name, "Resource", schema, websiteResourceFileTemplate, websiteResourceFallbackFileTemplate, websiteResourceFileStatic, @@ -402,7 +401,7 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso continue } - err := g.renderMissingResourceDoc(providerName, name, "Data Source", schema, + err := g.renderMissingResourceDoc(name, "Data Source", schema, websiteDataSourceFileTemplate, websiteDataSourceFallbackFileTemplate, websiteDataSourceFileStatic, @@ -414,7 +413,7 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso } g.infof("generating missing provider content") - err := g.renderMissingProviderDoc(providerName, providerSchema.ConfigSchema, + err := g.renderMissingProviderDoc(providerSchema.ConfigSchema, websiteProviderFileTemplate, websiteProviderFileStatic, examplesProviderFileTemplate, @@ -426,7 +425,7 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso return nil } -func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfjson.ProviderSchema) error { +func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) error { g.infof("cleaning rendered website dir") dirEntry, err := os.ReadDir(g.ProviderDocsDir()) if err != nil && !os.IsNotExist(err) { @@ -456,7 +455,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj } } - shortName := providerShortName(providerName) + shortName := providerShortName(g.providerName) g.infof("rendering templated website to static markdown") @@ -513,7 +512,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", resSchema) + render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", resSchema) if err != nil { return fmt.Errorf("unable to render data source template %q: %w", rel, err) } @@ -531,7 +530,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, providerName, g.renderedProviderName, "Resource", exampleFilePath, importFilePath, resSchema) + render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Resource", exampleFilePath, importFilePath, resSchema) if err != nil { return fmt.Errorf("unable to render resource template %q: %w", rel, err) } @@ -546,7 +545,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj if relFile == "index.md.tmpl" { tmpl := providerTemplate(tmplData) exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "provider", "provider.tf") - render, err := tmpl.Render(g.providerDir, providerName, g.renderedProviderName, exampleFilePath, providerSchema.ConfigSchema) + render, err := tmpl.Render(g.providerDir, g.providerName, g.renderedProviderName, exampleFilePath, providerSchema.ConfigSchema) if err != nil { return fmt.Errorf("unable to render provider template %q: %w", rel, err) } @@ -572,10 +571,10 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj return nil } -func (g *generator) terraformProviderSchema(ctx context.Context, providerName string) (*tfjson.ProviderSchema, error) { +func (g *generator) terraformProviderSchema(ctx context.Context) (*tfjson.ProviderSchema, error) { var err error - shortName := providerShortName(providerName) + shortName := providerShortName(g.providerName) tmpDir, err := os.MkdirTemp("", "tfws") if err != nil { @@ -583,11 +582,6 @@ func (g *generator) terraformProviderSchema(ctx context.Context, providerName st } defer os.RemoveAll(tmpDir) - // tmpDir := "/tmp/tftmp" - // os.RemoveAll(tmpDir) - // os.MkdirAll(tmpDir, 0755) - // fmt.Printf("[DEBUG] tmpdir %q\n", tmpDir) - g.infof("compiling provider %q", shortName) providerPath := fmt.Sprintf("plugins/registry.terraform.io/hashicorp/%s/0.0.1/%s_%s", shortName, runtime.GOOS, runtime.GOARCH) outFile := filepath.Join(tmpDir, providerPath, fmt.Sprintf("terraform-provider-%s", shortName)) From a4b08214e0f0c9d7eaae153d5817da088852e06f Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Thu, 16 Nov 2023 18:41:33 -0500 Subject: [PATCH 06/11] Add support for `--providers-schema` flag to pass in a providers schema JSON file. --- internal/provider/generate.go | 46 ++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/internal/provider/generate.go b/internal/provider/generate.go index f376d9f6..a303a57a 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -85,6 +85,7 @@ type generator struct { providerDir string providerName string + providersSchemaPath string renderedProviderName string renderedWebsiteDir string examplesDir string @@ -102,7 +103,7 @@ func (g *generator) warnf(format string, a ...interface{}) { g.ui.Warn(fmt.Sprintf(format, a...)) } -func Generate(ui cli.Ui, providerDir, providerName, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, templatesDir, tfVersion string, ignoreDeprecated bool) error { +func Generate(ui cli.Ui, providerDir, providerName, providersSchemaPath, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, templatesDir, tfVersion string, ignoreDeprecated bool) error { // Ensure provider directory is resolved absolute path if providerDir == "" { wd, err := os.Getwd() @@ -139,6 +140,7 @@ func Generate(ui cli.Ui, providerDir, providerName, renderedProviderName, render providerDir: providerDir, providerName: providerName, + providersSchemaPath: providersSchemaPath, renderedProviderName: renderedProviderName, renderedWebsiteDir: renderedWebsiteDir, examplesDir: examplesDir, @@ -205,10 +207,20 @@ func (g *generator) Generate(ctx context.Context) error { } } - g.infof("exporting schema from Terraform") - providerSchema, err := g.terraformProviderSchema(ctx) - if err != nil { - return fmt.Errorf("error exporting provider schema from Terraform: %w", err) + var providerSchema *tfjson.ProviderSchema + + if g.providersSchemaPath == "" { + g.infof("exporting schema from Terraform") + providerSchema, err = g.terraformProviderSchemaFromTerraform(ctx) + if err != nil { + return fmt.Errorf("error exporting provider schema from Terraform: %w", err) + } + } else { + g.infof("exporting schema from JSON file") + providerSchema, err = g.terraformProviderSchemaFromFile() + if err != nil { + return fmt.Errorf("error exporting provider schema from JSON file: %w", err) + } } g.infof("rendering missing docs") @@ -571,7 +583,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e return nil } -func (g *generator) terraformProviderSchema(ctx context.Context) (*tfjson.ProviderSchema, error) { +func (g *generator) terraformProviderSchemaFromTerraform(ctx context.Context) (*tfjson.ProviderSchema, error) { var err error shortName := providerShortName(g.providerName) @@ -661,3 +673,25 @@ provider %[1]q { return nil, fmt.Errorf("unable to find schema in JSON for provider %q", shortName) } + +func (g *generator) terraformProviderSchemaFromFile() (*tfjson.ProviderSchema, error) { + var err error + + shortName := providerShortName(g.providerName) + + g.infof("getting provider schema") + schemas, err := extractSchemaFromFile(g.providersSchemaPath) + if err != nil { + return nil, fmt.Errorf("unable to retrieve provider schema from JSON file: %w", err) + } + + if ps, ok := schemas.Schemas[shortName]; ok { + return ps, nil + } + + if ps, ok := schemas.Schemas["registry.terraform.io/hashicorp/"+shortName]; ok { + return ps, nil + } + + return nil, fmt.Errorf("unable to find schema in JSON for provider %q", shortName) +} From 8de2d4eb8619d378db6d5f1b15cbd8141574ebec Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 20 Nov 2023 16:06:44 -0500 Subject: [PATCH 07/11] Add legacy docs and static files schema-json acceptance tests --- ...amework_provider_success_legacy_docs.txtar | 210 ++++++++++++++++++ ...mework_provider_success_static_files.txtar | 210 ++++++++++++++++++ 2 files changed, 420 insertions(+) create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar new file mode 100644 index 00000000..663ea564 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar @@ -0,0 +1,210 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a Framework provider with docs in the legacy directory structure (i.e. r/.md.tmpl) +[!unix] skip +exec tfplugindocs --provider-name=terraform-provider-scaffolding --providers-schema=schema.json +cmp stdout expected-output.txt + +# Check that static files copied successfully to rendered docs directory +cmp templates/r/example.md docs/r/example.md +cmp templates/r/example.markdown docs/r/example.markdown +cmp templates/r/example.html.markdown docs/r/example.html.markdown +cmp templates/r/example.html.md docs/r/example.html.md + +cmp templates/d/example.md docs/d/example.md +cmp templates/d/example.markdown docs/d/example.markdown +cmp templates/d/example.html.markdown docs/d/example.html.markdown +cmp templates/d/example.html.md docs/d/example.html.md + +cmp templates/index.md docs/index.md +cmp templates/index.markdown docs/index.markdown +cmp templates/index.html.markdown docs/index.html.markdown +cmp templates/index.html.md docs/index.html.md +-- expected-output.txt -- +rendering website for provider "terraform-provider-scaffolding" (as "terraform-provider-scaffolding") +copying any existing content to tmp dir +exporting schema from JSON file +getting provider schema +rendering missing docs +generating missing resource content +resource "scaffolding_example" static file exists, skipping +generating missing data source content +resource "scaffolding_example" static file exists, skipping +generating missing provider content +provider "terraform-provider-scaffolding" static file exists, skipping +rendering static website +cleaning rendered website dir +rendering templated website to static markdown +copying non-template file: "d/example.html.markdown" +copying non-template file: "d/example.html.md" +copying non-template file: "d/example.markdown" +copying non-template file: "d/example.md" +copying non-template file: "index.html.markdown" +copying non-template file: "index.html.md" +copying non-template file: "index.markdown" +copying non-template file: "index.md" +copying non-template file: "r/example.html.markdown" +copying non-template file: "r/example.html.md" +copying non-template file: "r/example.markdown" +copying non-template file: "r/example.md" +-- templates/r/example.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/r/example.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/r/example.html.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/r/example.html.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/d/example.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/d/example.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/d/example.html.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/d/example.html.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.html.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.html.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- examples/README.md -- +# Examples + +This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. + +The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. + +* **provider/provider.tf** example file for the provider index page +* **data-sources/`full data source name`/data-source.tf** example file for the named data source page +* **resources/`full resource name`/resource.tf** example file for the named data source page +-- examples/data-sources/scaffolding_example/data-source.tf -- +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/provider/provider.tf -- +provider "scaffolding" { + # example configuration here +} +-- examples/resources/scaffolding_example/resource.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/resources/scaffolding_example/import.sh -- +terraform import scaffolding_example.example +-- schema.json -- +{ + "format_version": "1.0", + "provider_schemas": { + "registry.terraform.io/hashicorp/scaffolding": { + "provider": { + "version": 0, + "block": { + "attributes": { + "endpoint": { + "type": "string", + "description": "Example provider attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "Example provider", + "description_kind": "markdown" + } + }, + "resource_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "defaulted": { + "type": "string", + "description": "Example configurable attribute with default value", + "description_kind": "markdown", + "optional": true, + "computed": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example resource", + "description_kind": "markdown" + } + } + }, + "data_source_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example data source", + "description_kind": "markdown" + } + } + } + } + } +} \ No newline at end of file diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar new file mode 100644 index 00000000..f3cecdd3 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar @@ -0,0 +1,210 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a Framework provider with static files +[!unix] skip +exec tfplugindocs --provider-name=terraform-provider-scaffolding --providers-schema=schema.json +cmp stdout expected-output.txt + +# Check that static files copied successfully to rendered docs directory +cmp templates/resources/example.md docs/resources/example.md +cmp templates/resources/example.markdown docs/resources/example.markdown +cmp templates/resources/example.html.markdown docs/resources/example.html.markdown +cmp templates/resources/example.html.md docs/resources/example.html.md + +cmp templates/data-sources/example.md docs/data-sources/example.md +cmp templates/data-sources/example.markdown docs/data-sources/example.markdown +cmp templates/data-sources/example.html.markdown docs/data-sources/example.html.markdown +cmp templates/data-sources/example.html.md docs/data-sources/example.html.md + +cmp templates/index.md docs/index.md +cmp templates/index.markdown docs/index.markdown +cmp templates/index.html.markdown docs/index.html.markdown +cmp templates/index.html.md docs/index.html.md +-- expected-output.txt -- +rendering website for provider "terraform-provider-scaffolding" (as "terraform-provider-scaffolding") +copying any existing content to tmp dir +exporting schema from JSON file +getting provider schema +rendering missing docs +generating missing resource content +resource "scaffolding_example" static file exists, skipping +generating missing data source content +resource "scaffolding_example" static file exists, skipping +generating missing provider content +provider "terraform-provider-scaffolding" static file exists, skipping +rendering static website +cleaning rendered website dir +rendering templated website to static markdown +copying non-template file: "data-sources/example.html.markdown" +copying non-template file: "data-sources/example.html.md" +copying non-template file: "data-sources/example.markdown" +copying non-template file: "data-sources/example.md" +copying non-template file: "index.html.markdown" +copying non-template file: "index.html.md" +copying non-template file: "index.markdown" +copying non-template file: "index.md" +copying non-template file: "resources/example.html.markdown" +copying non-template file: "resources/example.html.md" +copying non-template file: "resources/example.markdown" +copying non-template file: "resources/example.md" +-- templates/resources/example.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/resources/example.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/resources/example.html.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/resources/example.html.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/data-sources/example.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/data-sources/example.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/data-sources/example.html.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/data-sources/example.html.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.html.markdown -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- templates/index.html.md -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- examples/README.md -- +# Examples + +This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. + +The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. + +* **provider/provider.tf** example file for the provider index page +* **data-sources/`full data source name`/data-source.tf** example file for the named data source page +* **resources/`full resource name`/resource.tf** example file for the named data source page +-- examples/data-sources/scaffolding_example/data-source.tf -- +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/provider/provider.tf -- +provider "scaffolding" { + # example configuration here +} +-- examples/resources/scaffolding_example/resource.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/resources/scaffolding_example/import.sh -- +terraform import scaffolding_example.example +-- schema.json -- +{ + "format_version": "1.0", + "provider_schemas": { + "registry.terraform.io/hashicorp/scaffolding": { + "provider": { + "version": 0, + "block": { + "attributes": { + "endpoint": { + "type": "string", + "description": "Example provider attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "Example provider", + "description_kind": "markdown" + } + }, + "resource_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "defaulted": { + "type": "string", + "description": "Example configurable attribute with default value", + "description_kind": "markdown", + "optional": true, + "computed": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example resource", + "description_kind": "markdown" + } + } + }, + "data_source_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example data source", + "description_kind": "markdown" + } + } + } + } + } +} \ No newline at end of file From 5bba6eaff8cf5727778003338bad0e1b38bcc2ab Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 20 Nov 2023 16:38:09 -0500 Subject: [PATCH 08/11] Refactor `renderMissingResourceDoc` method --- ...k_provider_success_generic_templates.txtar | 8 +- ...ork_provider_success_named_templates.txtar | 4 +- ...mework_provider_success_no_templates.txtar | 8 +- .../generate/null_provider_success.txtar | 8 +- ...k_provider_success_generic_templates.txtar | 8 +- ...amework_provider_success_legacy_docs.txtar | 4 +- ...ork_provider_success_named_templates.txtar | 4 +- ...mework_provider_success_no_templates.txtar | 8 +- ...mework_provider_success_static_files.txtar | 4 +- .../generate/null_provider_success.txtar | 8 +- internal/provider/generate.go | 231 +++++++----------- internal/provider/template.go | 37 +-- internal/provider/util.go | 6 + 13 files changed, 125 insertions(+), 213 deletions(-) diff --git a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar index 5a93b719..8dc12a3c 100644 --- a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar @@ -20,13 +20,11 @@ compiling provider "scaffolding" using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary running terraform init getting provider schema -rendering missing docs +generating missing templates generating missing resource content -resource "scaffolding_example" fallback template exists -generating template for "scaffolding_example" +resource "scaffolding_example" fallback template exists, creating template generating missing data source content -resource "scaffolding_example" fallback template exists -generating template for "scaffolding_example" +data-source "scaffolding_example" fallback template exists, creating template generating missing provider content provider "terraform-provider-scaffolding" template exists, skipping rendering static website diff --git a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_named_templates.txtar b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_named_templates.txtar index 381ba095..35e77b7f 100644 --- a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_named_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_named_templates.txtar @@ -20,11 +20,11 @@ compiling provider "scaffolding" using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary running terraform init getting provider schema -rendering missing docs +generating missing templates generating missing resource content resource "scaffolding_example" template exists, skipping generating missing data source content -resource "scaffolding_example" template exists, skipping +data-source "scaffolding_example" template exists, skipping generating missing provider content provider "terraform-provider-scaffolding" template exists, skipping rendering static website diff --git a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_no_templates.txtar b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_no_templates.txtar index 07ae876f..0460419c 100644 --- a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_no_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_no_templates.txtar @@ -18,13 +18,13 @@ compiling provider "scaffolding" using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary running terraform init getting provider schema -rendering missing docs +generating missing templates generating missing resource content -generating template for "scaffolding_example" +generating new template for "scaffolding_example" generating missing data source content -generating template for "scaffolding_example" +generating new template for data-source "scaffolding_example" generating missing provider content -generating template for "terraform-provider-scaffolding" +generating new template for "terraform-provider-scaffolding" rendering static website cleaning rendered website dir rendering templated website to static markdown diff --git a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/null_provider_success.txtar b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/null_provider_success.txtar index c776a3e2..5caa782e 100644 --- a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/null_provider_success.txtar +++ b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/null_provider_success.txtar @@ -17,13 +17,11 @@ compiling provider "null" using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary running terraform init getting provider schema -rendering missing docs +generating missing templates generating missing resource content -resource "null_resource" fallback template exists -generating template for "null_resource" +resource "null_resource" fallback template exists, creating template generating missing data source content -resource "null_data_source" fallback template exists -generating template for "null_data_source" +data-source "null_data_source" fallback template exists, creating template generating missing provider content provider "terraform-provider-null" template exists, skipping rendering static website diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar index 996b6ef0..f23dae70 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar @@ -15,13 +15,11 @@ rendering website for provider "terraform-provider-scaffolding" (as "terraform-p copying any existing content to tmp dir exporting schema from JSON file getting provider schema -rendering missing docs +generating missing templates generating missing resource content -resource "scaffolding_example" fallback template exists -generating template for "scaffolding_example" +resource "scaffolding_example" fallback template exists, creating template generating missing data source content -resource "scaffolding_example" fallback template exists -generating template for "scaffolding_example" +data-source "scaffolding_example" fallback template exists, creating template generating missing provider content provider "terraform-provider-scaffolding" template exists, skipping rendering static website diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar index 663ea564..374dd8b6 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_legacy_docs.txtar @@ -26,11 +26,11 @@ rendering website for provider "terraform-provider-scaffolding" (as "terraform-p copying any existing content to tmp dir exporting schema from JSON file getting provider schema -rendering missing docs +generating missing templates generating missing resource content resource "scaffolding_example" static file exists, skipping generating missing data source content -resource "scaffolding_example" static file exists, skipping +data-source "scaffolding_example" static file exists, skipping generating missing provider content provider "terraform-provider-scaffolding" static file exists, skipping rendering static website diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_named_templates.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_named_templates.txtar index dc1722c2..1bae5cd3 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_named_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_named_templates.txtar @@ -15,11 +15,11 @@ rendering website for provider "terraform-provider-scaffolding" (as "terraform-p copying any existing content to tmp dir exporting schema from JSON file getting provider schema -rendering missing docs +generating missing templates generating missing resource content resource "scaffolding_example" template exists, skipping generating missing data source content -resource "scaffolding_example" template exists, skipping +data-source "scaffolding_example" template exists, skipping generating missing provider content provider "terraform-provider-scaffolding" template exists, skipping rendering static website diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates.txtar index 00f55847..b8fe1392 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates.txtar @@ -13,13 +13,13 @@ cmp docs/resources/example.md expected-resource.md rendering website for provider "terraform-provider-scaffolding" (as "terraform-provider-scaffolding") exporting schema from JSON file getting provider schema -rendering missing docs +generating missing templates generating missing resource content -generating template for "scaffolding_example" +generating new template for "scaffolding_example" generating missing data source content -generating template for "scaffolding_example" +generating new template for data-source "scaffolding_example" generating missing provider content -generating template for "terraform-provider-scaffolding" +generating new template for "terraform-provider-scaffolding" rendering static website cleaning rendered website dir rendering templated website to static markdown diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar index f3cecdd3..a01fd1e4 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_static_files.txtar @@ -26,11 +26,11 @@ rendering website for provider "terraform-provider-scaffolding" (as "terraform-p copying any existing content to tmp dir exporting schema from JSON file getting provider schema -rendering missing docs +generating missing templates generating missing resource content resource "scaffolding_example" static file exists, skipping generating missing data source content -resource "scaffolding_example" static file exists, skipping +data-source "scaffolding_example" static file exists, skipping generating missing provider content provider "terraform-provider-scaffolding" static file exists, skipping rendering static website diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/null_provider_success.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/null_provider_success.txtar index 9ffa5d4b..71e98aae 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/null_provider_success.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/null_provider_success.txtar @@ -12,13 +12,11 @@ rendering website for provider "terraform-provider-null" (as "terraform-provider copying any existing content to tmp dir exporting schema from JSON file getting provider schema -rendering missing docs +generating missing templates generating missing resource content -resource "null_resource" fallback template exists -generating template for "null_resource" +resource "null_resource" fallback template exists, creating template generating missing data source content -resource "null_data_source" fallback template exists -generating template for "null_data_source" +data-source "null_data_source" fallback template exists, creating template generating missing provider content provider "terraform-provider-null" template exists, skipping rendering static website diff --git a/internal/provider/generate.go b/internal/provider/generate.go index a303a57a..13dd3d18 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -27,43 +27,36 @@ import ( ) var ( - examplesResourceFileTemplate = resourceFileTemplate("resources/{{.Name}}/resource.tf") - examplesResourceImportTemplate = resourceFileTemplate("resources/{{.Name}}/import.sh") - examplesDataSourceFileTemplate = resourceFileTemplate("data-sources/{{ .Name }}/data-source.tf") - examplesProviderFileTemplate = providerFileTemplate("provider/provider.tf") - - websiteResourceFileTemplate = resourceFileTemplate("resources/{{ .ShortName }}.md.tmpl") - websiteResourceFallbackFileTemplate = resourceFileTemplate("resources.md.tmpl") - websiteResourceFileStatic = []resourceFileTemplate{ - resourceFileTemplate("resources/{{ .ShortName }}.md"), - // TODO: warn for all of these, as they won't render? massage them to the proper output file name? - resourceFileTemplate("resources/{{ .ShortName }}.markdown"), - resourceFileTemplate("resources/{{ .ShortName }}.html.markdown"), - resourceFileTemplate("resources/{{ .ShortName }}.html.md"), - resourceFileTemplate("r/{{ .ShortName }}.markdown"), - resourceFileTemplate("r/{{ .ShortName }}.md"), - resourceFileTemplate("r/{{ .ShortName }}.html.markdown"), - resourceFileTemplate("r/{{ .ShortName }}.html.md"), - } - websiteDataSourceFileTemplate = resourceFileTemplate("data-sources/{{ .ShortName }}.md.tmpl") - websiteDataSourceFallbackFileTemplate = resourceFileTemplate("data-sources.md.tmpl") - websiteDataSourceFileStatic = []resourceFileTemplate{ - resourceFileTemplate("data-sources/{{ .ShortName }}.md"), - // TODO: warn for all of these, as they won't render? massage them to the proper output file name? - resourceFileTemplate("data-sources/{{ .ShortName }}.markdown"), - resourceFileTemplate("data-sources/{{ .ShortName }}.html.markdown"), - resourceFileTemplate("data-sources/{{ .ShortName }}.html.md"), - resourceFileTemplate("d/{{ .ShortName }}.markdown"), - resourceFileTemplate("d/{{ .ShortName }}.md"), - resourceFileTemplate("d/{{ .ShortName }}.html.markdown"), - resourceFileTemplate("d/{{ .ShortName }}.html.md"), - } - websiteProviderFileTemplate = providerFileTemplate("index.md.tmpl") - websiteProviderFileStatic = []providerFileTemplate{ - providerFileTemplate("index.markdown"), - providerFileTemplate("index.md"), - providerFileTemplate("index.html.markdown"), - providerFileTemplate("index.html.md"), + websiteResourceFile = "resources/%s.md.tmpl" + websiteResourceFallbackFile = "resources.md.tmpl" + websiteResourceFileStaticCandidates = []string{ + "resources/%s.md", + "resources/%s.markdown", + "resources/%s.html.markdown", + "resources/%s.html.md", + "r/%s.markdown", + "r/%s.md", + "r/%s.html.markdown", + "r/%s.html.md", + } + websiteDataSourceFile = "data-sources/%s.md.tmpl" + websiteDataSourceFallbackFile = "data-sources.md.tmpl" + websiteDataSourceFileStaticCandidates = []string{ + "data-sources/%s.md", + "data-sources/%s.markdown", + "data-sources/%s.html.markdown", + "data-sources/%s.html.md", + "d/%s.markdown", + "d/%s.md", + "d/%s.html.markdown", + "d/%s.html.md", + } + websiteProviderFile = "index.md.tmpl" + websiteProviderFileStaticCandidates = []string{ + "index.markdown", + "index.md", + "index.html.markdown", + "index.html.md", } managedWebsiteSubDirectories = []string{ @@ -223,10 +216,10 @@ func (g *generator) Generate(ctx context.Context) error { } } - g.infof("rendering missing docs") - err = g.renderMissingDocs(providerSchema) + g.infof("generating missing templates") + err = g.generateMissingTemplates(providerSchema) if err != nil { - return fmt.Errorf("error rendering missing docs: %w", err) + return fmt.Errorf("error generating missing templates: %w", err) } g.infof("rendering static website") @@ -263,22 +256,26 @@ func (g *generator) TempTemplatesDir() string { return filepath.Join(g.websiteTmpDir, "templates") } -func (g *generator) renderMissingResourceDoc(resourceName, typeName string, schema *tfjson.Schema, websiteFileTemplate resourceFileTemplate, fallbackWebsiteFileTemplate resourceFileTemplate, websiteStaticCandidateTemplates []resourceFileTemplate, examplesFileTemplate resourceFileTemplate, examplesImportTemplate *resourceFileTemplate) error { - tmplPath, err := websiteFileTemplate.Render(g.providerDir, resourceName, g.providerName) - if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) - } - tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) - if fileExists(tmplPath) { +func (g *generator) generateMissingResourceTemplate(resourceName string) error { + templatePath := fmt.Sprintf(websiteResourceFile, resourceShortName(resourceName, g.providerName)) + templatePath = filepath.Join(g.TempTemplatesDir(), templatePath) + if fileExists(templatePath) { g.infof("resource %q template exists, skipping", resourceName) return nil } - for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(g.providerDir, resourceName, g.providerName) + fallbackTemplatePath := filepath.Join(g.TempTemplatesDir(), websiteResourceFallbackFile) + if fileExists(fallbackTemplatePath) { + g.infof("resource %q fallback template exists, creating template", resourceName) + err := cp(fallbackTemplatePath, templatePath) if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) + return fmt.Errorf("unable to copy fallback template for %q: %w", resourceName, err) } + return nil + } + + for _, candidate := range websiteResourceFileStaticCandidates { + candidatePath := fmt.Sprintf(candidate, resourceShortName(resourceName, g.providerName)) candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) if fileExists(candidatePath) { g.infof("resource %q static file exists, skipping", resourceName) @@ -286,124 +283,85 @@ func (g *generator) renderMissingResourceDoc(resourceName, typeName string, sche } } - examplePath, err := examplesFileTemplate.Render(g.providerDir, resourceName, g.providerName) + g.infof("generating new template for %q", resourceName) + err := writeFile(templatePath, string(defaultResourceTemplate)) if err != nil { - return fmt.Errorf("unable to render example file path for %q: %w", resourceName, err) - } - if examplePath != "" { - examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) - } - if !fileExists(examplePath) { - examplePath = "" + return fmt.Errorf("unable to write template for %q: %w", resourceName, err) } - importPath := "" - if examplesImportTemplate != nil { - importPath, err = examplesImportTemplate.Render(g.providerDir, resourceName, g.providerName) - if err != nil { - return fmt.Errorf("unable to render example import file path for %q: %w", resourceName, err) - } - if importPath != "" { - importPath = filepath.Join(g.ProviderExamplesDir(), importPath) - } - if !fileExists(importPath) { - importPath = "" - } - } - - targetResourceTemplate := defaultResourceTemplate + return nil +} - fallbackTmplPath, err := fallbackWebsiteFileTemplate.Render(g.providerDir, resourceName, g.providerName) - if err != nil { - return fmt.Errorf("unable to render path for resource %q: %w", resourceName, err) +func (g *generator) generateMissingDataSourceTemplate(datasourceName string) error { + templatePath := fmt.Sprintf(websiteDataSourceFile, resourceShortName(datasourceName, g.providerName)) + templatePath = filepath.Join(g.TempTemplatesDir(), templatePath) + if fileExists(templatePath) { + g.infof("data-source %q template exists, skipping", datasourceName) + return nil } - fallbackTmplPath = filepath.Join(g.TempTemplatesDir(), fallbackTmplPath) - if fileExists(fallbackTmplPath) { - g.infof("resource %q fallback template exists", resourceName) - tmplData, err := os.ReadFile(fallbackTmplPath) + + fallbackTemplatePath := filepath.Join(g.TempTemplatesDir(), websiteDataSourceFallbackFile) + if fileExists(fallbackTemplatePath) { + g.infof("data-source %q fallback template exists, creating template", datasourceName) + err := cp(fallbackTemplatePath, templatePath) if err != nil { - return fmt.Errorf("unable to read file %q: %w", fallbackTmplPath, err) + return fmt.Errorf("unable to copy fallback template for %q: %w", datasourceName, err) } - targetResourceTemplate = resourceTemplate(tmplData) + return nil } - g.infof("generating template for %q", resourceName) - md, err := targetResourceTemplate.Render(g.providerDir, resourceName, g.providerName, g.renderedProviderName, typeName, examplePath, importPath, schema) - if err != nil { - return fmt.Errorf("unable to render template for %q: %w", resourceName, err) + for _, candidate := range websiteDataSourceFileStaticCandidates { + candidatePath := fmt.Sprintf(candidate, resourceShortName(datasourceName, g.providerName)) + candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) + if fileExists(candidatePath) { + g.infof("data-source %q static file exists, skipping", datasourceName) + return nil + } } - err = writeFile(tmplPath, md) + g.infof("generating new template for data-source %q", datasourceName) + err := writeFile(templatePath, string(defaultResourceTemplate)) if err != nil { - return fmt.Errorf("unable to write file %q: %w", tmplPath, err) + return fmt.Errorf("unable to write template for %q: %w", datasourceName, err) } return nil } -func (g *generator) renderMissingProviderDoc(schema *tfjson.Schema, websiteFileTemplate providerFileTemplate, websiteStaticCandidateTemplates []providerFileTemplate, examplesFileTemplate providerFileTemplate) error { - tmplPath, err := websiteFileTemplate.Render(g.providerDir, g.providerName) - if err != nil { - return fmt.Errorf("unable to render path for provider %q: %w", g.providerName, err) - } - tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) - if fileExists(tmplPath) { +func (g *generator) generateMissingProviderTemplate() error { + templatePath := filepath.Join(g.TempTemplatesDir(), websiteProviderFile) + if fileExists(templatePath) { g.infof("provider %q template exists, skipping", g.providerName) return nil } - for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(g.providerDir, g.providerName) - if err != nil { - return fmt.Errorf("unable to render path for provider %q: %w", g.providerName, err) - } - candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) + for _, candidate := range websiteProviderFileStaticCandidates { + candidatePath := filepath.Join(g.TempTemplatesDir(), candidate) if fileExists(candidatePath) { g.infof("provider %q static file exists, skipping", g.providerName) return nil } } - examplePath, err := examplesFileTemplate.Render(g.providerDir, g.providerName) - if err != nil { - return fmt.Errorf("unable to render example file path for %q: %w", g.providerName, err) - } - if examplePath != "" { - examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) - } - if !fileExists(examplePath) { - examplePath = "" - } - - g.infof("generating template for %q", g.providerName) - md, err := defaultProviderTemplate.Render(g.providerDir, g.providerName, g.renderedProviderName, examplePath, schema) - if err != nil { - return fmt.Errorf("unable to render template for %q: %w", g.providerName, err) - } - - err = writeFile(tmplPath, md) + g.infof("generating new template for %q", g.providerName) + err := writeFile(templatePath, string(defaultProviderTemplate)) if err != nil { - return fmt.Errorf("unable to write file %q: %w", tmplPath, err) + return fmt.Errorf("unable to write template for %q: %w", g.providerName, err) } return nil } -func (g *generator) renderMissingDocs(providerSchema *tfjson.ProviderSchema) error { +func (g *generator) generateMissingTemplates(providerSchema *tfjson.ProviderSchema) error { g.infof("generating missing resource content") for name, schema := range providerSchema.ResourceSchemas { if g.ignoreDeprecated && schema.Block.Deprecated { continue } - err := g.renderMissingResourceDoc(name, "Resource", schema, - websiteResourceFileTemplate, - websiteResourceFallbackFileTemplate, - websiteResourceFileStatic, - examplesResourceFileTemplate, - &examplesResourceImportTemplate) + err := g.generateMissingResourceTemplate(name) if err != nil { - return fmt.Errorf("unable to render doc %q: %w", name, err) + return fmt.Errorf("unable to generate template for resource %q: %w", name, err) } } @@ -413,25 +371,16 @@ func (g *generator) renderMissingDocs(providerSchema *tfjson.ProviderSchema) err continue } - err := g.renderMissingResourceDoc(name, "Data Source", schema, - websiteDataSourceFileTemplate, - websiteDataSourceFallbackFileTemplate, - websiteDataSourceFileStatic, - examplesDataSourceFileTemplate, - nil) + err := g.generateMissingDataSourceTemplate(name) if err != nil { - return fmt.Errorf("unable to render doc %q: %w", name, err) + return fmt.Errorf("unable to generate template for data-source %q: %w", name, err) } } g.infof("generating missing provider content") - err := g.renderMissingProviderDoc(providerSchema.ConfigSchema, - websiteProviderFileTemplate, - websiteProviderFileStatic, - examplesProviderFileTemplate, - ) + err := g.generateMissingProviderTemplate() if err != nil { - return fmt.Errorf("unable to render provider doc: %w", err) + return fmt.Errorf("unable to generate template for provider: %w", err) } return nil diff --git a/internal/provider/template.go b/internal/provider/template.go index 3d71b419..8b142e7c 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -30,9 +30,6 @@ type ( resourceTemplate string providerTemplate string - resourceFileTemplate string - providerFileTemplate string - docTemplate string ) @@ -116,37 +113,6 @@ func (t docTemplate) Render(providerDir string, out io.Writer) error { return renderTemplate(providerDir, "docTemplate", s, out, nil) } -func (t resourceFileTemplate) Render(providerDir, name, providerName string) (string, error) { - s := string(t) - if s == "" { - return "", nil - } - return renderStringTemplate(providerDir, "resourceFileTemplate", s, struct { - Name string - ShortName string - - ProviderName string - ProviderShortName string - }{ - Name: name, - ShortName: resourceShortName(name, providerName), - - ProviderName: providerName, - ProviderShortName: providerShortName(providerName), - }) -} - -func (t providerFileTemplate) Render(providerDir, name string) (string, error) { - s := string(t) - if s == "" { - return "", nil - } - return renderStringTemplate(providerDir, "providerFileTemplate", s, struct { - Name string - ShortName string - }{name, providerShortName(name)}) -} - func (t providerTemplate) Render(providerDir, providerName, renderedProviderName, exampleFile string, schema *tfjson.Schema) (string, error) { schemaBuffer := bytes.NewBuffer(nil) err := schemamd.Render(schema, schemaBuffer) @@ -167,8 +133,7 @@ func (t providerTemplate) Render(providerDir, providerName, renderedProviderName ProviderName string ProviderShortName string - - SchemaMarkdown string + SchemaMarkdown string RenderedProviderName string }{ diff --git a/internal/provider/util.go b/internal/provider/util.go index ce9acf76..bb4f50e4 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -31,6 +31,12 @@ func copyFile(srcPath, dstPath string, mode os.FileMode) error { } defer srcFile.Close() + // Ensure destination path exists for file creation + err = os.MkdirAll(filepath.Dir(dstPath), 0755) + if err != nil { + return err + } + // If the destination file already exists, we shouldn't blow it away dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode) if err != nil { From 6575fba2a705faa4ec43d1884cf90e7f44ec6ef2 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 20 Nov 2023 17:26:19 -0500 Subject: [PATCH 09/11] Update default templates to use `tffile` and `codefile` functions directly --- ...work_provider_success_generic_templates.txtar | 16 +++------------- ...work_provider_success_generic_templates.txtar | 16 +++------------- internal/provider/template.go | 6 +++--- 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar index 8dc12a3c..6738e4a4 100644 --- a/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/provider-build/generate/framework_provider_success_generic_templates.txtar @@ -76,11 +76,7 @@ printf codefile: printf tffile: ## Example Usage -```terraform -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` +{{tffile "$WORK/examples/data-sources/scaffolding_example/data-source.tf"}} codefile: @@ -180,18 +176,12 @@ printf codefile: Import is supported using the following syntax: -```shell -terraform import scaffolding_example.example -``` +{{codefile "shell" "$WORK/examples/resources/scaffolding_example/import.sh"}} printf tffile: ## Example Usage -```terraform -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` +{{tffile "$WORK/examples/resources/scaffolding_example/resource.tf"}} codefile: ## Import diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar index f23dae70..156b7442 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_generic_templates.txtar @@ -71,11 +71,7 @@ printf codefile: printf tffile: ## Example Usage -```terraform -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` +{{tffile "$WORK/examples/data-sources/scaffolding_example/data-source.tf"}} codefile: @@ -175,18 +171,12 @@ printf codefile: Import is supported using the following syntax: -```shell -terraform import scaffolding_example.example -``` +{{codefile "shell" "$WORK/examples/resources/scaffolding_example/import.sh"}} printf tffile: ## Example Usage -```terraform -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` +{{tffile "$WORK/examples/resources/scaffolding_example/resource.tf"}} codefile: ## Import diff --git a/internal/provider/template.go b/internal/provider/template.go index 8b142e7c..f2a24cbc 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -215,7 +215,7 @@ description: |- {{ if .HasExample -}} ## Example Usage -{{ printf "{{tffile %q}}" .ExampleFile }} +{{tffile .ExampleFile }} {{- end }} {{ .SchemaMarkdown | trimspace }} @@ -225,7 +225,7 @@ description: |- Import is supported using the following syntax: -{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{codefile "shell" .ImportFile }} {{- end }} ` @@ -244,7 +244,7 @@ description: |- {{ if .HasExample -}} ## Example Usage -{{ printf "{{tffile %q}}" .ExampleFile }} +{{tffile .ExampleFile }} {{- end }} {{ .SchemaMarkdown | trimspace }} From 3ee5b04e053ad7eef767a87dde3503e308e6e5a6 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 21 Nov 2023 15:00:53 -0500 Subject: [PATCH 10/11] Add changelog entry --- .changes/unreleased/BUG FIXES-20231121-150034.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/BUG FIXES-20231121-150034.yaml diff --git a/.changes/unreleased/BUG FIXES-20231121-150034.yaml b/.changes/unreleased/BUG FIXES-20231121-150034.yaml new file mode 100644 index 00000000..69a080a8 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20231121-150034.yaml @@ -0,0 +1,6 @@ +kind: BUG FIXES +body: 'generate: fix incorrect rendering of example and import files for providers + with no docs templates or with generic fallback templates.' +time: 2023-11-21T15:00:34.216261-05:00 +custom: + Issue: "300" From 3729ea61530bee3b6c4ad11b40602d3bcb20f46f Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 6 Dec 2023 14:14:00 -0500 Subject: [PATCH 11/11] Add additional changelog entry for template change --- .../BREAKING CHANGES-20231206-140932.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changes/unreleased/BREAKING CHANGES-20231206-140932.yaml diff --git a/.changes/unreleased/BREAKING CHANGES-20231206-140932.yaml b/.changes/unreleased/BREAKING CHANGES-20231206-140932.yaml new file mode 100644 index 00000000..e2519d9a --- /dev/null +++ b/.changes/unreleased/BREAKING CHANGES-20231206-140932.yaml @@ -0,0 +1,15 @@ +kind: BREAKING CHANGES +body: 'generate: templates using `printf` with either `codefile` or `tffile` to render code examples in markdown will need to switch to using those +functions directly. + +For example, switch the following template code: + +`{{printf "{{codefile \"shell\" %q}}" .ImportFile}}` + +to + +`{{codefile "shell" .ImportFile}}` +' +time: 2023-12-06T14:09:32.757057-05:00 +custom: + Issue: "300"