Repository: chanzuckerberg/fogg (go)

Issue:
Default Variables for Modules
fogg generates a blob in main.tf in the respective env after I `fogg apply`. If I specify the `module_source` for that component, it is going to generate a parameter for each variable defined in the module. For example 

```
module "my-default-module " {
  source                 = "../../../modules/my-default-module"
  env                    = "${local.env}"
}
```

I have to define `local.env` in another file within this component. 

However, fogg still generates a parameter for me if one of the variable has a default value. I will have to either delete that line in main.tf (which is auto-generated) or redeclare that variable with the default value (It kind of defy the purpose of setting up the default).



Pull request:
feat: Default module variables
- fix: Upgrade terraform-config-inspect
- feat: Add optional variables to component conf

### Summary
forked off my branch with `terraform-config-inspect` upgrade.

Allows users to supply a list of variables for a module at the component level, to fix #163 

### Test Plan
Unit test: `TestApplyModuleInvocationWithEmptyVariables`
Unit test: `TestApplyModuleInvocationWithOneDefaultVariable`

### Example

```yaml
envs:
  shared-services:
    components:
      state-s3-backend:
        module_name: backend
        module_source: "github.com/nozaq/terraform-aws-remote-state-s3-backend?ref=v1.3.3"
        variables:
          - override_s3_bucket_name
```

results in `main.tf`:

```hcl
# Auto-generated by fogg. Do not edit
# Make improvements in fogg, so that everyone can benefit.

module "backend" {
  source                  = "github.com/nozaq/terraform-aws-remote-state-s3-backend?ref=v1.3.3"
  override_s3_bucket_name = local.override_s3_bucket_name

}
```

passing in `variables:[]` will only generate placeholders for module required variables.

omitting `variables` all together results in HEAD behavior:

```hcl
# Auto-generated by fogg. Do not edit
# Make improvements in fogg, so that everyone can benefit.

module "backend" {
  source                                 = "github.com/nozaq/terraform-aws-remote-state-s3-backend?ref=v1.3.3"
  dynamodb_enable_server_side_encryption = local.dynamodb_enable_server_side_encryption
  dynamodb_table_billing_mode            = local.dynamodb_table_billing_mode
  dynamodb_table_name                    = local.dynamodb_table_name
  enable_replication                     = local.enable_replication
  iam_policy_attachment_name             = local.iam_policy_attachment_name
  iam_policy_name_prefix                 = local.iam_policy_name_prefix
  iam_role_arn                           = local.iam_role_arn
  iam_role_name_prefix                   = local.iam_role_name_prefix
  iam_role_permissions_boundary          = local.iam_role_permissions_boundary
  kms_key_alias                          = local.kms_key_alias
  kms_key_deletion_window_in_days        = local.kms_key_deletion_window_in_days
  kms_key_description                    = local.kms_key_description
  kms_key_enable_key_rotation            = local.kms_key_enable_key_rotation
  noncurrent_version_expiration          = local.noncurrent_version_expiration
  noncurrent_version_transitions         = local.noncurrent_version_transitions
  override_s3_bucket_name                = local.override_s3_bucket_name
  replica_bucket_prefix                  = local.replica_bucket_prefix
  s3_bucket_force_destroy                = local.s3_bucket_force_destroy
  s3_bucket_name                         = local.s3_bucket_name
  s3_bucket_name_replica                 = local.s3_bucket_name_replica
  s3_logging_target_bucket               = local.s3_logging_target_bucket
  s3_logging_target_prefix               = local.s3_logging_target_prefix
  state_bucket_prefix                    = local.state_bucket_prefix
  tags                                   = local.tags
  terraform_iam_policy_create            = local.terraform_iam_policy_create
  terraform_iam_policy_name_prefix       = local.terraform_iam_policy_name_prefix

}
```

Patch:
diff --git a/apply/apply.go b/apply/apply.go
index f5422fae..2adf2f42 100644
--- a/apply/apply.go
+++ b/apply/apply.go
@@ -15,6 +15,7 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
+	"golang.org/x/exp/slices"
 
 	v2 "github.com/chanzuckerberg/fogg/config/v2"
 	"github.com/chanzuckerberg/fogg/errs"
@@ -236,7 +237,7 @@ func applyTFE(fs afero.Fs, plan *plan.Plan, tmpl *templates.T) error {
 		if err != nil {
 			return errs.WrapUser(err, "unable to make a downloader")
 		}
-		err = applyModuleInvocation(fs, path, *plan.TFE.ModuleSource, plan.TFE.ModuleName, templates.Templates.ModuleInvocation, tmpl.Common, downloader)
+		err = applyModuleInvocation(fs, path, *plan.TFE.ModuleSource, plan.TFE.ModuleName, nil, templates.Templates.ModuleInvocation, tmpl.Common, downloader)
 		if err != nil {
 			return errs.WrapUser(err, "unable to apply module invocation")
 		}
@@ -370,7 +371,7 @@ func applyEnvs(
 				if err != nil {
 					return errs.WrapUser(err, "unable to make a downloader")
 				}
-				err = applyModuleInvocation(fs, path, *componentPlan.ModuleSource, componentPlan.ModuleName, templates.Templates.ModuleInvocation, commonBox, downloader)
+				err = applyModuleInvocation(fs, path, *componentPlan.ModuleSource, componentPlan.ModuleName, componentPlan.Variables, templates.Templates.ModuleInvocation, commonBox, downloader)
 				if err != nil {
 					return errs.WrapUser(err, "unable to apply module invocation")
 				}
@@ -544,6 +545,7 @@ func applyModuleInvocation(
 	fs afero.Fs,
 	path, moduleAddress string,
 	inModuleName *string,
+	variables []string,
 	box fs.FS,
 	commonBox fs.FS,
 	downloadFunc util.ModuleDownloader,
@@ -561,9 +563,15 @@ func applyModuleInvocation(
 	// This should really be part of the plan stage, not apply. But going to
 	// leave it here for now and re-think it when we make this mechanism
 	// general purpose.
-	variables := make([]string, 0)
+	addAll := variables == nil
 	for _, v := range moduleConfig.Variables {
-		variables = append(variables, v.Name)
+		if addAll {
+			variables = append(variables, v.Name)
+		} else {
+			if v.Required && !slices.Contains(variables, v.Name) {
+				variables = append(variables, v.Name)
+			}
+		}
 	}
 	sort.Strings(variables)
 	outputs := make([]string, 0)
diff --git a/config/v2/config.go b/config/v2/config.go
index 64f98492..d61c0ed1 100644
--- a/config/v2/config.go
+++ b/config/v2/config.go
@@ -126,6 +126,7 @@ type Component struct {
 	Kind         *ComponentKind `yaml:"kind,omitempty"`
 	ModuleSource *string        `yaml:"module_source,omitempty"`
 	ModuleName   *string        `yaml:"module_name,omitempty"`
+	Variables    []string       `yaml:"variables,omitempty"`
 }
 
 type Providers struct {
diff --git a/go.mod b/go.mod
index 019bba04..6287a940 100644
--- a/go.mod
+++ b/go.mod
@@ -34,6 +34,7 @@ require (
 	github.com/spf13/cobra v1.5.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.8.0
+	golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
 	gopkg.in/go-playground/validator.v9 v9.31.0
 	gopkg.in/ini.v1 v1.67.0
 	gopkg.in/yaml.v3 v3.0.1
@@ -101,14 +102,13 @@ require (
 	go.opencensus.io v0.22.4 // indirect
 	golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect
 	golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
-	golang.org/x/mod v0.4.2 // indirect
-	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+	golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
 	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
-	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
+	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/tools v0.1.1 // indirect
-	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	golang.org/x/tools v0.1.12 // indirect
 	google.golang.org/api v0.34.0 // indirect
 	google.golang.org/appengine v1.6.6 // indirect
 	google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
diff --git a/go.sum b/go.sum
index 80febd97..bbbf4dad 100644
--- a/go.sum
+++ b/go.sum
@@ -894,6 +894,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
+golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -915,8 +917,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -968,6 +970,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1043,8 +1047,9 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1118,12 +1123,11 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
 golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
diff --git a/plan/plan.go b/plan/plan.go
index 32d247ba..946e73f2 100644
--- a/plan/plan.go
+++ b/plan/plan.go
@@ -323,6 +323,7 @@ type Component struct {
 	Kind         *v2.ComponentKind `yaml:"kind,omitempty"`
 	ModuleSource *string           `yaml:"module_source"`
 	ModuleName   *string           `yaml:"module_name"`
+	Variables    []string          `yaml:"variables"`
 	Global       *Component        `yaml:"global"`
 }
 
@@ -601,6 +602,7 @@ func (p *Plan) buildEnvs(conf *v2.Config) (map[string]Env, error) {
 			componentPlan.Name = componentName
 			componentPlan.ModuleSource = componentConf.ModuleSource
 			componentPlan.ModuleName = componentConf.ModuleName
+			componentPlan.Variables = componentConf.Variables
 			componentPlan.PathToRepoRoot = "../../../../"
 
 			componentPlan.Global = &p.Global


Test patch:
diff --git a/apply/apply_test.go b/apply/apply_test.go
index d6425c17..415ba968 100644
--- a/apply/apply_test.go
+++ b/apply/apply_test.go
@@ -407,7 +407,40 @@ func TestApplyModuleInvocation(t *testing.T) {
 	fs := afero.NewCopyOnWriteFs(pwdFs, testFs)
 	downloader, err := util.MakeDownloader("test-module")
 	r.NoError(err)
-	e := applyModuleInvocation(fs, "mymodule", "test-module", nil, templates.Templates.ModuleInvocation, templates.Templates.Common, downloader)
+	e := applyModuleInvocation(fs, "mymodule", "test-module", nil, nil, templates.Templates.ModuleInvocation, templates.Templates.Common, downloader)
+	r.NoError(e)
+
+	s, e := fs.Stat("mymodule")
+	r.Nil(e)
+	r.True(s.IsDir())
+
+	_, e = fs.Stat("mymodule/main.tf")
+	r.Nil(e)
+	i, e := afero.ReadFile(fs, "mymodule/main.tf")
+	r.Nil(e)
+	expected := "# Auto-generated by fogg. Do not edit\n# Make improvements in fogg, so that everyone can benefit.\n\nmodule \"test-module\" {\n  source = \"../test-module\"\n  bar    = local.bar\n  baz    = local.baz\n  foo    = local.foo\n  quux   = local.quux\n\n}\n"
+	r.Equal(expected, string(i))
+
+	_, e = fs.Stat("mymodule/outputs.tf")
+	r.Nil(e)
+	i, e = afero.ReadFile(fs, "mymodule/outputs.tf")
+	r.Nil(e)
+	expected = "# Auto-generated by fogg. Do not edit\n# Make improvements in fogg, so that everyone can benefit.\n\noutput \"bar\" {\n  value = module.test-module.bar\n}\n\noutput \"foo\" {\n  value = module.test-module.foo\n}\n\n\n"
+	r.Equal(expected, string(i))
+}
+
+func TestApplyModuleInvocationWithEmptyVariables(t *testing.T) {
+	r := require.New(t)
+	testFs, d, err := util.TestFs()
+	r.NoError(err)
+	defer os.RemoveAll(d)
+	pwdFs, err := util.PwdFs()
+	r.NoError(err)
+
+	fs := afero.NewCopyOnWriteFs(pwdFs, testFs)
+	downloader, err := util.MakeDownloader("test-module")
+	r.NoError(err)
+	e := applyModuleInvocation(fs, "mymodule", "test-module", nil, []string{}, templates.Templates.ModuleInvocation, templates.Templates.Common, downloader)
 	r.NoError(e)
 
 	s, e := fs.Stat("mymodule")
@@ -429,6 +462,39 @@ func TestApplyModuleInvocation(t *testing.T) {
 	r.Equal(expected, string(i))
 }
 
+func TestApplyModuleInvocationWithOneDefaultVariable(t *testing.T) {
+	r := require.New(t)
+	testFs, d, err := util.TestFs()
+	r.NoError(err)
+	defer os.RemoveAll(d)
+	pwdFs, err := util.PwdFs()
+	r.NoError(err)
+
+	fs := afero.NewCopyOnWriteFs(pwdFs, testFs)
+	downloader, err := util.MakeDownloader("test-module")
+	r.NoError(err)
+	e := applyModuleInvocation(fs, "mymodule", "test-module", nil, []string{"baz"}, templates.Templates.ModuleInvocation, templates.Templates.Common, downloader)
+	r.NoError(e)
+
+	s, e := fs.Stat("mymodule")
+	r.Nil(e)
+	r.True(s.IsDir())
+
+	_, e = fs.Stat("mymodule/main.tf")
+	r.Nil(e)
+	i, e := afero.ReadFile(fs, "mymodule/main.tf")
+	r.Nil(e)
+	expected := "# Auto-generated by fogg. Do not edit\n# Make improvements in fogg, so that everyone can benefit.\n\nmodule \"test-module\" {\n  source = \"../test-module\"\n  bar    = local.bar\n  baz    = local.baz\n  foo    = local.foo\n\n}\n"
+	r.Equal(expected, string(i))
+
+	_, e = fs.Stat("mymodule/outputs.tf")
+	r.Nil(e)
+	i, e = afero.ReadFile(fs, "mymodule/outputs.tf")
+	r.Nil(e)
+	expected = "# Auto-generated by fogg. Do not edit\n# Make improvements in fogg, so that everyone can benefit.\n\noutput \"bar\" {\n  value = module.test-module.bar\n}\n\noutput \"foo\" {\n  value = module.test-module.foo\n}\n\n\n"
+	r.Equal(expected, string(i))
+}
+
 func TestApplyModuleInvocationWithModuleName(t *testing.T) {
 	r := require.New(t)
 	testFs, d, err := util.TestFs()
@@ -442,7 +508,7 @@ func TestApplyModuleInvocationWithModuleName(t *testing.T) {
 	moduleName := "module-name"
 	downloader, err := util.MakeDownloader("test-module")
 	r.NoError(err)
-	e := applyModuleInvocation(fs, "mymodule", "test-module", &moduleName, templates.Templates.ModuleInvocation, templates.Templates.Common, downloader)
+	e := applyModuleInvocation(fs, "mymodule", "test-module", &moduleName, nil, templates.Templates.ModuleInvocation, templates.Templates.Common, downloader)
 	r.NoError(e)
 
 	s, e := fs.Stat("mymodule")
@@ -453,7 +519,7 @@ func TestApplyModuleInvocationWithModuleName(t *testing.T) {
 	r.Nil(e)
 	i, e := afero.ReadFile(fs, "mymodule/main.tf")
 	r.Nil(e)
-	expected := "# Auto-generated by fogg. Do not edit\n# Make improvements in fogg, so that everyone can benefit.\n\nmodule \"module-name\" {\n  source = \"../test-module\"\n  bar    = local.bar\n  foo    = local.foo\n\n}\n"
+	expected := "# Auto-generated by fogg. Do not edit\n# Make improvements in fogg, so that everyone can benefit.\n\nmodule \"module-name\" {\n  source = \"../test-module\"\n  bar    = local.bar\n  baz    = local.baz\n  foo    = local.foo\n  quux   = local.quux\n\n}\n"
 	r.Equal(expected, string(i))
 
 	_, e = fs.Stat("mymodule/outputs.tf")
diff --git a/apply/test-module/variables.tf b/apply/test-module/variables.tf
index 6d8ae6a0..eaee6755 100644
--- a/apply/test-module/variables.tf
+++ b/apply/test-module/variables.tf
@@ -1,2 +1,4 @@
 variable "foo" {}
 variable "bar" {}
+variable "baz" { default = "" }
+variable "quux" { default = "" }