@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package provider
package provider
import (
import (
@ -64,15 +67,17 @@ var (
type generator struct {
type generator struct {
ignoreDeprecated bool
ignoreDeprecated bool
legacySidebar bool
tfVersion string
tfVersion string
// providerDir is the absolute path to the root provider directory
providerDir string
providerName string
providerName string
renderedProviderName string
renderedProviderName string
renderedWebsiteDir string
renderedWebsiteDir string
examplesDir string
examplesDir string
templatesDir string
websiteTmpDir string
websiteTmpDir string
websiteSourceDir string
ui cli . Ui
ui cli . Ui
}
}
@ -85,18 +90,48 @@ func (g *generator) warnf(format string, a ...interface{}) {
g . ui . Warn ( fmt . Sprintf ( format , a ... ) )
g . ui . Warn ( fmt . Sprintf ( format , a ... ) )
}
}
func Generate ( ui cli . Ui , legacySidebar bool , 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 {
g := & generator {
ignoreDeprecated : ignoreDeprecated ,
ignoreDeprecated : ignoreDeprecated ,
legacySidebar : legacySidebar ,
tfVersion : tfVersion ,
tfVersion : tfVersion ,
providerDir : providerDir ,
providerName : providerName ,
providerName : providerName ,
renderedProviderName : renderedProviderName ,
renderedProviderName : renderedProviderName ,
renderedWebsiteDir : renderedWebsiteDir ,
renderedWebsiteDir : renderedWebsiteDir ,
examplesDir : examplesDir ,
examplesDir : examplesDir ,
templatesDir : templatesDir ,
websiteTmpDir : websiteTmpDir ,
websiteTmpDir : websiteTmpDir ,
websiteSourceDir : websiteSourceDir ,
ui : ui ,
ui : ui ,
}
}
@ -109,14 +144,9 @@ func Generate(ui cli.Ui, legacySidebar bool, providerName, renderedProviderName,
func ( g * generator ) Generate ( ctx context . Context ) error {
func ( g * generator ) Generate ( ctx context . Context ) error {
var err error
var err error
wd , err := os . Getwd ( )
if err != nil {
return err
}
providerName := g . providerName
providerName := g . providerName
if g . providerName == "" {
if g . providerName == "" {
providerName = filepath . Base ( wd )
providerName = filepath . Base ( g . providerDir )
}
}
if g . renderedProviderName == "" {
if g . renderedProviderName == "" {
@ -146,19 +176,19 @@ func (g *generator) Generate(ctx context.Context) error {
}
}
}
}
websiteSource DirInfo, err := os . Stat ( g . websiteSourceDir )
templates DirInfo, err := os . Stat ( g . ProviderTemplatesDir ( ) )
switch {
switch {
case os . IsNotExist ( err ) :
case os . IsNotExist ( err ) :
// do nothing, no template dir
// do nothing, no template dir
case err != nil :
case err != nil :
return err
return err
default :
default :
if ! websiteSource DirInfo. IsDir ( ) {
if ! templates DirInfo. IsDir ( ) {
return fmt . Errorf ( "template path is not a directory: %s" , g . websiteSourceDir )
return fmt . Errorf ( "template path is not a directory: %s" , g . ProviderTemplatesDir ( ) )
}
}
g . infof ( "copying any existing content to tmp dir" )
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 {
if err != nil {
return err
return err
}
}
@ -182,44 +212,63 @@ func (g *generator) Generate(ctx context.Context) error {
return err
return err
}
}
// TODO: may not ever need this, unsure on when this will go live
if g . legacySidebar {
g . infof ( "rendering legacy sidebar..." )
g . warnf ( "TODO...!" )
}
return nil
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 {
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 {
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" , name , err )
}
}
tmplPath = filepath . Join ( g . websiteTmpDir , g . websiteSourceDir , tmplPath )
tmplPath = filepath . Join ( g . TempTemplatesDir ( ) , tmplPath )
if fileExists ( tmplPath ) {
if fileExists ( tmplPath ) {
g . infof ( "resource %q template exists, skipping" , name )
g . infof ( "resource %q template exists, skipping" , name )
return nil
return nil
}
}
for _ , candidate := range websiteStaticCandidateTemplates {
for _ , candidate := range websiteStaticCandidateTemplates {
candidatePath , err := candidate . Render ( name , providerName )
candidatePath , err := candidate . Render ( g. providerDir , name, providerName )
if err != nil {
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" , name , err )
}
}
candidatePath = filepath . Join ( g . websiteTmpDir , g . websiteSourceDir , candidatePath )
candidatePath = filepath . Join ( g . TempTemplatesDir ( ) , candidatePath )
if fileExists ( candidatePath ) {
if fileExists ( candidatePath ) {
g . infof ( "resource %q static file exists, skipping" , name )
g . infof ( "resource %q static file exists, skipping" , name )
return nil
return nil
}
}
}
}
examplePath , err := examplesFileTemplate . Render ( name , providerName )
examplePath , err := examplesFileTemplate . Render ( g. providerDir , name, providerName )
if err != nil {
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" , name , err )
}
}
if examplePath != "" {
if examplePath != "" {
examplePath = filepath . Join ( g . examplesDir , examplePath )
examplePath = filepath . Join ( g . ProviderExamplesDir ( ) , examplePath )
}
}
if ! fileExists ( examplePath ) {
if ! fileExists ( examplePath ) {
examplePath = ""
examplePath = ""
@ -227,12 +276,12 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string
importPath := ""
importPath := ""
if examplesImportTemplate != nil {
if examplesImportTemplate != nil {
importPath , err = examplesImportTemplate . Render ( name, providerName )
importPath , err = examplesImportTemplate . Render ( g. providerDir , name, providerName )
if err != nil {
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" , name , err )
}
}
if importPath != "" {
if importPath != "" {
importPath = filepath . Join ( g . examplesDir , importPath )
importPath = filepath . Join ( g . ProviderExamplesDir ( ) , importPath )
}
}
if ! fileExists ( importPath ) {
if ! fileExists ( importPath ) {
importPath = ""
importPath = ""
@ -241,11 +290,11 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string
targetResourceTemplate := defaultResourceTemplate
targetResourceTemplate := defaultResourceTemplate
fallbackTmplPath , err := fallbackWebsiteFileTemplate . Render ( name, providerName )
fallbackTmplPath , err := fallbackWebsiteFileTemplate . Render ( g. providerDir , name, providerName )
if err != nil {
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" , name , err )
}
}
fallbackTmplPath = filepath . Join ( g . websiteTmpDir , g . websiteSourceDir , fallbackTmplPath )
fallbackTmplPath = filepath . Join ( g . TempTemplatesDir ( ) , fallbackTmplPath )
if fileExists ( fallbackTmplPath ) {
if fileExists ( fallbackTmplPath ) {
g . infof ( "resource %q fallback template exists" , name )
g . infof ( "resource %q fallback template exists" , name )
tmplData , err := os . ReadFile ( fallbackTmplPath )
tmplData , err := os . ReadFile ( fallbackTmplPath )
@ -256,7 +305,7 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string
}
}
g . infof ( "generating template for %q" , name )
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 {
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" , name , err )
}
}
@ -270,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 {
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 {
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" , providerName , err )
}
}
tmplPath = filepath . Join ( g . websiteTmpDir , g . websiteSourceDir , tmplPath )
tmplPath = filepath . Join ( g . TempTemplatesDir ( ) , tmplPath )
if fileExists ( tmplPath ) {
if fileExists ( tmplPath ) {
g . infof ( "provider %q template exists, skipping" , providerName )
g . infof ( "provider %q template exists, skipping" , providerName )
return nil
return nil
}
}
for _ , candidate := range websiteStaticCandidateTemplates {
for _ , candidate := range websiteStaticCandidateTemplates {
candidatePath , err := candidate . Render ( providerName)
candidatePath , err := candidate . Render ( g. providerDir , providerName)
if err != nil {
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" , providerName , err )
}
}
candidatePath = filepath . Join ( g . websiteTmpDir , g . websiteSourceDir , candidatePath )
candidatePath = filepath . Join ( g . TempTemplatesDir ( ) , candidatePath )
if fileExists ( candidatePath ) {
if fileExists ( candidatePath ) {
g . infof ( "provider %q static file exists, skipping" , providerName )
g . infof ( "provider %q static file exists, skipping" , providerName )
return nil
return nil
}
}
}
}
examplePath , err := examplesFileTemplate . Render ( providerName)
examplePath , err := examplesFileTemplate . Render ( g. providerDir , providerName)
if err != nil {
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" , providerName , err )
}
}
if examplePath != "" {
if examplePath != "" {
examplePath = filepath . Join ( g . examplesDir , examplePath )
examplePath = filepath . Join ( g . ProviderExamplesDir ( ) , examplePath )
}
}
if ! fileExists ( examplePath ) {
if ! fileExists ( examplePath ) {
examplePath = ""
examplePath = ""
}
}
g . infof ( "generating template for %q" , providerName )
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 {
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" , providerName , err )
}
}
@ -367,7 +416,7 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso
func ( g * generator ) renderStaticWebsite ( providerName string , providerSchema * tfjson . ProviderSchema ) error {
func ( g * generator ) renderStaticWebsite ( providerName string , providerSchema * tfjson . ProviderSchema ) error {
g . infof ( "cleaning rendered website dir" )
g . infof ( "cleaning rendered website dir" )
err := os . RemoveAll ( g . renderedWebsiteDir )
err := os . RemoveAll ( g . ProviderDocsDir ( ) )
if err != nil {
if err != nil {
return err
return err
}
}
@ -382,7 +431,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
return nil
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 {
if err != nil {
return err
return err
}
}
@ -395,7 +444,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
return nil
return nil
}
}
renderedPath := filepath . Join ( g . renderedWebsiteDir , rel )
renderedPath := filepath . Join ( g . ProviderDocsDir ( ) , rel )
err = os . MkdirAll ( filepath . Dir ( renderedPath ) , 0755 )
err = os . MkdirAll ( filepath . Dir ( renderedPath ) , 0755 )
if err != nil {
if err != nil {
return err
return err
@ -424,10 +473,11 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
switch relDir {
switch relDir {
case "data-sources/" :
case "data-sources/" :
resSchema , resName := resourceSchema ( providerSchema . DataSourceSchemas , shortName , relFile )
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 {
if resSchema != nil {
tmpl := resourceTemplate ( tmplData )
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 {
if err != nil {
return fmt . Errorf ( "unable to render data source template %q: %w" , rel , err )
return fmt . Errorf ( "unable to render data source template %q: %w" , rel , err )
}
}
@ -440,12 +490,12 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
g . warnf ( "data source entitled %q, or %q does not exist" , shortName , resName )
g . warnf ( "data source entitled %q, or %q does not exist" , shortName , resName )
case "resources/" :
case "resources/" :
resSchema , resName := resourceSchema ( providerSchema . ResourceSchemas , shortName , relFile )
resSchema , resName := resourceSchema ( providerSchema . ResourceSchemas , shortName , relFile )
exampleFilePath := filepath . Join ( g . examplesDir , "resources" , resName , "resource.tf" )
exampleFilePath := filepath . Join ( g . ProviderExamplesDir ( ) , "resources" , resName , "resource.tf" )
importFilePath := filepath . Join ( g . examplesDir , "resources" , resName , "import.sh" )
importFilePath := filepath . Join ( g . ProviderExamplesDir ( ) , "resources" , resName , "import.sh" )
if resSchema != nil {
if resSchema != nil {
tmpl := resourceTemplate ( tmplData )
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 {
if err != nil {
return fmt . Errorf ( "unable to render resource template %q: %w" , rel , err )
return fmt . Errorf ( "unable to render resource template %q: %w" , rel , err )
}
}
@ -459,8 +509,8 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
case "" : // provider
case "" : // provider
if relFile == "index.md.tmpl" {
if relFile == "index.md.tmpl" {
tmpl := providerTemplate ( tmplData )
tmpl := providerTemplate ( tmplData )
exampleFilePath := filepath . Join ( g . examplesDir , "provider" , "provider.tf" )
exampleFilePath := filepath . Join ( g . ProviderExamplesDir ( ) , "provider" , "provider.tf" )
render , err := tmpl . Render ( providerName, g . renderedProviderName , exampleFilePath , providerSchema . ConfigSchema )
render , err := tmpl . Render ( g. providerDir , providerName, g . renderedProviderName , exampleFilePath , providerSchema . ConfigSchema )
if err != nil {
if err != nil {
return fmt . Errorf ( "unable to render provider template %q: %w" , rel , err )
return fmt . Errorf ( "unable to render provider template %q: %w" , rel , err )
}
}
@ -473,7 +523,7 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
}
}
tmpl := docTemplate ( tmplData )
tmpl := docTemplate ( tmplData )
err = tmpl . Render ( out)
err = tmpl . Render ( g. providerDir , out)
if err != nil {
if err != nil {
return fmt . Errorf ( "unable to render template %q: %w" , rel , err )
return fmt . Errorf ( "unable to render template %q: %w" , rel , err )
}
}
@ -510,6 +560,7 @@ func (g *generator) terraformProviderSchema(ctx context.Context, providerName st
outFile = outFile + ".exe"
outFile = outFile + ".exe"
}
}
buildCmd := exec . Command ( "go" , "build" , "-o" , outFile )
buildCmd := exec . Command ( "go" , "build" , "-o" , outFile )
buildCmd . Dir = g . providerDir
// TODO: constrain env here to make it a little safer?
// TODO: constrain env here to make it a little safer?
_ , err = runCmd ( buildCmd )
_ , err = runCmd ( buildCmd )
if err != nil {
if err != nil {