package tfexec import ( "context" "fmt" "io" "os/exec" "path/filepath" "strings" ) type formatConfig struct { recursive bool dir string } var defaultFormatConfig = formatConfig{ recursive: false, } type FormatOption interface { configureFormat(*formatConfig) } func (opt *RecursiveOption) configureFormat(conf *formatConfig) { conf.recursive = opt.recursive } func (opt *DirOption) configureFormat(conf *formatConfig) { conf.dir = opt.path } // FormatString formats a passed string, given a path to Terraform. func FormatString(ctx context.Context, execPath string, content string) (string, error) { tf, err := NewTerraform(filepath.Dir(execPath), execPath) if err != nil { return "", err } return tf.FormatString(ctx, content) } // FormatString formats a passed string. func (tf *Terraform) FormatString(ctx context.Context, content string) (string, error) { in := strings.NewReader(content) var outBuf strings.Builder err := tf.Format(ctx, in, &outBuf) if err != nil { return "", err } return outBuf.String(), nil } // Format performs formatting on the unformatted io.Reader (as stdin to the CLI) and returns // the formatted result on the formatted io.Writer. func (tf *Terraform) Format(ctx context.Context, unformatted io.Reader, formatted io.Writer) error { cmd, err := tf.formatCmd(ctx, nil, Dir("-")) if err != nil { return err } cmd.Stdin = unformatted cmd.Stdout = mergeWriters(cmd.Stdout, formatted) return tf.runTerraformCmd(ctx, cmd) } // FormatWrite attempts to format and modify all config files in the working or selected (via DirOption) directory. func (tf *Terraform) FormatWrite(ctx context.Context, opts ...FormatOption) error { for _, o := range opts { switch o := o.(type) { case *DirOption: if o.path == "-" { return fmt.Errorf("a path of \"-\" is not supported for this method, please use FormatString") } } } cmd, err := tf.formatCmd(ctx, []string{"-write=true", "-list=false", "-diff=false"}, opts...) if err != nil { return err } return tf.runTerraformCmd(ctx, cmd) } // FormatCheck returns true if the config files in the working or selected (via DirOption) directory are already formatted. func (tf *Terraform) FormatCheck(ctx context.Context, opts ...FormatOption) (bool, []string, error) { for _, o := range opts { switch o := o.(type) { case *DirOption: if o.path == "-" { return false, nil, fmt.Errorf("a path of \"-\" is not supported for this method, please use FormatString") } } } cmd, err := tf.formatCmd(ctx, []string{"-write=false", "-list=true", "-diff=false", "-check=true"}, opts...) if err != nil { return false, nil, err } var outBuf strings.Builder cmd.Stdout = mergeWriters(cmd.Stdout, &outBuf) err = tf.runTerraformCmd(ctx, cmd) if err == nil { return true, nil, nil } if cmd.ProcessState.ExitCode() == 3 { // unformatted, parse the file list files := []string{} lines := strings.Split(strings.Replace(outBuf.String(), "\r\n", "\n", -1), "\n") for _, l := range lines { l = strings.TrimSpace(l) if l == "" { continue } files = append(files, l) } return false, files, nil } return false, nil, err } func (tf *Terraform) formatCmd(ctx context.Context, args []string, opts ...FormatOption) (*exec.Cmd, error) { err := tf.compatible(ctx, tf0_7_7, nil) if err != nil { return nil, fmt.Errorf("fmt was first introduced in Terraform 0.7.7: %w", err) } c := defaultFormatConfig for _, o := range opts { switch o.(type) { case *RecursiveOption: err := tf.compatible(ctx, tf0_12_0, nil) if err != nil { return nil, fmt.Errorf("-recursive was added to fmt in Terraform 0.12: %w", err) } } o.configureFormat(&c) } args = append([]string{"fmt", "-no-color"}, args...) if c.recursive { args = append(args, "-recursive") } if c.dir != "" { args = append(args, c.dir) } return tf.buildTerraformCmd(ctx, nil, args...), nil }