Skip to content

Commit

Permalink
adjust for terraform fmt and review
Browse files Browse the repository at this point in the history
  • Loading branch information
Trojan295 authored and tgross committed Oct 6, 2022
1 parent b1192bf commit 7e8b4ff
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 132 deletions.
3 changes: 3 additions & 0 deletions .changelog/14779.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
cli: add nomad fmt to the CLI
```
254 changes: 173 additions & 81 deletions command/fmt.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package command

import (
"context"
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
Expand All @@ -11,57 +12,34 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/posener/complete"
"golang.org/x/crypto/ssh/terminal"
)

const (
stdinArg = "-"
stdinPath = "<stdin>"
)

type FormatCommand struct {
Meta

diagWr hcl.DiagnosticWriter
parser *hclparse.Parser
files []string
}

type FormatArgs struct {
Paths []string
Check bool
Overwrite bool
}

func (f *FormatCommand) RunContext(ctx context.Context, args *FormatArgs) int {
var errs error
parser *hclparse.Parser
hclDiags hcl.Diagnostics

f.parser = hclparse.NewParser()

color := terminal.IsTerminal(int(os.Stderr.Fd()))
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
w = 80
}

f.diagWr = hcl.NewDiagnosticTextWriter(os.Stderr, f.parser.Files(), uint(w), color)

if err := f.findFiles(args); err != nil {
f.Ui.Error("Failed to find files to format:")
f.Ui.Error(err.Error())
f.Ui.Error(commandErrorText(f))
return 1
}

for _, file := range f.files {
if err := f.processFile(file, args); err != nil {
errs = multierror.Append(errs, err)
}
}
errs *multierror.Error

if errs != nil {
f.Ui.Error(errs.Error())
return 1
}
list bool
check bool
recursive bool
write bool
paths []string

return 0
stdin io.Reader
}

func (*FormatCommand) Help() string {
Expand All @@ -71,15 +49,24 @@ Usage: nomad fmt [flags] paths ...
Formats Nomad agent configuration and job file to a canonical format.
If a path is a directory, it will recursively format all files
with .nomad and .hcl extensions in the directory.
If you provide a single dash (-) as argument, fmt will read from standard
input (STDIN) and output the processed output to standard output (STDOUT).
Format Options:
-list=false
Don't list the files, which contain formatting inconsistencies.
-check
Check if the files are valid HCL files. If not, exit status of the command
will be 1 and the incorrect files will not be formatted.
-overwrite=false
Print the formatted files to stdout, instead of overwriting the file content.
-write=false
Don't overwrite the input files.
-recursive
Process also files in subdirectories. By default only the given (or current) directory is processed.
`

return strings.TrimSpace(helpText)
Expand All @@ -95,83 +82,188 @@ func (*FormatCommand) AutocompleteArgs() complete.Predictor {

func (*FormatCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-check": complete.PredictNothing,
"-overwrite=false": complete.PredictNothing,
"-check": complete.PredictNothing,
"-write": complete.PredictNothing,
"-list": complete.PredictNothing,
"-recursive": complete.PredictNothing,
}
}

func (f *FormatCommand) Name() string { return "fmt" }

func (f *FormatCommand) Run(args []string) int {
ctx := context.Background()

fmtArgs := &FormatArgs{}
if f.stdin == nil {
f.stdin = os.Stdin
}

flags := f.Meta.FlagSet(f.Name(), FlagSetClient)
flags.Usage = func() { f.Ui.Output(f.Help()) }
flags.BoolVar(&fmtArgs.Check, "check", false, "")
flags.BoolVar(&fmtArgs.Overwrite, "overwrite", true, "")
flags.BoolVar(&f.check, "check", false, "")
flags.BoolVar(&f.write, "write", true, "")
flags.BoolVar(&f.list, "list", true, "")
flags.BoolVar(&f.recursive, "recursive", false, "")

if err := flags.Parse(args); err != nil {
return 1
}

fmtArgs.Paths = flags.Args()
f.parser = hclparse.NewParser()

return f.RunContext(ctx, fmtArgs)
color := terminal.IsTerminal(int(os.Stderr.Fd()))
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err != nil {
w = 80
}

f.diagWr = hcl.NewDiagnosticTextWriter(os.Stderr, f.parser.Files(), uint(w), color)

if len(flags.Args()) == 0 {
f.paths = []string{"."}
} else if flags.Args()[0] == stdinArg {
f.write = false
f.list = false
} else {
f.paths = flags.Args()
}

f.fmt()

if f.hclDiags.HasErrors() {
f.diagWr.WriteDiagnostics(f.hclDiags)
}

if f.errs != nil {
f.Ui.Error(f.errs.Error())
f.Ui.Error(commandErrorText(f))
}

if f.hclDiags.HasErrors() || f.errs != nil {
return 1
}

return 0
}

func (f *FormatCommand) findFiles(args *FormatArgs) error {
for _, path := range args.Paths {
fi, err := os.Stat(path)
func (f *FormatCommand) fmt() {
if len(f.paths) == 0 {
f.processFile(stdinPath, f.stdin)
return
}

for _, path := range f.paths {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("while stating %s: %w", path, err)
f.appendError(fmt.Errorf("No file or directory at %s", path))
continue
}

if !fi.IsDir() {
f.files = append(f.files, path)
if info.IsDir() {
f.processDir(path)
} else {
if isNomadFile(info) {
fp, err := os.Open(path)
if err != nil {
f.appendError(fmt.Errorf("Failed to open file %s: %w", path, err))
continue
}

f.processFile(path, fp)

fp.Close()
} else {
f.appendError(fmt.Errorf("Only .nomad and .hcl files can be processed using nomad fmt"))
continue
}
}
}
}

func (f *FormatCommand) processDir(path string) {
entries, err := os.ReadDir(path)
if err != nil {
f.appendError(fmt.Errorf("Failed to list directory %s", path))
return
}

for _, entry := range entries {
name := entry.Name()
subpath := filepath.Join(path, name)

if entry.IsDir() {
if f.recursive {
f.processDir(subpath)
}

continue
}

info, err := entry.Info()
if err != nil {
f.appendError(err)
continue
}

if err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if isNomadFile(info) {
fp, err := os.Open(subpath)
if err != nil {
return err
f.appendError(fmt.Errorf("Failed to open file %s: %w", path, err))
continue
}

if !d.IsDir() && (filepath.Ext(path) == ".nomad" || filepath.Ext(path) == ".hcl") {
f.files = append(f.files, path)
}
f.processFile(subpath, fp)

return nil
}); err != nil {
return fmt.Errorf("while walking through %s: %w", path, err)
fp.Close()
}
}

return nil
}

func (f *FormatCommand) processFile(filepath string, args *FormatArgs) error {
bytes, err := os.ReadFile(filepath)
func (f *FormatCommand) processFile(path string, r io.Reader) {
src, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("while reading %s: %w", filepath, err)
f.appendError(fmt.Errorf("Failed to read file %s: %w", path, err))
return
}

if args.Check {
_, diags := f.parser.ParseHCL(bytes, filepath)
f.diagWr.WriteDiagnostics(diags)
if diags.HasErrors() {
return fmt.Errorf("error in HCL syntax: %s", filepath)
}
f.parser.AddFile(path, &hcl.File{
Body: hcl.EmptyBody(),
Bytes: src,
})

_, syntaxDiags := hclsyntax.ParseConfig(src, path, hcl.InitialPos)
if syntaxDiags.HasErrors() {
f.hclDiags = append(f.hclDiags, syntaxDiags...)
return
}
formattedFile, diags := hclwrite.ParseConfig(src, path, hcl.InitialPos)
if diags.HasErrors() {
f.hclDiags = append(f.hclDiags, diags...)
return
}

fmtBytes := hclwrite.Format(bytes)
out := formattedFile.Bytes()

if args.Overwrite {
os.WriteFile(filepath, fmtBytes, 0644)
} else {
f.Ui.Output(string(fmtBytes))
if !bytes.Equal(src, out) {
if f.list {
f.Ui.Output(path)
}

if f.write {
if err := os.WriteFile(path, out, 0644); err != nil {
f.appendError(fmt.Errorf("Failed to write file %s: %w", path, err))
return
}
}
}

return nil
if !f.list && !f.write {
f.Ui.Output(string(out))
}
}

func isNomadFile(file fs.FileInfo) bool {
return !file.IsDir() && (filepath.Ext(file.Name()) == ".nomad" || filepath.Ext(file.Name()) == ".hcl")
}

func (f *FormatCommand) appendError(err error) {
f.errs = multierror.Append(f.errs, err)
}
Loading

0 comments on commit 7e8b4ff

Please sign in to comment.