Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement more of the CLI #12

Merged
merged 3 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 104 additions & 47 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,21 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"

"github.com/HewlettPackard/terraschema/pkg/jsonschema"
)

// wanted behaviour:
// - disallow-additional-properties: disallow additional properties in schema (default is false)
// - overwrite: overwrite an existing file (default is false for safety reasons)
// - stdout: suppress errors and output schema to stdout (generally not recommended)
// - output: file, default is ./schema.json. Allow creation of directories.
// - input: folder, default is .
// - allow-empty: if no variables are found, print empty schema and exit with 0

var (
disallowAdditionalProperties bool
overwrite bool
allowEmpty bool
requireAll bool
outputStdOut bool
output string
input string

errReturned error
inputPath string
outputPath string
)

// rootCmd is the base command for terraschema
Expand All @@ -42,27 +33,24 @@ var rootCmd = &cobra.Command{
"them to a schema which complies with JSON Schema Draft-07.\nThe default behaviour is to scan " +
"the current directory and output a schema file called 'schema.json' in the same location. " +
"\nFor more information see https://github.com/HewlettPackard/terraschema.",
Run: runCommand,
PostRun: func(cmd *cobra.Command, args []string) {
if errReturned != nil {
fmt.Printf("error: %v\n", errReturned)
os.Exit(1)
}
},
PreRunE: preRunCommand,
RunE: runCommand,
SilenceUsage: true,
}

// Execute command with the following flags:
// - disallow-additional-properties: disallow additional properties in schema (default is false)
// - overwrite: overwrite an existing file (default is false for safety reasons)
// - stdout: suppress errors and output schema to stdout (generally not recommended)
// - output: file, default is ./schema.json. Allow creation of directories.
// - input: folder, default is .
// - allow-empty: if no variables are found, print empty schema and exit with 0
// - require-all: require all variables to be present in the schema, even if a default value is specified
func Execute() error {
return rootCmd.Execute()
}

func init() {
// TODO: implement
rootCmd.Flags().BoolVar(&overwrite, "overwrite", false, "allow overwriting an existing file")
// TODO: implement
rootCmd.Flags().BoolVar(&outputStdOut, "stdout", false,
"output JSON Schema content to stdout instead of a file and disable error output",
)

rootCmd.Flags().BoolVar(&disallowAdditionalProperties, "disallow-additional-properties", false,
"set additionalProperties to false in the JSON Schema and in nested objects",
)
Expand All @@ -72,57 +60,126 @@ func init() {
)

rootCmd.Flags().BoolVar(&requireAll, "require-all", false,
"set all variables to be 'required' in the JSON Schema, even if a default value is specified",
"set all variables to be 'required' in the JSON Schema, even if a default\n"+
"value is specified",
)

rootCmd.Flags().StringVarP(&input, "input", "i", ".",
rootCmd.Flags().StringVarP(&inputPath, "input", "i", ".",
"input folder containing a Terraform module",
)

// TODO: implement
rootCmd.Flags().StringVarP(&output, "output", "o", "schema.json",
rootCmd.Flags().StringVarP(&outputPath, "output", "o", "schema.json",
"output path for the JSON Schema file",
)

rootCmd.Flags().BoolVar(&overwrite, "overwrite", false,
"overwrite an existing schema file",
)

rootCmd.Flags().BoolVar(&outputStdOut, "stdout", false,
"output schema content to stdout instead of a file and disable any other logging\n"+
"unless an error occurs. Overrides 'debug' and 'output.",
)

rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
_ = rootCmd.Usage()

return err
})
}

func runCommand(cmd *cobra.Command, args []string) {
path, err := filepath.Abs(input) // absolute path
func preRunCommand(cmd *cobra.Command, args []string) error {
err := inputFileChecks()
if err != nil {
errReturned = fmt.Errorf("could not get absolute path for %q: %w", input, err)

return
return err
}
if !outputStdOut {
return outputFileChecks()
}

return nil
}

folder, err := os.Stat(path)
func inputFileChecks() error {
_, err := filepath.Abs(inputPath) // absolute path
if err != nil {
errReturned = fmt.Errorf("could not access directory %q: %w", path, err)
return fmt.Errorf("could not get absolute path for %q: %w", inputPath, err)
}

return
folder, err := os.Stat(inputPath)
if err != nil {
return fmt.Errorf("could not access directory %q: %w", inputPath, err)
}

if !folder.IsDir() {
errReturned = fmt.Errorf("input %q is not a directory", path)
return fmt.Errorf("input path %q is not a directory", inputPath)
}

return nil
}

func outputFileChecks() error {
_, err := filepath.Abs(outputPath) // absolute path
if err != nil {
return fmt.Errorf("could not get absolute path for %q: %w", outputPath, err)
}

outputFile, err := os.Stat(outputPath)
if err == nil {
if overwrite {
if outputFile.IsDir() {
return fmt.Errorf(
"output path %q is an existing directory, please specify a file path",
outputPath,
)
}
} else {
return fmt.Errorf("output path %q already exists, use --overwrite to overwrite", outputPath)
}
}

return
if !strings.HasSuffix(outputPath, ".json") {
fmt.Printf("Warning: output path %q does not have a .json extension, continuing\n", outputPath)
}

output, err := jsonschema.CreateSchema(path, jsonschema.CreateSchemaOptions{
return nil
}

func runCommand(cmd *cobra.Command, args []string) error {
// TODO: suppress other printing while outputting to stdout (probably with slog)
outputMap, err := jsonschema.CreateSchema(inputPath, jsonschema.CreateSchemaOptions{
RequireAll: requireAll,
AllowAdditionalProperties: !disallowAdditionalProperties,
AllowEmpty: allowEmpty,
})
if err != nil {
errReturned = fmt.Errorf("error creating schema: %w", err)
return fmt.Errorf("error creating schema: %w", err)
}

return
jsonOutput, err := json.MarshalIndent(outputMap, "", "\t")
if err != nil {
return fmt.Errorf("error marshalling schema: %w", err)
}

jsonOutput, err := json.MarshalIndent(output, "", " ")
if outputStdOut {
fmt.Println(string(jsonOutput))

return nil
}

// create folder path for output file if it doesn't exist
err = os.MkdirAll(filepath.Dir(outputPath), 0o755)
if err != nil {
errReturned = fmt.Errorf("error marshalling schema: %w", err)
return fmt.Errorf("error creating folder for %q: %w", outputPath, err)
}

return
// Create a file with 644 file permissions. If this causes issues, we can use 600 instead later.
//nolint:gosec
err = os.WriteFile(outputPath, jsonOutput, 0o644)
if err != nil {
return fmt.Errorf("error writing schema to %q: %w", outputPath, err)
}
fmt.Printf("Schema written to %q\n", outputPath)

fmt.Println(string(jsonOutput))
return nil
}
9 changes: 1 addition & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,9 @@
package main

import (
"fmt"
"os"

"github.com/HewlettPackard/terraschema/cmd"
)

func main() {
err := cmd.Execute()
if err != nil {
fmt.Printf("exited with error: %v\n", err)
os.Exit(1)
}
_ = cmd.Execute()
}