Skip to content

Commit

Permalink
check: Implement terraform providers schema -json ingress
Browse files Browse the repository at this point in the history
Closes #7
  • Loading branch information
bflad committed Dec 17, 2019
1 parent 5ddf392 commit dcadc71
Show file tree
Hide file tree
Showing 8 changed files with 519 additions and 0 deletions.
6 changes: 6 additions & 0 deletions check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"sort"

"github.com/hashicorp/go-multierror"
tfjson "github.com/hashicorp/terraform-json"
)

const (
Expand All @@ -23,10 +24,15 @@ type CheckOptions struct {
LegacyIndexFile *LegacyIndexFileOptions
LegacyResourceFile *LegacyResourceFileOptions

ProviderName string

RegistryDataSourceFile *RegistryDataSourceFileOptions
RegistryGuideFile *RegistryGuideFileOptions
RegistryIndexFile *RegistryIndexFileOptions
RegistryResourceFile *RegistryResourceFileOptions

SchemaDataSources map[string]*tfjson.Schema
SchemaResources map[string]*tfjson.Schema
}

func NewCheck(opts *CheckOptions) *Check {
Expand Down
143 changes: 143 additions & 0 deletions command/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ package command

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strings"
"text/tabwriter"

"github.com/bflad/tfproviderdocs/check"
tfjson "github.com/hashicorp/terraform-json"
"github.com/mitchellh/cli"
)

Expand All @@ -16,6 +23,8 @@ type CheckCommandConfig struct {
AllowedResourceSubcategories string
LogLevel string
Path string
ProviderName string
ProvidersSchemaJson string
RequireGuideSubcategory bool
RequireResourceSubcategory bool
}
Expand All @@ -31,6 +40,8 @@ func (*CheckCommand) Help() string {
LogLevelFlagHelp(opts)
fmt.Fprintf(opts, CommandHelpOptionFormat, "-allowed-guide-subcategories", "Comma separated list of allowed guide frontmatter subcategories.")
fmt.Fprintf(opts, CommandHelpOptionFormat, "-allowed-resource-subcategories", "Comma separated list of allowed data source and resource frontmatter subcategories.")
fmt.Fprintf(opts, CommandHelpOptionFormat, "-provider-name", "Terraform Provider name. Automatically determined if current working directory or provided path is prefixed with terraform-provider-*.")
fmt.Fprintf(opts, CommandHelpOptionFormat, "-providers-schema-json", "Path to terraform providers schema -json file. Enables enhanced validations.")
fmt.Fprintf(opts, CommandHelpOptionFormat, "-require-guide-subcategory", "Require guide frontmatter subcategory.")
fmt.Fprintf(opts, CommandHelpOptionFormat, "-require-resource-subcategory", "Require data source and resource frontmatter subcategory.")
opts.Flush()
Expand Down Expand Up @@ -58,6 +69,8 @@ func (c *CheckCommand) Run(args []string) int {
LogLevelFlag(flags, &config.LogLevel)
flags.StringVar(&config.AllowedGuideSubcategories, "allowed-guide-subcategories", "", "")
flags.StringVar(&config.AllowedResourceSubcategories, "allowed-resource-subcategories", "", "")
flags.StringVar(&config.ProviderName, "provider-name", "", "")
flags.StringVar(&config.ProvidersSchemaJson, "providers-schema-json", "", "")
flags.BoolVar(&config.RequireGuideSubcategory, "require-guide-subcategory", false, "")
flags.BoolVar(&config.RequireResourceSubcategory, "require-resource-subcategory", false, "")

Expand All @@ -74,6 +87,20 @@ func (c *CheckCommand) Run(args []string) int {

ConfigureLogging(c.Name(), config.LogLevel)

if config.ProviderName == "" {
if config.Path == "" {
config.ProviderName = providerNameFromCurrentDirectory()
} else {
config.ProviderName = providerNameFromPath(config.Path)
}

if config.ProviderName == "" {
log.Printf("[WARN] Unable to determine provider name. Enhanced validations may fail.")
} else {
log.Printf("[DEBUG] Found provider name: %s", config.ProviderName)
}
}

directories, err := check.GetDirectories(config.Path)

if err != nil {
Expand Down Expand Up @@ -129,6 +156,7 @@ func (c *CheckCommand) Run(args []string) int {
RequireSubcategory: config.RequireResourceSubcategory,
},
},
ProviderName: config.ProviderName,
RegistryDataSourceFile: &check.RegistryDataSourceFileOptions{
FileOptions: fileOpts,
FrontMatter: &check.FrontMatterOptions{
Expand All @@ -155,6 +183,26 @@ func (c *CheckCommand) Run(args []string) int {
},
}

if config.ProvidersSchemaJson != "" {
ps, err := providerSchemas(config.ProvidersSchemaJson)

if err != nil {
c.Ui.Error(fmt.Sprintf("Error enabling Terraform Provider schema checks: %s", err))
return 1
}

if config.ProviderName == "" {
msg := `Unknown provider name for enabling Terraform Provider schema checks.
Check that the current working directory or provided path is prefixed with terraform-provider-*.`
c.Ui.Error(msg)
return 1
}

checkOpts.SchemaDataSources = providerSchemasDataSources(ps, config.ProviderName)
checkOpts.SchemaResources = providerSchemasResources(ps, config.ProviderName)
}

if err := check.NewCheck(checkOpts).Run(directories); err != nil {
c.Ui.Error(fmt.Sprintf("Error checking Terraform Provider documentation: %s", err))
return 1
Expand All @@ -166,3 +214,98 @@ func (c *CheckCommand) Run(args []string) int {
func (c *CheckCommand) Synopsis() string {
return "Checks Terraform Provider documentation"
}

func providerNameFromCurrentDirectory() string {
path, _ := os.Getwd()

return providerNameFromPath(path)
}

func providerNameFromPath(path string) string {
base := filepath.Base(path)

if strings.ContainsAny(base, "./") {
return ""
}

if !strings.HasPrefix(base, "terraform-provider-") {
return ""
}

return strings.TrimPrefix(base, "terraform-provider-")
}

// providerSchemas reads, parses, and validates a provided terraform provider schema -json path.
func providerSchemas(path string) (*tfjson.ProviderSchemas, error) {
log.Printf("[DEBUG] Loading providers schema JSON file: %s", path)

content, err := ioutil.ReadFile(path)

if err != nil {
return nil, fmt.Errorf("error reading providers schema JSON file (%s): %w", path, err)
}

var ps tfjson.ProviderSchemas

if err := json.Unmarshal(content, &ps); err != nil {
return nil, fmt.Errorf("error parsing providers schema JSON file (%s): %w", path, err)
}

if err := ps.Validate(); err != nil {
return nil, fmt.Errorf("error validating providers schema JSON file (%s): %w", path, err)
}

return &ps, nil
}

// providerSchemasDataSources returns all data sources from a terraform providers schema -json provider.
func providerSchemasDataSources(ps *tfjson.ProviderSchemas, providerName string) map[string]*tfjson.Schema {
if ps == nil || providerName == "" {
return nil
}

provider, ok := ps.Schemas[providerName]

if !ok {
log.Printf("[WARN] Provider name (%s) not found in provider schema", providerName)
return nil
}

dataSources := make([]string, 0, len(provider.DataSourceSchemas))

for name := range provider.DataSourceSchemas {
dataSources = append(dataSources, name)
}

sort.Strings(dataSources)

log.Printf("[DEBUG] Found provider schema data sources: %v", dataSources)

return provider.DataSourceSchemas
}

// providerSchemasResources returns all resources from a terraform providers schema -json provider.
func providerSchemasResources(ps *tfjson.ProviderSchemas, providerName string) map[string]*tfjson.Schema {
if ps == nil || providerName == "" {
return nil
}

provider, ok := ps.Schemas[providerName]

if !ok {
log.Printf("[WARN] Provider name (%s) not found in provider schema", providerName)
return nil
}

resources := make([]string, 0, len(provider.ResourceSchemas))

for name := range provider.ResourceSchemas {
resources = append(resources, name)
}

sort.Strings(resources)

log.Printf("[DEBUG] Found provider schema data sources: %v", resources)

return provider.ResourceSchemas
}
Loading

0 comments on commit dcadc71

Please sign in to comment.