diff --git a/tfexec/internal/e2etest/providers_lock_test.go b/tfexec/internal/e2etest/providers_lock_test.go new file mode 100644 index 00000000..b6c9a83e --- /dev/null +++ b/tfexec/internal/e2etest/providers_lock_test.go @@ -0,0 +1,26 @@ +package e2etest + +import ( + "context" + "testing" + + "github.com/hashicorp/go-version" + + "github.com/hashicorp/terraform-exec/tfexec" + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestProvidersLock(t *testing.T) { + runTestVersions(t, []string{testutil.Latest014, testutil.Latest015, testutil.Latest_v1}, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + err := tf.Init(context.Background()) + if err != nil { + t.Fatalf("error running Init in test directory: %s", err) + } + + err = tf.ProvidersLock(context.Background()) + if err != nil { + t.Fatalf("error running provider lock: %s", err) + } + }) + +} diff --git a/tfexec/options.go b/tfexec/options.go index d7890107..74fd96a0 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -117,6 +117,15 @@ func DryRun(dryRun bool) *DryRunOption { return &DryRunOption{dryRun} } +type FSMirrorOption struct { + fsMirror string +} + +// FSMirror represents the -fs-mirror option (path to filesystem mirror directory) +func FSMirror(fsMirror string) *FSMirrorOption { + return &FSMirrorOption{fsMirror} +} + type ForceOption struct { force bool } @@ -178,6 +187,15 @@ func LockTimeout(lockTimeout string) *LockTimeoutOption { return &LockTimeoutOption{lockTimeout} } +type NetMirrorOption struct { + netMirror string +} + +// NetMirror represents the -net-mirror option (base URL of a network mirror) +func NetMirror(netMirror string) *NetMirrorOption { + return &NetMirrorOption{netMirror} +} + type OutOption struct { path string } @@ -194,6 +212,15 @@ func Parallelism(n int) *ParallelismOption { return &ParallelismOption{n} } +type PlatformOption struct { + platform string +} + +// Platform represents the -platform flag which is an os_arch string +func Platform(platform string) *PlatformOption { + return &PlatformOption{platform} +} + type PluginDirOption struct { pluginDir string } @@ -202,6 +229,15 @@ func PluginDir(pluginDir string) *PluginDirOption { return &PluginDirOption{pluginDir} } +type ProviderOption struct { + provider string +} + +// Provider represents the positional argument (provider source address) +func Provider(providers string) *ProviderOption { + return &ProviderOption{providers} +} + type ReattachInfo map[string]ReattachConfig // ReattachConfig holds the information Terraform needs to be able to attach diff --git a/tfexec/providers_lock.go b/tfexec/providers_lock.go new file mode 100644 index 00000000..b3a20216 --- /dev/null +++ b/tfexec/providers_lock.go @@ -0,0 +1,82 @@ +package tfexec + +import ( + "context" + "fmt" + "os/exec" +) + +type providersLockConfig struct { + fsMirror string + netMirror string + platforms []string + providers []string +} + +var defaultProvidersLockOptions = providersLockConfig{} + +type ProvidersLockOption interface { + configureProvidersLock(*providersLockConfig) +} + +func (opt *FSMirrorOption) configureProvidersLock(conf *providersLockConfig) { + conf.fsMirror = opt.fsMirror +} + +func (opt *NetMirrorOption) configureProvidersLock(conf *providersLockConfig) { + conf.netMirror = opt.netMirror +} + +func (opt *PlatformOption) configureProvidersLock(conf *providersLockConfig) { + conf.platforms = append(conf.platforms, opt.platform) +} + +func (opt *ProviderOption) configureProvidersLock(conf *providersLockConfig) { + conf.providers = append(conf.providers, opt.provider) +} + +// ProvidersLock represents the `terraform providers lock` command +func (tf *Terraform) ProvidersLock(ctx context.Context, opts ...ProvidersLockOption) error { + err := tf.compatible(ctx, tf0_14_0, nil) + if err != nil { + return fmt.Errorf("terraform providers lock was added in 0.14.0: %w", err) + } + + lockCmd := tf.providersLockCmd(ctx, opts...) + + err = tf.runTerraformCmd(ctx, lockCmd) + if err != nil { + return err + } + + return err +} + +func (tf *Terraform) providersLockCmd(ctx context.Context, opts ...ProvidersLockOption) *exec.Cmd { + c := defaultProvidersLockOptions + + for _, o := range opts { + o.configureProvidersLock(&c) + } + args := []string{"providers", "lock"} + + // string options, only pass if set + if c.fsMirror != "" { + args = append(args, "-fs-mirror="+c.fsMirror) + } + + if c.netMirror != "" { + args = append(args, "-net-mirror="+c.netMirror) + } + + for _, p := range c.platforms { + args = append(args, "-platform="+p) + } + + // positional providers argument + for _, p := range c.providers { + args = append(args, p) + } + + return tf.buildTerraformCmd(ctx, nil, args...) +} diff --git a/tfexec/providers_lock_test.go b/tfexec/providers_lock_test.go new file mode 100644 index 00000000..9d5b8dbc --- /dev/null +++ b/tfexec/providers_lock_test.go @@ -0,0 +1,42 @@ +package tfexec + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestProvidersLockCmd(t *testing.T) { + td := testTempDir(t) + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + t.Run("defaults", func(t *testing.T) { + lockCmd := tf.providersLockCmd(context.Background()) + + assertCmd(t, []string{ + "providers", + "lock", + }, nil, lockCmd) + }) + + t.Run("override all defaults", func(t *testing.T) { + lockCmd := tf.providersLockCmd(context.Background(), FSMirror("test"), NetMirror("test"), Platform("linux_amd64"), Provider("workingdir")) + + assertCmd(t, []string{ + "providers", + "lock", + "-fs-mirror=test", + "-net-mirror=test", + "-platform=linux_amd64", + "workingdir", + }, nil, lockCmd) + }) +}