| // Copyright IBM Corp. 2020, 2026 | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package tfexec | |
| import ( | |
| "context" | |
| "fmt" | |
| "io" | |
| "os/exec" | |
| ) | |
| type initConfig struct { | |
| backend bool | |
| backendConfig []string | |
| dir string | |
| forceCopy bool | |
| fromModule string | |
| get bool | |
| getPlugins bool | |
| lock bool | |
| lockTimeout string | |
| pluginDir []string | |
| reattachInfo ReattachInfo | |
| reconfigure bool | |
| upgrade bool | |
| verifyPlugins bool | |
| } | |
| var defaultInitOptions = initConfig{ | |
| backend: true, | |
| forceCopy: false, | |
| get: true, | |
| getPlugins: true, | |
| lock: true, | |
| lockTimeout: "0s", | |
| reconfigure: false, | |
| upgrade: false, | |
| verifyPlugins: true, | |
| } | |
| // InitOption represents options used in the Init method. | |
| type InitOption interface { | |
| configureInit(*initConfig) | |
| } | |
| func (opt *BackendOption) configureInit(conf *initConfig) { | |
| conf.backend = opt.backend | |
| } | |
| func (opt *BackendConfigOption) configureInit(conf *initConfig) { | |
| conf.backendConfig = append(conf.backendConfig, opt.path) | |
| } | |
| func (opt *DirOption) configureInit(conf *initConfig) { | |
| conf.dir = opt.path | |
| } | |
| func (opt *ForceCopyOption) configureInit(conf *initConfig) { | |
| conf.forceCopy = opt.forceCopy | |
| } | |
| func (opt *FromModuleOption) configureInit(conf *initConfig) { | |
| conf.fromModule = opt.source | |
| } | |
| func (opt *GetOption) configureInit(conf *initConfig) { | |
| conf.get = opt.get | |
| } | |
| func (opt *GetPluginsOption) configureInit(conf *initConfig) { | |
| conf.getPlugins = opt.getPlugins | |
| } | |
| func (opt *LockOption) configureInit(conf *initConfig) { | |
| conf.lock = opt.lock | |
| } | |
| func (opt *LockTimeoutOption) configureInit(conf *initConfig) { | |
| conf.lockTimeout = opt.timeout | |
| } | |
| func (opt *PluginDirOption) configureInit(conf *initConfig) { | |
| conf.pluginDir = append(conf.pluginDir, opt.pluginDir) | |
| } | |
| func (opt *ReattachOption) configureInit(conf *initConfig) { | |
| conf.reattachInfo = opt.info | |
| } | |
| func (opt *ReconfigureOption) configureInit(conf *initConfig) { | |
| conf.reconfigure = opt.reconfigure | |
| } | |
| func (opt *UpgradeOption) configureInit(conf *initConfig) { | |
| conf.upgrade = opt.upgrade | |
| } | |
| func (opt *VerifyPluginsOption) configureInit(conf *initConfig) { | |
| conf.verifyPlugins = opt.verifyPlugins | |
| } | |
| func (tf *Terraform) configureInitOptions(ctx context.Context, c *initConfig, opts ...InitOption) error { | |
| for _, o := range opts { | |
| switch o.(type) { | |
| case *LockOption, *LockTimeoutOption, *VerifyPluginsOption, *GetPluginsOption: | |
| err := tf.compatible(ctx, nil, tf0_15_0) | |
| if err != nil { | |
| return fmt.Errorf("-lock, -lock-timeout, -verify-plugins, and -get-plugins options are no longer available as of Terraform 0.15: %w", err) | |
| } | |
| } | |
| o.configureInit(c) | |
| } | |
| return nil | |
| } | |
| // Init represents the terraform init subcommand. | |
| func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error { | |
| cmd, err := tf.initCmd(ctx, opts...) | |
| if err != nil { | |
| return err | |
| } | |
| return tf.runTerraformCmd(ctx, cmd) | |
| } | |
| // InitJSON represents the terraform init subcommand with the `-json` flag. | |
| // Using the `-json` flag will result in | |
| // [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui) | |
| // JSON being written to the supplied `io.Writer`. | |
| func (tf *Terraform) InitJSON(ctx context.Context, w io.Writer, opts ...InitOption) error { | |
| err := tf.compatible(ctx, tf1_9_0, nil) | |
| if err != nil { | |
| return fmt.Errorf("terraform init -json was added in 1.9.0: %w", err) | |
| } | |
| tf.SetStdout(w) | |
| cmd, err := tf.initJSONCmd(ctx, opts...) | |
| if err != nil { | |
| return err | |
| } | |
| return tf.runTerraformCmd(ctx, cmd) | |
| } | |
| func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) { | |
| c := defaultInitOptions | |
| err := tf.configureInitOptions(ctx, &c, opts...) | |
| if err != nil { | |
| return nil, err | |
| } | |
| args, err := tf.buildInitArgs(ctx, c) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Optional positional argument; must be last as flags precede positional arguments. | |
| if c.dir != "" { | |
| args = append(args, c.dir) | |
| } | |
| return tf.buildInitCmd(ctx, c, args) | |
| } | |
| func (tf *Terraform) initJSONCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) { | |
| c := defaultInitOptions | |
| err := tf.configureInitOptions(ctx, &c, opts...) | |
| if err != nil { | |
| return nil, err | |
| } | |
| args, err := tf.buildInitArgs(ctx, c) | |
| if err != nil { | |
| return nil, err | |
| } | |
| args = append(args, "-json") | |
| // Optional positional argument; must be last as flags precede positional arguments. | |
| if c.dir != "" { | |
| args = append(args, c.dir) | |
| } | |
| return tf.buildInitCmd(ctx, c, args) | |
| } | |
| func (tf *Terraform) buildInitArgs(ctx context.Context, c initConfig) ([]string, error) { | |
| args := []string{"init", "-no-color", "-input=false"} | |
| // string opts: only pass if set | |
| if c.fromModule != "" { | |
| args = append(args, "-from-module="+c.fromModule) | |
| } | |
| // string opts removed in 0.15: pass if set and <0.15 | |
| err := tf.compatible(ctx, nil, tf0_15_0) | |
| if err == nil { | |
| if c.lockTimeout != "" { | |
| args = append(args, "-lock-timeout="+c.lockTimeout) | |
| } | |
| } | |
| // boolean opts: always pass | |
| args = append(args, "-backend="+fmt.Sprint(c.backend)) | |
| args = append(args, "-get="+fmt.Sprint(c.get)) | |
| args = append(args, "-upgrade="+fmt.Sprint(c.upgrade)) | |
| // boolean opts removed in 0.15: pass if <0.15 | |
| err = tf.compatible(ctx, nil, tf0_15_0) | |
| if err == nil { | |
| args = append(args, "-lock="+fmt.Sprint(c.lock)) | |
| args = append(args, "-get-plugins="+fmt.Sprint(c.getPlugins)) | |
| args = append(args, "-verify-plugins="+fmt.Sprint(c.verifyPlugins)) | |
| } | |
| if c.forceCopy { | |
| args = append(args, "-force-copy") | |
| } | |
| // unary flags: pass if true | |
| if c.reconfigure { | |
| args = append(args, "-reconfigure") | |
| } | |
| // string slice opts: split into separate args | |
| if c.backendConfig != nil { | |
| for _, bc := range c.backendConfig { | |
| args = append(args, "-backend-config="+bc) | |
| } | |
| } | |
| if c.pluginDir != nil { | |
| for _, pd := range c.pluginDir { | |
| args = append(args, "-plugin-dir="+pd) | |
| } | |
| } | |
| return args, nil | |
| } | |
| func (tf *Terraform) buildInitCmd(ctx context.Context, c initConfig, args []string) (*exec.Cmd, error) { | |
| mergeEnv := map[string]string{} | |
| if c.reattachInfo != nil { | |
| reattachStr, err := c.reattachInfo.marshalString() | |
| if err != nil { | |
| return nil, err | |
| } | |
| mergeEnv[reattachEnvVar] = reattachStr | |
| } | |
| return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil | |
| } |