From fe6bf46cebc193434161714b4e4a35e739d7d7f7 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 7 Jun 2023 09:30:46 -0400 Subject: [PATCH] generate: Introduce provider-dir flag (#259) Reference: https://github.com/hashicorp/terraform-plugin-docs/issues/13 By default, the generator expects to be running in the root provider directory. The new `provider-dir` flag enables developers to run the command in any directory where the relative or absolute path is passed in as the base directory for all file operations for a provider codebase. All prior behaviors should be preserved, such as customizing via other flags like `examples-dir`. Due to lack of existing end-to-end testing (known technical debt), verified by locally compiling and running against existing HashiCorp-owned provider implementations which use `tfplugindocs` and by running the command in a subdirectory of hashicorp/terraform-provider-tls which has a mixture of custom and default templates and observed no changes or errors: ```console $ tfplugindocs generate -provider-dir=.. rendering website for provider "terraform-provider-tls" (as "terraform-provider-tls") copying any existing content to tmp dir exporting schema from Terraform compiling provider "tls" 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 resource content resource "tls_locally_signed_cert" template exists, skipping resource "tls_private_key" template exists, skipping resource "tls_self_signed_cert" template exists, skipping resource "tls_cert_request" template exists, skipping generating missing data source content resource "tls_certificate" template exists, skipping generating template for "tls_public_key" generating missing provider content provider "terraform-provider-tls" template exists, skipping rendering static website cleaning rendered website dir rendering templated website to static markdown rendering "data-sources/certificate.md.tmpl" rendering "data-sources/public_key.md.tmpl" rendering "index.md.tmpl" rendering "resources/cert_request.md.tmpl" rendering "resources/locally_signed_cert.md.tmpl" rendering "resources/private_key.md.tmpl" rendering "resources/self_signed_cert.md.tmpl" ``` --- .../ENHANCEMENTS-20230606-141357.yaml | 6 + README.md | 21 +-- internal/cmd/generate.go | 11 +- internal/provider/generate.go | 140 ++++++++++++------ internal/provider/template.go | 56 ++++--- internal/provider/template_test.go | 2 +- internal/tmplfuncs/tmplfuncs.go | 11 +- 7 files changed, 161 insertions(+), 86 deletions(-) create mode 100644 .changes/unreleased/ENHANCEMENTS-20230606-141357.yaml diff --git a/.changes/unreleased/ENHANCEMENTS-20230606-141357.yaml b/.changes/unreleased/ENHANCEMENTS-20230606-141357.yaml new file mode 100644 index 00000000..891f6806 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20230606-141357.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: 'generate: Added `provider-dir` flag, which enables the command to be run from + any directory' +time: 2023-06-06T14:13:57.482032-04:00 +custom: + Issue: "259" diff --git a/README.md b/README.md index 97f16ae9..0cfde412 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Usage: tfplugindocs [--version] [--help] [] Available commands are: the generate command is run by default - generate generates a plugin website from code, templates, and examples for the current directory + generate generates a plugin website from code, templates, and examples validate validates a plugin website for the current directory ``` @@ -39,14 +39,15 @@ $ tfplugindocs generate --help Usage: tfplugindocs generate [] - --examples-dir examples directory (default: "examples") - --ignore-deprecated don't generate documentation for deprecated resources and data-sources (default: "false") - --provider-name provider name, as used in Terraform configurations - --rendered-provider-name provider name, as generated in documentation (ex. page titles, ...) - --rendered-website-dir output directory (default: "docs") - --tf-version terraform binary version to download - --website-source-dir templates directory (default: "templates") - --website-temp-dir temporary directory (used during generation) + --examples-dir examples directory based on provider-dir (default: "examples") + --ignore-deprecated don't generate documentation for deprecated resources and data-sources (default: "false") + --provider-dir relative or absolute path to the root provider code directory when running the command outside the root provider code directory + --provider-name provider name, as used in Terraform configurations + --rendered-provider-name provider name, as generated in documentation (ex. page titles, ...) + --rendered-website-dir output directory based on provider-dir (default: "docs") + --tf-version terraform binary version to download + --website-source-dir templates directory based on provider-dir (default: "templates") + --website-temp-dir temporary directory (used during generation) ``` `validate` command: @@ -59,7 +60,7 @@ Usage: tfplugindocs validate [] ### How it Works -When you run `tfplugindocs` from root directory of the provider the tool takes the following actions: +When you run `tfplugindocs`, by default from the root directory of a provider codebase, the tool takes the following actions: * Copy all the templates and static files to a temporary directory * Build (`go build`) a temporary binary of the provider source code diff --git a/internal/cmd/generate.go b/internal/cmd/generate.go index 3bd3468d..29b25ed5 100644 --- a/internal/cmd/generate.go +++ b/internal/cmd/generate.go @@ -19,6 +19,7 @@ type generateCmd struct { flagProviderName string flagRenderedProviderName string + flagProviderDir string flagRenderedWebsiteDir string flagExamplesDir string flagWebsiteTmpDir string @@ -27,7 +28,7 @@ type generateCmd struct { } func (cmd *generateCmd) Synopsis() string { - return "generates a plugin website from code, templates, and examples for the current directory" + return "generates a plugin website from code, templates, and examples" } func (cmd *generateCmd) Help() string { @@ -71,11 +72,12 @@ func (cmd *generateCmd) Help() string { func (cmd *generateCmd) Flags() *flag.FlagSet { fs := flag.NewFlagSet("generate", flag.ExitOnError) fs.StringVar(&cmd.flagProviderName, "provider-name", "", "provider name, as used in Terraform configurations") + fs.StringVar(&cmd.flagProviderDir, "provider-dir", "", "relative or absolute path to the root provider code directory when running the command outside the root provider code directory") fs.StringVar(&cmd.flagRenderedProviderName, "rendered-provider-name", "", "provider name, as generated in documentation (ex. page titles, ...)") - fs.StringVar(&cmd.flagRenderedWebsiteDir, "rendered-website-dir", "docs", "output directory") - fs.StringVar(&cmd.flagExamplesDir, "examples-dir", "examples", "examples directory") + fs.StringVar(&cmd.flagRenderedWebsiteDir, "rendered-website-dir", "docs", "output directory based on provider-dir") + fs.StringVar(&cmd.flagExamplesDir, "examples-dir", "examples", "examples directory based on provider-dir") fs.StringVar(&cmd.flagWebsiteTmpDir, "website-temp-dir", "", "temporary directory (used during generation)") - fs.StringVar(&cmd.flagWebsiteSourceDir, "website-source-dir", "templates", "templates directory") + fs.StringVar(&cmd.flagWebsiteSourceDir, "website-source-dir", "templates", "templates directory based on provider-dir") fs.StringVar(&cmd.tfVersion, "tf-version", "", "terraform binary version to download") fs.BoolVar(&cmd.flagIgnoreDeprecated, "ignore-deprecated", false, "don't generate documentation for deprecated resources and data-sources") return fs @@ -95,6 +97,7 @@ func (cmd *generateCmd) Run(args []string) int { func (cmd *generateCmd) runInternal() error { err := provider.Generate( cmd.ui, + cmd.flagProviderDir, cmd.flagProviderName, cmd.flagRenderedProviderName, cmd.flagRenderedWebsiteDir, diff --git a/internal/provider/generate.go b/internal/provider/generate.go index 388368b6..71fd1f8a 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -69,12 +69,15 @@ type generator struct { ignoreDeprecated bool tfVersion string + // providerDir is the absolute path to the root provider directory + providerDir string + providerName string renderedProviderName string renderedWebsiteDir string examplesDir string + templatesDir string websiteTmpDir string - websiteSourceDir string ui cli.Ui } @@ -87,17 +90,48 @@ func (g *generator) warnf(format string, a ...interface{}) { g.ui.Warn(fmt.Sprintf(format, a...)) } -func Generate(ui cli.Ui, providerName, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, websiteSourceDir, 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() + + if err != nil { + return fmt.Errorf("error getting working directory: %w", err) + } + + providerDir = wd + } else { + absProviderDir, err := filepath.Abs(providerDir) + + if err != nil { + return fmt.Errorf("error getting absolute path with provider directory %q: %w", providerDir, err) + } + + providerDir = absProviderDir + } + + // Verify provider directory + providerDirFileInfo, err := os.Stat(providerDir) + + if err != nil { + return fmt.Errorf("error getting information for provider directory %q: %w", providerDir, err) + } + + if !providerDirFileInfo.IsDir() { + return fmt.Errorf("expected %q to be a directory", providerDir) + } + g := &generator{ ignoreDeprecated: ignoreDeprecated, tfVersion: tfVersion, + providerDir: providerDir, providerName: providerName, renderedProviderName: renderedProviderName, renderedWebsiteDir: renderedWebsiteDir, examplesDir: examplesDir, + templatesDir: templatesDir, websiteTmpDir: websiteTmpDir, - websiteSourceDir: websiteSourceDir, ui: ui, } @@ -110,14 +144,9 @@ func Generate(ui cli.Ui, providerName, renderedProviderName, renderedWebsiteDir, func (g *generator) Generate(ctx context.Context) error { var err error - wd, err := os.Getwd() - if err != nil { - return err - } - providerName := g.providerName if g.providerName == "" { - providerName = filepath.Base(wd) + providerName = filepath.Base(g.providerDir) } if g.renderedProviderName == "" { @@ -147,19 +176,19 @@ func (g *generator) Generate(ctx context.Context) error { } } - websiteSourceDirInfo, err := os.Stat(g.websiteSourceDir) + templatesDirInfo, err := os.Stat(g.ProviderTemplatesDir()) switch { case os.IsNotExist(err): // do nothing, no template dir case err != nil: return err default: - if !websiteSourceDirInfo.IsDir() { - return fmt.Errorf("template path is not a directory: %s", g.websiteSourceDir) + if !templatesDirInfo.IsDir() { + return fmt.Errorf("template path is not a directory: %s", g.ProviderTemplatesDir()) } g.infof("copying any existing content to tmp dir") - err = cp(g.websiteSourceDir, filepath.Join(g.websiteTmpDir, "templates")) + err = cp(g.ProviderTemplatesDir(), g.TempTemplatesDir()) if err != nil { return err } @@ -186,35 +215,60 @@ func (g *generator) Generate(ctx context.Context) error { return nil } +// ProviderDocsDir returns the absolute path to the joined provider and +// given website documentation directory, which defaults to "docs". +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 { + 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 { + 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 +// copied. +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(name, providerName) + tmplPath, err := websiteFileTemplate.Render(g.providerDir, name, providerName) if err != nil { return fmt.Errorf("unable to render path for resource %q: %w", name, err) } - tmplPath = filepath.Join(g.websiteTmpDir, g.websiteSourceDir, tmplPath) + tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) if fileExists(tmplPath) { g.infof("resource %q template exists, skipping", name) return nil } for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(name, providerName) + candidatePath, err := candidate.Render(g.providerDir, name, providerName) if err != nil { return fmt.Errorf("unable to render path for resource %q: %w", name, err) } - candidatePath = filepath.Join(g.websiteTmpDir, g.websiteSourceDir, candidatePath) + candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) if fileExists(candidatePath) { g.infof("resource %q static file exists, skipping", name) return nil } } - examplePath, err := examplesFileTemplate.Render(name, providerName) + examplePath, err := examplesFileTemplate.Render(g.providerDir, name, providerName) if err != nil { return fmt.Errorf("unable to render example file path for %q: %w", name, err) } if examplePath != "" { - examplePath = filepath.Join(g.examplesDir, examplePath) + examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) } if !fileExists(examplePath) { examplePath = "" @@ -222,12 +276,12 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string importPath := "" if examplesImportTemplate != nil { - importPath, err = examplesImportTemplate.Render(name, 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", name, err) } if importPath != "" { - importPath = filepath.Join(g.examplesDir, importPath) + importPath = filepath.Join(g.ProviderExamplesDir(), importPath) } if !fileExists(importPath) { importPath = "" @@ -236,11 +290,11 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string targetResourceTemplate := defaultResourceTemplate - fallbackTmplPath, err := fallbackWebsiteFileTemplate.Render(name, providerName) + fallbackTmplPath, err := fallbackWebsiteFileTemplate.Render(g.providerDir, name, providerName) if err != nil { return fmt.Errorf("unable to render path for resource %q: %w", name, err) } - fallbackTmplPath = filepath.Join(g.websiteTmpDir, g.websiteSourceDir, fallbackTmplPath) + fallbackTmplPath = filepath.Join(g.TempTemplatesDir(), fallbackTmplPath) if fileExists(fallbackTmplPath) { g.infof("resource %q fallback template exists", name) tmplData, err := os.ReadFile(fallbackTmplPath) @@ -251,7 +305,7 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string } g.infof("generating template for %q", name) - md, err := targetResourceTemplate.Render(name, providerName, g.renderedProviderName, typeName, examplePath, importPath, schema) + 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", name, err) } @@ -265,41 +319,41 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string } func (g *generator) renderMissingProviderDoc(providerName string, schema *tfjson.Schema, websiteFileTemplate providerFileTemplate, websiteStaticCandidateTemplates []providerFileTemplate, examplesFileTemplate providerFileTemplate) error { - tmplPath, err := websiteFileTemplate.Render(providerName) + tmplPath, err := websiteFileTemplate.Render(g.providerDir, providerName) if err != nil { return fmt.Errorf("unable to render path for provider %q: %w", providerName, err) } - tmplPath = filepath.Join(g.websiteTmpDir, g.websiteSourceDir, tmplPath) + tmplPath = filepath.Join(g.TempTemplatesDir(), tmplPath) if fileExists(tmplPath) { g.infof("provider %q template exists, skipping", providerName) return nil } for _, candidate := range websiteStaticCandidateTemplates { - candidatePath, err := candidate.Render(providerName) + candidatePath, err := candidate.Render(g.providerDir, providerName) if err != nil { return fmt.Errorf("unable to render path for provider %q: %w", providerName, err) } - candidatePath = filepath.Join(g.websiteTmpDir, g.websiteSourceDir, candidatePath) + candidatePath = filepath.Join(g.TempTemplatesDir(), candidatePath) if fileExists(candidatePath) { g.infof("provider %q static file exists, skipping", providerName) return nil } } - examplePath, err := examplesFileTemplate.Render(providerName) + examplePath, err := examplesFileTemplate.Render(g.providerDir, providerName) if err != nil { return fmt.Errorf("unable to render example file path for %q: %w", providerName, err) } if examplePath != "" { - examplePath = filepath.Join(g.examplesDir, examplePath) + examplePath = filepath.Join(g.ProviderExamplesDir(), examplePath) } if !fileExists(examplePath) { examplePath = "" } g.infof("generating template for %q", providerName) - md, err := defaultProviderTemplate.Render(providerName, g.renderedProviderName, examplePath, schema) + md, err := defaultProviderTemplate.Render(g.providerDir, providerName, g.renderedProviderName, examplePath, schema) if err != nil { return fmt.Errorf("unable to render template for %q: %w", providerName, err) } @@ -362,7 +416,7 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfjson.ProviderSchema) error { g.infof("cleaning rendered website dir") - err := os.RemoveAll(g.renderedWebsiteDir) + err := os.RemoveAll(g.ProviderDocsDir()) if err != nil { return err } @@ -377,7 +431,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj return nil } - rel, err := filepath.Rel(filepath.Join(g.websiteTmpDir, g.websiteSourceDir), path) + rel, err := filepath.Rel(filepath.Join(g.TempTemplatesDir()), path) if err != nil { return err } @@ -390,7 +444,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj return nil } - renderedPath := filepath.Join(g.renderedWebsiteDir, rel) + renderedPath := filepath.Join(g.ProviderDocsDir(), rel) err = os.MkdirAll(filepath.Dir(renderedPath), 0755) if err != nil { return err @@ -419,10 +473,11 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj switch relDir { case "data-sources/": resSchema, resName := resourceSchema(providerSchema.DataSourceSchemas, shortName, relFile) - exampleFilePath := filepath.Join(g.examplesDir, "data-sources", resName, "data-source.tf") + exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "data-sources", resName, "data-source.tf") + if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(resName, 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) } @@ -435,12 +490,12 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj g.warnf("data source entitled %q, or %q does not exist", shortName, resName) case "resources/": resSchema, resName := resourceSchema(providerSchema.ResourceSchemas, shortName, relFile) - exampleFilePath := filepath.Join(g.examplesDir, "resources", resName, "resource.tf") - importFilePath := filepath.Join(g.examplesDir, "resources", resName, "import.sh") + exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "resource.tf") + importFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "import.sh") if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(resName, 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) } @@ -454,8 +509,8 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj case "": // provider if relFile == "index.md.tmpl" { tmpl := providerTemplate(tmplData) - exampleFilePath := filepath.Join(g.examplesDir, "provider", "provider.tf") - render, err := tmpl.Render(providerName, g.renderedProviderName, exampleFilePath, providerSchema.ConfigSchema) + exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "provider", "provider.tf") + 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) } @@ -468,7 +523,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj } tmpl := docTemplate(tmplData) - err = tmpl.Render(out) + err = tmpl.Render(g.providerDir, out) if err != nil { return fmt.Errorf("unable to render template %q: %w", rel, err) } @@ -505,6 +560,7 @@ func (g *generator) terraformProviderSchema(ctx context.Context, providerName st outFile = outFile + ".exe" } buildCmd := exec.Command("go", "build", "-o", outFile) + buildCmd.Dir = g.providerDir // TODO: constrain env here to make it a little safer? _, err = runCmd(buildCmd) if err != nil { diff --git a/internal/provider/template.go b/internal/provider/template.go index 2896fde5..3d71b419 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "io" + "path/filepath" "strings" "text/template" @@ -35,17 +36,17 @@ type ( docTemplate string ) -func newTemplate(name, text string) (*template.Template, error) { +func newTemplate(providerDir, name, text string) (*template.Template, error) { tmpl := template.New(name) titleCaser := cases.Title(language.Und) tmpl.Funcs(map[string]interface{}{ - "codefile": tmplfuncs.CodeFile, + "codefile": codeFile(providerDir), "lower": strings.ToLower, "plainmarkdown": mdplain.PlainMarkdown, "prefixlines": tmplfuncs.PrefixLines, "split": strings.Split, - "tffile": terraformCodeFile, + "tffile": terraformCodeFile(providerDir), "title": titleCaser.String, "trimspace": strings.TrimSpace, "upper": strings.ToUpper, @@ -60,13 +61,29 @@ func newTemplate(name, text string) (*template.Template, error) { return tmpl, nil } -func terraformCodeFile(file string) (string, error) { +func codeFile(providerDir string) func(string, string) (string, error) { + return func(format string, file string) (string, error) { + if filepath.IsAbs(file) { + return tmplfuncs.CodeFile(format, file) + } + + return tmplfuncs.CodeFile(format, filepath.Join(providerDir, file)) + } +} + +func terraformCodeFile(providerDir string) func(string) (string, error) { // TODO: omit comment handling - return tmplfuncs.CodeFile("terraform", file) + return func(file string) (string, error) { + if filepath.IsAbs(file) { + return tmplfuncs.CodeFile("terraform", file) + } + + return tmplfuncs.CodeFile("terraform", filepath.Join(providerDir, file)) + } } -func renderTemplate(name string, text string, out io.Writer, data interface{}) error { - tmpl, err := newTemplate(name, text) +func renderTemplate(providerDir, name string, text string, out io.Writer, data interface{}) error { + tmpl, err := newTemplate(providerDir, name, text) if err != nil { return err } @@ -79,10 +96,10 @@ func renderTemplate(name string, text string, out io.Writer, data interface{}) e return nil } -func renderStringTemplate(name, text string, data interface{}) (string, error) { +func renderStringTemplate(providerDir, name, text string, data interface{}) (string, error) { var buf bytes.Buffer - err := renderTemplate(name, text, &buf, data) + err := renderTemplate(providerDir, name, text, &buf, data) if err != nil { return "", err } @@ -90,21 +107,21 @@ func renderStringTemplate(name, text string, data interface{}) (string, error) { return buf.String(), nil } -func (t docTemplate) Render(out io.Writer) error { +func (t docTemplate) Render(providerDir string, out io.Writer) error { s := string(t) if s == "" { return nil } - return renderTemplate("docTemplate", s, out, nil) + return renderTemplate(providerDir, "docTemplate", s, out, nil) } -func (t resourceFileTemplate) Render(name, providerName string) (string, error) { +func (t resourceFileTemplate) Render(providerDir, name, providerName string) (string, error) { s := string(t) if s == "" { return "", nil } - return renderStringTemplate("resourceFileTemplate", s, struct { + return renderStringTemplate(providerDir, "resourceFileTemplate", s, struct { Name string ShortName string @@ -119,18 +136,18 @@ func (t resourceFileTemplate) Render(name, providerName string) (string, error) }) } -func (t providerFileTemplate) Render(name string) (string, error) { +func (t providerFileTemplate) Render(providerDir, name string) (string, error) { s := string(t) if s == "" { return "", nil } - return renderStringTemplate("providerFileTemplate", s, struct { + return renderStringTemplate(providerDir, "providerFileTemplate", s, struct { Name string ShortName string }{name, providerShortName(name)}) } -func (t providerTemplate) Render(providerName, renderedProviderName, exampleFile string, schema *tfjson.Schema) (string, error) { +func (t providerTemplate) Render(providerDir, providerName, renderedProviderName, exampleFile string, schema *tfjson.Schema) (string, error) { schemaBuffer := bytes.NewBuffer(nil) err := schemamd.Render(schema, schemaBuffer) if err != nil { @@ -141,7 +158,8 @@ func (t providerTemplate) Render(providerName, renderedProviderName, exampleFile if s == "" { return "", nil } - return renderStringTemplate("providerTemplate", s, struct { + + return renderStringTemplate(providerDir, "providerTemplate", s, struct { Description string HasExample bool @@ -168,7 +186,7 @@ func (t providerTemplate) Render(providerName, renderedProviderName, exampleFile }) } -func (t resourceTemplate) Render(name, providerName, renderedProviderName, typeName, exampleFile, importFile string, schema *tfjson.Schema) (string, error) { +func (t resourceTemplate) Render(providerDir, name, providerName, renderedProviderName, typeName, exampleFile, importFile string, schema *tfjson.Schema) (string, error) { schemaBuffer := bytes.NewBuffer(nil) err := schemamd.Render(schema, schemaBuffer) if err != nil { @@ -180,7 +198,7 @@ func (t resourceTemplate) Render(name, providerName, renderedProviderName, typeN return "", nil } - return renderStringTemplate("resourceTemplate", s, struct { + return renderStringTemplate(providerDir, "resourceTemplate", s, struct { Type string Name string Description string diff --git a/internal/provider/template_test.go b/internal/provider/template_test.go index 88804e25..349f6892 100644 --- a/internal/provider/template_test.go +++ b/internal/provider/template_test.go @@ -34,7 +34,7 @@ Prefixlines: This text used multiple lines ` - result, err := renderStringTemplate("testTemplate", template, struct { + result, err := renderStringTemplate("test-provider-dir", "testTemplate", template, struct { Text string MultiLineTest string }{ diff --git a/internal/tmplfuncs/tmplfuncs.go b/internal/tmplfuncs/tmplfuncs.go index 8fd393f5..15bcc761 100644 --- a/internal/tmplfuncs/tmplfuncs.go +++ b/internal/tmplfuncs/tmplfuncs.go @@ -6,7 +6,6 @@ package tmplfuncs import ( "fmt" "os" - "path/filepath" "strings" ) @@ -15,15 +14,7 @@ func PrefixLines(prefix, text string) string { } func CodeFile(format, file string) (string, error) { - // paths are relative to the rendering process work dir, which - // may be undesirable, probably need to think about it - wd, err := os.Getwd() - if err != nil { - return "", err - } - - fullPath := filepath.Join(wd, file) - content, err := os.ReadFile(fullPath) + content, err := os.ReadFile(file) if err != nil { return "", fmt.Errorf("unable to read content from %q: %w", file, err) }