0
0
mirror of https://github.com/thegeeklab/wp-opentofu.git synced 2024-11-24 13:20:39 +00:00

refactor: switch to plugin Cmd and add tests (#44)

This commit is contained in:
Robert Kaussow 2024-05-12 12:03:10 +02:00 committed by GitHub
parent 2eda2b50fc
commit dcc53a341d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 780 additions and 231 deletions

4
go.mod
View File

@ -5,6 +5,7 @@ go 1.22
require ( require (
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/rs/zerolog v1.32.0 github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.9.0
github.com/thegeeklab/wp-plugin-go/v2 v2.3.1 github.com/thegeeklab/wp-plugin-go/v2 v2.3.1
github.com/urfave/cli/v2 v2.27.2 github.com/urfave/cli/v2 v2.27.2
golang.org/x/sys v0.20.0 golang.org/x/sys v0.20.0
@ -14,6 +15,7 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.1.1 // indirect github.com/google/uuid v1.1.1 // indirect
github.com/huandu/xstrings v1.3.3 // indirect github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.11 // indirect github.com/imdario/mergo v0.3.11 // indirect
@ -22,10 +24,12 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cast v1.3.1 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

1
go.sum
View File

@ -90,6 +90,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=

View File

@ -8,7 +8,9 @@ import (
"io" "io"
"os" "os"
"github.com/thegeeklab/wp-opentofu/tofu"
"github.com/thegeeklab/wp-plugin-go/v2/trace" "github.com/thegeeklab/wp-plugin-go/v2/trace"
"github.com/thegeeklab/wp-plugin-go/v2/types"
) )
var ( var (
@ -43,21 +45,21 @@ func (p *Plugin) run(ctx context.Context) error {
func (p *Plugin) FlagsFromContext() error { func (p *Plugin) FlagsFromContext() error {
if p.Context.String("init-option") != "" { if p.Context.String("init-option") != "" {
initOptions := InitOptions{} initOptions := tofu.InitOptions{}
if err := json.Unmarshal([]byte(p.Context.String("init-option")), &initOptions); err != nil { if err := json.Unmarshal([]byte(p.Context.String("init-option")), &initOptions); err != nil {
return fmt.Errorf("cannot unmarshal init_option: %w", err) return fmt.Errorf("cannot unmarshal init_option: %w", err)
} }
p.Settings.InitOptions = initOptions p.Settings.Tofu.InitOptions = initOptions
} }
if p.Context.String("fmt-option") != "" { if p.Context.String("fmt-option") != "" {
fmtOptions := FmtOptions{} fmtOptions := tofu.FmtOptions{}
if err := json.Unmarshal([]byte(p.Context.String("fmt-option")), &fmtOptions); err != nil { if err := json.Unmarshal([]byte(p.Context.String("fmt-option")), &fmtOptions); err != nil {
return fmt.Errorf("cannot unmarshal fmt_option: %w", err) return fmt.Errorf("cannot unmarshal fmt_option: %w", err)
} }
p.Settings.FmtOptions = fmtOptions p.Settings.Tofu.FmtOptions = fmtOptions
} }
return nil return nil
@ -70,9 +72,9 @@ func (p *Plugin) Validate() error {
p.Settings.DataDir = value p.Settings.DataDir = value
} }
p.Settings.OutFile = "plan.tfout" p.Settings.Tofu.OutFile = "plan.tfout"
if p.Settings.DataDir == ".terraform" { if p.Settings.DataDir == ".terraform" {
p.Settings.OutFile = fmt.Sprintf("%s.plan.tfout", p.Settings.DataDir) p.Settings.Tofu.OutFile = fmt.Sprintf("%s.plan.tfout", p.Settings.DataDir)
} }
return nil return nil
@ -80,8 +82,8 @@ func (p *Plugin) Validate() error {
// Execute provides the implementation of the plugin. // Execute provides the implementation of the plugin.
func (p *Plugin) Execute() error { func (p *Plugin) Execute() error {
batchCmd := make([]*Cmd, 0) batchCmd := make([]*types.Cmd, 0)
batchCmd = append(batchCmd, p.versionCommand()) batchCmd = append(batchCmd, p.Settings.Tofu.Version())
if p.Settings.TofuVersion != "" { if p.Settings.TofuVersion != "" {
err := installPackage(p.Plugin.Network.Context, p.Plugin.Network.Client, p.Settings.TofuVersion, maxDecompressionSize) err := installPackage(p.Plugin.Network.Context, p.Plugin.Network.Client, p.Settings.TofuVersion, maxDecompressionSize)
@ -90,29 +92,29 @@ func (p *Plugin) Execute() error {
} }
} }
batchCmd = append(batchCmd, p.initCommand()) batchCmd = append(batchCmd, p.Settings.Tofu.Init())
batchCmd = append(batchCmd, p.getModulesCommand()) batchCmd = append(batchCmd, p.Settings.Tofu.GetModules())
for _, action := range p.Settings.Action.Value() { for _, action := range p.Settings.Action.Value() {
switch action { switch action {
case "fmt": case "fmt":
batchCmd = append(batchCmd, p.fmtCommand()) batchCmd = append(batchCmd, p.Settings.Tofu.Fmt())
case "validate": case "validate":
batchCmd = append(batchCmd, p.validateCommand()) batchCmd = append(batchCmd, p.Settings.Tofu.Validate())
case "plan": case "plan":
batchCmd = append(batchCmd, p.planCommand(false)) batchCmd = append(batchCmd, p.Settings.Tofu.Plan(false))
case "plan-destroy": case "plan-destroy":
batchCmd = append(batchCmd, p.planCommand(true)) batchCmd = append(batchCmd, p.Settings.Tofu.Plan(true))
case "apply": case "apply":
batchCmd = append(batchCmd, p.applyCommand()) batchCmd = append(batchCmd, p.Settings.Tofu.Apply())
case "destroy": case "destroy":
batchCmd = append(batchCmd, p.destroyCommand()) batchCmd = append(batchCmd, p.Settings.Tofu.Destroy())
default: default:
return fmt.Errorf("%w: %s", ErrActionUnknown, action) return fmt.Errorf("%w: %s", ErrActionUnknown, action)
} }
} }
if err := deleteDir(p.Settings.DataDir); err != nil { if err := os.RemoveAll(p.Settings.DataDir); err != nil {
return err return err
} }
@ -136,5 +138,5 @@ func (p *Plugin) Execute() error {
} }
} }
return deleteDir(p.Settings.DataDir) return os.RemoveAll(p.Settings.DataDir)
} }

View File

@ -3,9 +3,9 @@ package plugin
import ( import (
"fmt" "fmt"
"github.com/thegeeklab/wp-opentofu/tofu"
wp "github.com/thegeeklab/wp-plugin-go/v2/plugin" wp "github.com/thegeeklab/wp-plugin-go/v2/plugin"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/sys/execabs"
) )
//go:generate go run ../internal/docs/main.go -output=../docs/data/data-raw.yaml //go:generate go run ../internal/docs/main.go -output=../docs/data/data-raw.yaml
@ -19,38 +19,10 @@ type Plugin struct {
// Settings for the Plugin. // Settings for the Plugin.
type Settings struct { type Settings struct {
Action cli.StringSlice Action cli.StringSlice
TofuVersion string
InitOptions InitOptions
FmtOptions FmtOptions
RootDir string RootDir string
DataDir string DataDir string
OutFile string TofuVersion string
Parallelism int Tofu tofu.Tofu
Targets cli.StringSlice
Refresh bool
NoLog bool
}
// InitOptions include options for the OpenTofu init command.
type InitOptions struct {
BackendConfig []string `json:"backend-config"`
Lock *bool `json:"lock"`
LockTimeout string `json:"lock-timeout"`
}
// FmtOptions fmt options for the OpenTofu fmt command.
type FmtOptions struct {
List *bool `json:"list"`
Write *bool `json:"write"`
Diff *bool `json:"diff"`
Check *bool `json:"check"`
}
type Cmd struct {
*execabs.Cmd
Private bool
} }
func New(e wp.ExecuteFunc, build ...string) *Plugin { func New(e wp.ExecuteFunc, build ...string) *Plugin {
@ -123,14 +95,14 @@ func Flags(settings *Settings, category string) []cli.Flag {
Name: "no-log", Name: "no-log",
Usage: "suppress tofu command output for `plan`, `apply` and `destroy` action", Usage: "suppress tofu command output for `plan`, `apply` and `destroy` action",
EnvVars: []string{"PLUGIN_NO_LOG"}, EnvVars: []string{"PLUGIN_NO_LOG"},
Destination: &settings.NoLog, Destination: &settings.Tofu.NoLog,
Category: category, Category: category,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "targets", Name: "targets",
Usage: "targets to run `plan` or `apply` action on", Usage: "targets to run `plan` or `apply` action on",
EnvVars: []string{"PLUGIN_TARGETS"}, EnvVars: []string{"PLUGIN_TARGETS"},
Destination: &settings.Targets, Destination: &settings.Tofu.Targets,
Category: category, Category: category,
}, },
&cli.StringFlag{ &cli.StringFlag{
@ -144,7 +116,7 @@ func Flags(settings *Settings, category string) []cli.Flag {
Name: "refresh", Name: "refresh",
Usage: "enables refreshing of the state before `plan` and `apply` commands", Usage: "enables refreshing of the state before `plan` and `apply` commands",
EnvVars: []string{"PLUGIN_REFRESH"}, EnvVars: []string{"PLUGIN_REFRESH"},
Destination: &settings.Refresh, Destination: &settings.Tofu.Refresh,
Value: true, Value: true,
Category: category, Category: category,
}, },

View File

@ -1,172 +0,0 @@
package plugin
import (
"fmt"
"golang.org/x/sys/execabs"
)
const (
tofuBin = "/usr/local/bin/tofu"
)
func (p *Plugin) versionCommand() *Cmd {
return &Cmd{
Cmd: execabs.Command(tofuBin, "version"),
Private: p.Settings.NoLog,
}
}
func (p *Plugin) initCommand() *Cmd {
args := []string{
"init",
}
for _, v := range p.Settings.InitOptions.BackendConfig {
args = append(args, fmt.Sprintf("-backend-config=%s", v))
}
// Fail tofu execution on prompt
args = append(args, "-input=false")
return &Cmd{
Cmd: execabs.Command(tofuBin, args...),
}
}
func (p *Plugin) getModulesCommand() *Cmd {
return &Cmd{
Cmd: execabs.Command(tofuBin, "get"),
}
}
func (p *Plugin) validateCommand() *Cmd {
return &Cmd{
Cmd: execabs.Command(tofuBin, "validate"),
}
}
func (p *Plugin) fmtCommand() *Cmd {
args := []string{
"fmt",
}
if p.Settings.FmtOptions.List != nil {
args = append(args, fmt.Sprintf("-list=%t", *p.Settings.FmtOptions.List))
}
if p.Settings.FmtOptions.Write != nil {
args = append(args, fmt.Sprintf("-write=%t", *p.Settings.FmtOptions.Write))
}
if p.Settings.FmtOptions.Diff != nil {
args = append(args, fmt.Sprintf("-diff=%t", *p.Settings.FmtOptions.Diff))
}
if p.Settings.FmtOptions.Check != nil {
args = append(args, fmt.Sprintf("-check=%t", *p.Settings.FmtOptions.Check))
}
return &Cmd{
Cmd: execabs.Command(tofuBin, args...),
}
}
func (p *Plugin) planCommand(destroy bool) *Cmd {
args := []string{
"plan",
}
if destroy {
args = append(args, "-destroy")
} else {
args = append(args, fmt.Sprintf("-out=%s", p.Settings.OutFile))
}
for _, value := range p.Settings.Targets.Value() {
args = append(args, "--target", value)
}
if p.Settings.Parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", p.Settings.Parallelism))
}
if p.Settings.InitOptions.Lock != nil {
args = append(args, fmt.Sprintf("-lock=%t", *p.Settings.InitOptions.Lock))
}
if p.Settings.InitOptions.LockTimeout != "" {
args = append(args, fmt.Sprintf("-lock-timeout=%s", p.Settings.InitOptions.LockTimeout))
}
if !p.Settings.Refresh {
args = append(args, "-refresh=false")
}
return &Cmd{
Cmd: execabs.Command(tofuBin, args...),
Private: p.Settings.NoLog,
}
}
func (p *Plugin) applyCommand() *Cmd {
args := []string{
"apply",
}
for _, v := range p.Settings.Targets.Value() {
args = append(args, "--target", v)
}
if p.Settings.Parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", p.Settings.Parallelism))
}
if p.Settings.InitOptions.Lock != nil {
args = append(args, fmt.Sprintf("-lock=%t", *p.Settings.InitOptions.Lock))
}
if p.Settings.InitOptions.LockTimeout != "" {
args = append(args, fmt.Sprintf("-lock-timeout=%s", p.Settings.InitOptions.LockTimeout))
}
if !p.Settings.Refresh {
args = append(args, "-refresh=false")
}
args = append(args, p.Settings.OutFile)
return &Cmd{
Cmd: execabs.Command(tofuBin, args...),
Private: p.Settings.NoLog,
}
}
func (p *Plugin) destroyCommand() *Cmd {
args := []string{
"destroy",
}
for _, v := range p.Settings.Targets.Value() {
args = append(args, fmt.Sprintf("-target=%s", v))
}
if p.Settings.Parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", p.Settings.Parallelism))
}
if p.Settings.InitOptions.Lock != nil {
args = append(args, fmt.Sprintf("-lock=%t", *p.Settings.InitOptions.Lock))
}
if p.Settings.InitOptions.LockTimeout != "" {
args = append(args, fmt.Sprintf("-lock-timeout=%s", p.Settings.InitOptions.LockTimeout))
}
args = append(args, "-auto-approve")
return &Cmd{
Cmd: execabs.Command(tofuBin, args...),
Private: p.Settings.NoLog,
}
}

View File

@ -13,6 +13,7 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/thegeeklab/wp-opentofu/tofu"
) )
func installPackage(ctx context.Context, client *http.Client, version string, maxSize int64) error { func installPackage(ctx context.Context, client *http.Client, version string, maxSize int64) error {
@ -34,7 +35,7 @@ func installPackage(ctx context.Context, client *http.Client, version string, ma
} }
defer func() { defer func() {
_ = deleteDir(tmpdir) _ = os.RemoveAll(tmpdir)
}() }()
log.Debug(). log.Debug().
@ -51,7 +52,7 @@ func installPackage(ctx context.Context, client *http.Client, version string, ma
return fmt.Errorf("failed to unzip: %w", err) return fmt.Errorf("failed to unzip: %w", err)
} }
if err := os.Rename(filepath.Join(tmpdir, "tofu"), tofuBin); err != nil { if err := os.Rename(filepath.Join(tmpdir, "tofu"), tofu.TofuBin); err != nil {
return fmt.Errorf("failed to rename: %w", err) return fmt.Errorf("failed to rename: %w", err)
} }
@ -169,7 +170,3 @@ func sanitizeArchivePath(d, t string) (string, error) {
return "", fmt.Errorf("%w: %v", ErrTaintedPath, t) return "", fmt.Errorf("%w: %v", ErrTaintedPath, t)
} }
func deleteDir(path string) error {
return os.RemoveAll(path)
}

200
tofu/tofu.go Normal file
View File

@ -0,0 +1,200 @@
package tofu
import (
"fmt"
"github.com/thegeeklab/wp-plugin-go/v2/types"
"github.com/urfave/cli/v2"
"golang.org/x/sys/execabs"
)
const TofuBin = "/usr/local/bin/tofu"
type Tofu struct {
InitOptions InitOptions
FmtOptions FmtOptions
OutFile string
Parallelism int
Targets cli.StringSlice
Refresh bool
NoLog bool
}
// InitOptions include options for the OpenTofu init command.
type InitOptions struct {
BackendConfig []string `json:"backend-config"`
Lock *bool `json:"lock"`
LockTimeout string `json:"lock-timeout"`
}
// FmtOptions fmt options for the OpenTofu fmt command.
type FmtOptions struct {
List *bool `json:"list"`
Write *bool `json:"write"`
Diff *bool `json:"diff"`
Check *bool `json:"check"`
}
func (t *Tofu) Version() *types.Cmd {
return &types.Cmd{
Cmd: execabs.Command(TofuBin, "version"),
Private: t.NoLog,
}
}
func (t *Tofu) Init() *types.Cmd {
args := []string{
"init",
}
for _, v := range t.InitOptions.BackendConfig {
args = append(args, fmt.Sprintf("-backend-config=%s", v))
}
// Fail tofu execution on prompt
args = append(args, "-input=false")
return &types.Cmd{
Cmd: execabs.Command(TofuBin, args...),
}
}
func (t *Tofu) GetModules() *types.Cmd {
return &types.Cmd{
Cmd: execabs.Command(TofuBin, "get"),
}
}
func (t *Tofu) Validate() *types.Cmd {
return &types.Cmd{
Cmd: execabs.Command(TofuBin, "validate"),
}
}
func (t *Tofu) Fmt() *types.Cmd {
args := []string{
"fmt",
}
if t.FmtOptions.List != nil {
args = append(args, fmt.Sprintf("-list=%t", *t.FmtOptions.List))
}
if t.FmtOptions.Write != nil {
args = append(args, fmt.Sprintf("-write=%t", *t.FmtOptions.Write))
}
if t.FmtOptions.Diff != nil {
args = append(args, fmt.Sprintf("-diff=%t", *t.FmtOptions.Diff))
}
if t.FmtOptions.Check != nil {
args = append(args, fmt.Sprintf("-check=%t", *t.FmtOptions.Check))
}
return &types.Cmd{
Cmd: execabs.Command(TofuBin, args...),
}
}
func (t *Tofu) Plan(destroy bool) *types.Cmd {
args := []string{
"plan",
}
if destroy {
args = append(args, "-destroy")
} else if t.OutFile != "" {
args = append(args, fmt.Sprintf("-out=%s", t.OutFile))
}
for _, value := range t.Targets.Value() {
args = append(args, "--target", value)
}
if t.Parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", t.Parallelism))
}
if t.InitOptions.Lock != nil {
args = append(args, fmt.Sprintf("-lock=%t", *t.InitOptions.Lock))
}
if t.InitOptions.LockTimeout != "" {
args = append(args, fmt.Sprintf("-lock-timeout=%s", t.InitOptions.LockTimeout))
}
if !t.Refresh {
args = append(args, "-refresh=false")
}
return &types.Cmd{
Cmd: execabs.Command(TofuBin, args...),
Private: t.NoLog,
}
}
func (t *Tofu) Apply() *types.Cmd {
args := []string{
"apply",
}
for _, v := range t.Targets.Value() {
args = append(args, "--target", v)
}
if t.Parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", t.Parallelism))
}
if t.InitOptions.Lock != nil {
args = append(args, fmt.Sprintf("-lock=%t", *t.InitOptions.Lock))
}
if t.InitOptions.LockTimeout != "" {
args = append(args, fmt.Sprintf("-lock-timeout=%s", t.InitOptions.LockTimeout))
}
if !t.Refresh {
args = append(args, "-refresh=false")
}
if t.OutFile != "" {
args = append(args, t.OutFile)
}
return &types.Cmd{
Cmd: execabs.Command(TofuBin, args...),
Private: t.NoLog,
}
}
func (t *Tofu) Destroy() *types.Cmd {
args := []string{
"destroy",
}
for _, v := range t.Targets.Value() {
args = append(args, fmt.Sprintf("-target=%s", v))
}
if t.Parallelism > 0 {
args = append(args, fmt.Sprintf("-parallelism=%d", t.Parallelism))
}
if t.InitOptions.Lock != nil {
args = append(args, fmt.Sprintf("-lock=%t", *t.InitOptions.Lock))
}
if t.InitOptions.LockTimeout != "" {
args = append(args, fmt.Sprintf("-lock-timeout=%s", t.InitOptions.LockTimeout))
}
args = append(args, "-auto-approve")
return &types.Cmd{
Cmd: execabs.Command(TofuBin, args...),
Private: t.NoLog,
}
}

545
tofu/tofu_test.go Normal file
View File

@ -0,0 +1,545 @@
package tofu
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
func boolPtr(b bool) *bool {
return &b
}
func TestTofu_Version(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
want []string
}{
{
name: "test version command",
tofu: &Tofu{},
want: []string{TofuBin, "version"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.Version()
assert.Equal(t, tt.want, cmd.Cmd.Args)
})
}
}
func TestTofu_Init(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
want []string
}{
{
name: "init with no backend config",
tofu: &Tofu{},
want: []string{
TofuBin,
"init",
"-input=false",
},
},
{
name: "init with single backend config",
tofu: &Tofu{
InitOptions: InitOptions{
BackendConfig: []string{"key=value"},
},
},
want: []string{
TofuBin,
"init",
"-backend-config=key=value",
"-input=false",
},
},
{
name: "init with multiple backend configs",
tofu: &Tofu{
InitOptions: InitOptions{
BackendConfig: []string{"key1=value1", "key2=value2"},
},
},
want: []string{
TofuBin,
"init",
"-backend-config=key1=value1",
"-backend-config=key2=value2",
"-input=false",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.Init()
assert.Equal(t, tt.want, cmd.Cmd.Args)
})
}
}
func TestTofu_GetModules(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
want []string
}{
{
name: "get modules command",
tofu: &Tofu{},
want: []string{TofuBin, "get"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.GetModules()
assert.Equal(t, tt.want, cmd.Cmd.Args)
})
}
}
func TestTofu_Validate(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
want []string
}{
{
name: "validate command",
tofu: &Tofu{},
want: []string{TofuBin, "validate"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.Validate()
assert.Equal(t, tt.want, cmd.Cmd.Args)
})
}
}
func TestTofu_Fmt(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
want []string
}{
{
name: "fmt with no options",
tofu: &Tofu{},
want: []string{
TofuBin,
"fmt",
},
},
{
name: "fmt with list option",
tofu: &Tofu{
FmtOptions: FmtOptions{
List: boolPtr(true),
},
},
want: []string{
TofuBin,
"fmt",
"-list=true",
},
},
{
name: "fmt with write option",
tofu: &Tofu{
FmtOptions: FmtOptions{
Write: boolPtr(true),
},
},
want: []string{
TofuBin,
"fmt",
"-write=true",
},
},
{
name: "fmt with diff option",
tofu: &Tofu{
FmtOptions: FmtOptions{
Diff: boolPtr(true),
},
},
want: []string{
TofuBin,
"fmt",
"-diff=true",
},
},
{
name: "fmt with check option",
tofu: &Tofu{
FmtOptions: FmtOptions{
Check: boolPtr(true),
},
},
want: []string{
TofuBin,
"fmt",
"-check=true",
},
},
{
name: "fmt with multiple options",
tofu: &Tofu{
FmtOptions: FmtOptions{
List: boolPtr(true),
Write: boolPtr(true),
Diff: boolPtr(true),
Check: boolPtr(true),
},
},
want: []string{
TofuBin,
"fmt",
"-list=true",
"-write=true",
"-diff=true",
"-check=true",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.Fmt()
assert.Equal(t, tt.want, cmd.Cmd.Args)
})
}
}
func TestTofu_Plan(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
destroy bool
want []string
}{
{
name: "plan with no options",
tofu: &Tofu{},
destroy: false,
want: []string{
TofuBin,
"plan",
"-refresh=false",
},
},
{
name: "plan with output options",
tofu: &Tofu{
OutFile: "plan.tfout",
},
destroy: false,
want: []string{
TofuBin,
"plan",
"-out=plan.tfout",
"-refresh=false",
},
},
{
name: "plan with destroy option",
tofu: &Tofu{},
destroy: true,
want: []string{
TofuBin,
"plan",
"-destroy",
"-refresh=false",
},
},
{
name: "plan with targets",
tofu: &Tofu{
Targets: *cli.NewStringSlice("target1", "target2"),
},
destroy: false,
want: []string{
TofuBin,
"plan",
"--target", "target1",
"--target", "target2",
"-refresh=false",
},
},
{
name: "plan with parallelism",
tofu: &Tofu{
Parallelism: 10,
},
destroy: false,
want: []string{
TofuBin,
"plan",
"-parallelism=10",
"-refresh=false",
},
},
{
name: "plan with lock option",
tofu: &Tofu{
InitOptions: InitOptions{
Lock: boolPtr(true),
},
},
destroy: false,
want: []string{
TofuBin,
"plan",
"-lock=true",
"-refresh=false",
},
},
{
name: "plan with lock timeout",
tofu: &Tofu{
InitOptions: InitOptions{
LockTimeout: "10s",
},
},
destroy: false,
want: []string{
TofuBin,
"plan",
"-lock-timeout=10s",
"-refresh=false",
},
},
{
name: "plan with refresh option",
tofu: &Tofu{
Refresh: true,
},
destroy: false,
want: []string{
TofuBin,
"plan",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.Plan(tt.destroy)
assert.Equal(t, tt.want, cmd.Cmd.Args)
})
}
}
func TestTofu_Apply(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
want []string
}{
{
name: "apply with no options",
tofu: &Tofu{},
want: []string{
TofuBin,
"apply",
"-refresh=false",
},
},
{
name: "apply with targets",
tofu: &Tofu{
Targets: *cli.NewStringSlice("target1", "target2"),
},
want: []string{
TofuBin,
"apply",
"--target", "target1",
"--target", "target2",
"-refresh=false",
},
},
{
name: "apply with parallelism",
tofu: &Tofu{
Parallelism: 10,
},
want: []string{
TofuBin,
"apply",
"-parallelism=10",
"-refresh=false",
},
},
{
name: "apply with lock option",
tofu: &Tofu{
InitOptions: InitOptions{
Lock: boolPtr(true),
},
},
want: []string{
TofuBin,
"apply",
"-lock=true",
"-refresh=false",
},
},
{
name: "apply with lock timeout",
tofu: &Tofu{
InitOptions: InitOptions{
LockTimeout: "10s",
},
},
want: []string{
TofuBin,
"apply",
"-lock-timeout=10s",
"-refresh=false",
},
},
{
name: "apply with refresh option",
tofu: &Tofu{
Refresh: true,
},
want: []string{
TofuBin,
"apply",
},
},
{
name: "apply with output file",
tofu: &Tofu{
OutFile: "out.tfout",
},
want: []string{
TofuBin,
"apply",
"-refresh=false",
"out.tfout",
},
},
{
name: "apply with no log",
tofu: &Tofu{
NoLog: true,
},
want: []string{
TofuBin,
"apply",
"-refresh=false",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.Apply()
assert.Equal(t, tt.want, cmd.Cmd.Args)
assert.Equal(t, tt.tofu.NoLog, cmd.Private)
})
}
}
func TestTofu_Destroy(t *testing.T) {
tests := []struct {
name string
tofu *Tofu
want []string
}{
{
name: "destroy with no options",
tofu: &Tofu{},
want: []string{
TofuBin,
"destroy",
"-auto-approve",
},
},
{
name: "destroy with targets",
tofu: &Tofu{
Targets: *cli.NewStringSlice("target1", "target2"),
},
want: []string{
TofuBin,
"destroy",
"-target=target1",
"-target=target2",
"-auto-approve",
},
},
{
name: "destroy with parallelism",
tofu: &Tofu{
Parallelism: 10,
},
want: []string{
TofuBin,
"destroy",
"-parallelism=10",
"-auto-approve",
},
},
{
name: "destroy with lock option",
tofu: &Tofu{
InitOptions: InitOptions{
Lock: boolPtr(true),
},
},
want: []string{
TofuBin,
"destroy",
"-lock=true",
"-auto-approve",
},
},
{
name: "destroy with lock timeout",
tofu: &Tofu{
InitOptions: InitOptions{
LockTimeout: "10s",
},
},
want: []string{
TofuBin,
"destroy",
"-lock-timeout=10s",
"-auto-approve",
},
},
{
name: "destroy with no log",
tofu: &Tofu{
NoLog: true,
},
want: []string{
TofuBin,
"destroy",
"-auto-approve",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.tofu.Destroy()
assert.Equal(t, tt.want, cmd.Cmd.Args)
assert.Equal(t, tt.tofu.NoLog, cmd.Private)
})
}
}