Repository: chanzuckerberg/fogg
Pull request #300 (2019-06-19 15:10:12)

[feature] Add an Okta provider
### Summary
Allow us to configure [okta providers](https://github.com/articulate/terraform-provider-okta) through fogg 

### Test Plan
- unittests
- run it in repo



codecov[bot]: # [Codecov](https://codecov.io/gh/chanzuckerberg/fogg/pull/300?src=pr&el=h1) Report
> Merging [#300](https://codecov.io/gh/chanzuckerberg/fogg/pull/300?src=pr&el=desc) into [master](https://codecov.io/gh/chanzuckerberg/fogg/commit/d7a5ccc903fd704db3e77149175bbc763a928b4e?src=pr&el=desc) will **decrease** coverage by `0.37%`.
> The diff coverage is `73.21%`.

[![Impacted file tree graph](https://codecov.io/gh/chanzuckerberg/fogg/pull/300/graphs/tree.svg?width=650&token=57DhtZ7g2A&height=150&src=pr)](https://codecov.io/gh/chanzuckerberg/fogg/pull/300?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #300      +/-   ##
==========================================
- Coverage   80.09%   79.72%   -0.38%     
==========================================
  Files          17       17              
  Lines        1422     1465      +43     
==========================================
+ Hits         1139     1168      +29     
- Misses        162      176      +14     
  Partials      121      121
```


| [Impacted Files](https://codecov.io/gh/chanzuckerberg/fogg/pull/300?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [plan/plan.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/300/diff?src=pr&el=tree#diff-cGxhbi9wbGFuLmdv) | `91.35% <100%> (+0.39%)` | :arrow_up: |
| [config/v2/config.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/300/diff?src=pr&el=tree#diff-Y29uZmlnL3YyL2NvbmZpZy5nbw==) | `100% <100%> (ø)` | :arrow_up: |
| [config/v2/resolvers.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/300/diff?src=pr&el=tree#diff-Y29uZmlnL3YyL3Jlc29sdmVycy5nbw==) | `98.87% <100%> (+0.1%)` | :arrow_up: |
| [config/v2/validation.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/300/diff?src=pr&el=tree#diff-Y29uZmlnL3YyL3ZhbGlkYXRpb24uZ28=) | `75% <44.44%> (-6.82%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/chanzuckerberg/fogg/pull/300?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/chanzuckerberg/fogg/pull/300?src=pr&el=footer). Last update [d7a5ccc...b6533e1](https://codecov.io/gh/chanzuckerberg/fogg/pull/300?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/config/v2/config.go b/config/v2/config.go
index 8bdb7ed2..8ee92523 100644
--- a/config/v2/config.go
+++ b/config/v2/config.go
@@ -71,6 +71,14 @@ type Providers struct {
 	AWS       *AWSProvider       `json:"aws,omitempty"`
 	Snowflake *SnowflakeProvider `json:"snowflake,omitempty"`
 	Bless     *BlessProvider     `json:"bless,omitempty"`
+	Okta      *OktaProvider      `json:"okta,omitempty"`
+}
+
+// OktaProvider is an okta provider
+type OktaProvider struct {
+	// the okta provider is optional (above) but if supplied you must set an OrgName
+	OrgName *string `json:"org_name,omitempty"`
+	Version *string `json:"version,omitempty"`
 }
 
 // BlessProvider allows for terraform-provider-bless configuration
@@ -152,6 +160,16 @@ func (c *Config) Generate(r *rand.Rand, size int) reflect.Value {
 		}
 	}
 
+	randOktaProvider := func(r *rand.Rand, s int) *OktaProvider {
+		if r.Float32() < 0.5 {
+			return nil
+		}
+		return &OktaProvider{
+			Version: randStringPtr(r, s),
+			OrgName: randStringPtr(r, s),
+		}
+	}
+
 	randBlessProvider := func(r *rand.Rand, s int) *BlessProvider {
 		if r.Float32() < 0.5 {
 			return nil
@@ -199,6 +217,7 @@ func (c *Config) Generate(r *rand.Rand, size int) reflect.Value {
 			Providers: &Providers{
 				AWS:       randAWSProvider(r, s),
 				Snowflake: randSnowflakeProvider(r, s),
+				Okta:      randOktaProvider(r, s),
 				Bless:     randBlessProvider(r, s),
 			},
 			TerraformVersion: randStringPtr(r, s),
diff --git a/config/v2/resolvers.go b/config/v2/resolvers.go
index 93d27f57..af4ce4cc 100644
--- a/config/v2/resolvers.go
+++ b/config/v2/resolvers.go
@@ -117,6 +117,19 @@ func ResolveSnowflakeProvider(commons ...Common) *SnowflakeProvider {
 	return nil
 }
 
+func ResolveOktaProvider(commons ...Common) *OktaProvider {
+	orgName := lastNonNil(OktaProviderOrgNameGetter, commons...)
+
+	// required fields
+	if orgName == nil {
+		return nil
+	}
+
+	return &OktaProvider{
+		OrgName: orgName,
+		Version: lastNonNil(OktaProviderVersionGetter, commons...),
+	}
+}
 func ResolveBlessProvider(commons ...Common) *BlessProvider {
 	profile := lastNonNil(BlessProviderProfileGetter, commons...)
 	region := lastNonNil(BlessProviderRegionGetter, commons...)
@@ -285,3 +298,17 @@ func BlessProviderAdditionalRegionsGetter(comm Common) []string {
 	}
 	return comm.Providers.Bless.AdditionalRegions
 }
+
+func OktaProviderVersionGetter(comm Common) *string {
+	if comm.Providers == nil || comm.Providers.Okta == nil {
+		return nil
+	}
+	return comm.Providers.Okta.Version
+}
+
+func OktaProviderOrgNameGetter(comm Common) *string {
+	if comm.Providers == nil || comm.Providers.Okta == nil {
+		return nil
+	}
+	return comm.Providers.Okta.OrgName
+}
diff --git a/config/v2/validation.go b/config/v2/validation.go
index cc2f00b4..9746e729 100644
--- a/config/v2/validation.go
+++ b/config/v2/validation.go
@@ -46,6 +46,7 @@ func (c *Config) Validate() ([]string, error) {
 	errs = multierror.Append(errs, c.ValidateAWSProviders())
 	errs = multierror.Append(errs, c.ValidateSnowflakeProviders())
 	errs = multierror.Append(errs, c.ValidateBlessProviders())
+	errs = multierror.Append(errs, c.ValidateOktaProviders())
 	errs = multierror.Append(errs, c.validateModules())
 
 	// refactor to make it easier to manage these
@@ -104,7 +105,21 @@ func (c *Config) ValidateAWSProviders() error {
 	return errs.ErrorOrNil()
 }
 
-func ValidateSnowflakeProvider(p *SnowflakeProvider, component string) error {
+func (p *BlessProvider) Validate(component string) error {
+	var errs *multierror.Error
+	if p == nil {
+		return nil // nothing to do
+	}
+	if p.AWSProfile == nil {
+		errs = multierror.Append(errs, fmt.Errorf("bless provider aws_profile required in %s", component))
+	}
+	if p.AWSRegion == nil {
+		errs = multierror.Append(errs, fmt.Errorf("bless provider aws_region required in %s", component))
+	}
+	return errs
+}
+
+func (p *SnowflakeProvider) Validate(component string) error {
 	var errs *multierror.Error
 	if p == nil {
 		return nil // nothing to do
@@ -125,39 +140,44 @@ func ValidateSnowflakeProvider(p *SnowflakeProvider, component string) error {
 	return errs
 }
 
-func (c *Config) ValidateSnowflakeProviders() error {
+func (o *OktaProvider) Validate(component string) error {
 	var errs *multierror.Error
+	if o == nil {
+		return nil
+	}
+	if o.OrgName == nil {
+		errs = multierror.Append(errs, fmt.Errorf("okta provider org_name required in %s", component))
+	}
+	return errs
+}
 
+func (c *Config) ValidateSnowflakeProviders() error {
+	var errs *multierror.Error
 	c.WalkComponents(func(component string, comms ...Common) {
-		v := ResolveSnowflakeProvider(comms...)
-		if e := ValidateSnowflakeProvider(v, component); e != nil {
+		p := ResolveSnowflakeProvider(comms...)
+		if e := p.Validate(component); e != nil {
 			errs = multierror.Append(errs, e)
 		}
 	})
-
 	return errs.ErrorOrNil()
 }
 
-func ValidateBlessProvider(p *BlessProvider, component string) error {
+func (c *Config) ValidateOktaProviders() error {
 	var errs *multierror.Error
-	if p == nil {
-		return nil // nothing to do
-	}
-
-	if p.AWSProfile == nil {
-		errs = multierror.Append(errs, fmt.Errorf("bless provider aws_profile required in %s", component))
-	}
-	if p.AWSRegion == nil {
-		errs = multierror.Append(errs, fmt.Errorf("bless provider aws_region required in %s", component))
-	}
+	c.WalkComponents(func(component string, comms ...Common) {
+		p := ResolveOktaProvider(comms...)
+		if err := p.Validate(component); err != nil {
+			errs = multierror.Append(errs, err)
+		}
+	})
 	return errs
 }
 
 func (c *Config) ValidateBlessProviders() error {
 	var errs *multierror.Error
 	c.WalkComponents(func(component string, comms ...Common) {
-		v := ResolveBlessProvider(comms...)
-		if err := ValidateBlessProvider(v, component); err != nil {
+		p := ResolveBlessProvider(comms...)
+		if err := p.Validate(component); err != nil {
 			errs = multierror.Append(errs, err)
 		}
 	})
diff --git a/plan/plan.go b/plan/plan.go
index 1994bd13..4a8055a0 100644
--- a/plan/plan.go
+++ b/plan/plan.go
@@ -42,6 +42,7 @@ type Providers struct {
 	AWS       *AWSProvider       `yaml:"aws"`
 	Snowflake *SnowflakeProvider `yaml:"snowflake"`
 	Bless     *BlessProvider     `yaml:"bless"`
+	Okta      *OktaProvider      `yaml:"okta"`
 }
 
 type AWSProvider struct {
@@ -59,6 +60,11 @@ type SnowflakeProvider struct {
 	Version *string `yaml:"version,omitempty"`
 }
 
+type OktaProvider struct {
+	OrgName string  `json:"org_name,omitempty"`
+	Version *string `json:"version,omitempty"`
+}
+
 type BlessProvider struct {
 	AdditionalRegions []string `yaml:"additional_regions,omitempty"`
 	AWSProfile        string   `yaml:"aws_profile,omitempty"`
@@ -272,6 +278,15 @@ func resolveComponentCommon(commons ...v2.Common) ComponentCommon {
 		}
 	}
 
+	var oktaPlan *OktaProvider
+	oktaConfig := v2.ResolveOktaProvider(commons...)
+	if oktaConfig != nil {
+		oktaPlan = &OktaProvider{
+			OrgName: *oktaConfig.OrgName,
+			Version: oktaConfig.Version,
+		}
+	}
+
 	var blessPlan *BlessProvider
 	blessConfig := v2.ResolveBlessProvider(commons...)
 	if blessConfig != nil && blessConfig.AWSProfile != nil && blessConfig.AWSRegion != nil {
@@ -300,6 +315,7 @@ func resolveComponentCommon(commons ...v2.Common) ComponentCommon {
 			AWS:       awsPlan,
 			Snowflake: snowflakePlan,
 			Bless:     blessPlan,
+			Okta:      oktaPlan,
 		},
 		TfLint:    tfLintPlan,
 		ExtraVars: v2.ResolveStringMap(v2.ExtraVarsGetter, commons...),
diff --git a/templates/account/fogg.tf.tmpl b/templates/account/fogg.tf.tmpl
index 3076bd32..c14dfc35 100644
--- a/templates/account/fogg.tf.tmpl
+++ b/templates/account/fogg.tf.tmpl
@@ -31,7 +31,9 @@
   {{ template "bless_provider" .Providers.Bless }}
 {{ end }}
 
-
+{{ if .Providers.Okta}}
+  {{ template "okta_provider" .Providers.Okta}}
+{{ end }}
 
 terraform {
   required_version = "={{ .TerraformVersion }}"
diff --git a/templates/common/okta_provider.tmpl b/templates/common/okta_provider.tmpl
new file mode 100644
index 00000000..6d6961ac
--- /dev/null
+++ b/templates/common/okta_provider.tmpl
@@ -0,0 +1,10 @@
+{{define "okta_provider"}}
+
+// https://github.com/articulate/terraform-provider-okta
+provider "okta" {
+  {{ if .Version -}}
+  version = "~>{{ .Version }}"
+  {{ end -}}
+  org_name = "{{ .OrgName }}"
+}
+{{ end }}
diff --git a/templates/component/terraform/fogg.tf.tmpl b/templates/component/terraform/fogg.tf.tmpl
index a4af12dc..922420cd 100644
--- a/templates/component/terraform/fogg.tf.tmpl
+++ b/templates/component/terraform/fogg.tf.tmpl
@@ -31,6 +31,9 @@
   {{ template "bless_provider" .Providers.Bless }}
 {{ end }}
 
+{{ if .Providers.Okta}}
+  {{ template "okta_provider" .Providers.Okta}}
+{{ end }}
 
 terraform {
   required_version = "~>{{ .TerraformVersion }}"
diff --git a/templates/global/fogg.tf.tmpl b/templates/global/fogg.tf.tmpl
index e28e0019..f849f2e7 100644
--- a/templates/global/fogg.tf.tmpl
+++ b/templates/global/fogg.tf.tmpl
@@ -30,6 +30,10 @@
   {{ template "bless_provider" .Providers.Bless }}
 {{ end }}
 
+{{ if .Providers.Okta}}
+  {{ template "okta_provider" .Providers.Okta}}
+{{ end }}
+
 terraform {
   required_version = "~>{{ .TerraformVersion }}"
 


Test patch:
diff --git a/apply/golden_file_test.go b/apply/golden_file_test.go
index e31d7999..5fa1901f 100644
--- a/apply/golden_file_test.go
+++ b/apply/golden_file_test.go
@@ -30,6 +30,7 @@ func TestIntegration(t *testing.T) {
 		{"v2_no_aws_provider"},
 		{"snowflake_provider"},
 		{"bless_provider"},
+		{"okta_provider"},
 	}
 
 	for _, tc := range testCases {
@@ -42,6 +43,7 @@ func TestIntegration(t *testing.T) {
 			if *updateGoldenFiles {
 				// delete all files except fogg.json
 				e := afero.Walk(testdataFs, ".", func(path string, info os.FileInfo, err error) error {
+					fmt.Printf("\n\n HERE:%s \n\n", path)
 					if !info.IsDir() && path != "fogg.json" {
 						return testdataFs.Remove(path)
 					}
@@ -52,6 +54,7 @@ func TestIntegration(t *testing.T) {
 				conf, e := config.FindAndReadConfig(testdataFs, "fogg.json")
 				a.NoError(e)
 				fmt.Printf("conf %#v\n", conf)
+				fmt.Println("READ CONFIG")
 
 				w, e := conf.Validate()
 				a.NoError(e)
diff --git a/config/v2/config_test.go b/config/v2/config_test.go
index db5a7faf..c5279137 100644
--- a/config/v2/config_test.go
+++ b/config/v2/config_test.go
@@ -75,3 +75,24 @@ func TestReadBlessProvider(t *testing.T) {
 	r.Equal("0.0.0", *c.Defaults.Providers.Bless.Version)
 	r.Equal([]string{"a", "b"}, c.Defaults.Providers.Bless.AdditionalRegions)
 }
+
+func TestReadOktaProvider(t *testing.T) {
+	r := require.New(t)
+
+	b, e := util.TestFile("okta_provider")
+	r.NoError(e)
+	r.NotNil(b)
+
+	c, e := ReadConfig(b)
+	r.NoError(e)
+	r.NotNil(c)
+
+	w, e := c.Validate()
+	r.NoError(e)
+	r.Len(w, 0)
+
+	r.NotNil(c.Defaults.Providers)
+	r.NotNil(c.Defaults.Providers.Okta)
+	r.Equal("aversion", *c.Defaults.Providers.Okta.Version)
+	r.Equal("orgname", *c.Defaults.Providers.Okta.OrgName)
+}
diff --git a/testdata/okta_provider/.fogg-version b/testdata/okta_provider/.fogg-version
new file mode 100644
index 00000000..2d5cfc01
--- /dev/null
+++ b/testdata/okta_provider/.fogg-version
@@ -0,0 +1,1 @@
+undefined+undefined+dirty
\ No newline at end of file
diff --git a/testdata/okta_provider/.gitattributes b/testdata/okta_provider/.gitattributes
new file mode 100644
index 00000000..b67d3875
--- /dev/null
+++ b/testdata/okta_provider/.gitattributes
@@ -0,0 +1,2 @@
+fogg.tf linguist-generated
+Makefile linguist-generated
diff --git a/testdata/okta_provider/.gitignore b/testdata/okta_provider/.gitignore
new file mode 100644
index 00000000..ac842423
--- /dev/null
+++ b/testdata/okta_provider/.gitignore
@@ -0,0 +1,29 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+# Compiled files
+*.tfstate
+*.tfstate.*.backup
+*.tfstate.backup
+*tfvars
+
+# Module directory
+.terraform/
+
+# Pycharm folder
+.idea
+
+# Editor Swap Files
+*.swp
+*.swo
+*.swn
+*.swm
+*.swl
+*.swk
+
+.fogg
+/terraform.d
+
+.DS_Store
+.vscode
+.envrc
\ No newline at end of file
diff --git a/testdata/okta_provider/.terraform.d/plugin-cache/.gitignore b/testdata/okta_provider/.terraform.d/plugin-cache/.gitignore
new file mode 100644
index 00000000..504d4d91
--- /dev/null
+++ b/testdata/okta_provider/.terraform.d/plugin-cache/.gitignore
@@ -0,0 +1,5 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+*
+!.gitignore
diff --git a/testdata/okta_provider/Makefile b/testdata/okta_provider/Makefile
new file mode 100644
index 00000000..3a9d1870
--- /dev/null
+++ b/testdata/okta_provider/Makefile
@@ -0,0 +1,113 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+ENVS=bar
+MODULES=
+ACCOUNTS=foo
+
+all: check
+
+lint: lint-terraform
+
+lint-terraform: lint-accounts lint-envs lint-modules
+
+lint-accounts:
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account lint || exit $$?; \
+	done
+
+lint-envs:
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env lint || exit $$?; \
+	done; \
+	$(MAKE) -C terraform/global lint || exit $$?
+
+lint-modules:
+	@for module in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module lint || exit $$?; \
+	done
+
+fmt: fmt-accounts fmt-envs fmt-global fmt-modules
+
+fmt-accounts:
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account fmt || exit $$?; \
+	done
+
+fmt-envs:
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env fmt || exit $$?; \
+	done
+
+fmt-global:
+	$(MAKE) -C terraform/global fmt || exit $$?
+
+fmt-modules:
+	@for module in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module fmt || exit $$?; \
+	done
+
+docs: docs-envs docs-global docs-modules
+
+docs-accounts:
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account docs || exit $$?; \
+	done
+
+docs-envs:
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env docs || exit $$?; \
+	done
+
+docs-global:
+	$(MAKE) -C terraform/global docs || exit $$?;
+
+docs-modules:
+	@for module in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module docs || exit $$?; \
+	done
+
+clean:
+
+test:
+
+check: check-plan check-docs
+
+check-plan: check-plan-accounts check-plan-envs check-plan-global
+
+check-plan-accounts:
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account check-plan || exit $$?; \
+	done
+
+check-plan-envs:
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env check-plan || exit $$?; \
+	done
+
+check-plan-global:
+	$(MAKE) -C terraform/global check-plan || exit $$?
+
+check-docs:
+	@for mod in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module check-docs || exit $$?; \
+	done
+
+clean: clean-accounts clean-envs clean-global
+
+clean-accounts:
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account clean || exit $$?; \
+	done
+
+clean-envs:
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env clean || exit $$?; \
+	done
+
+clean-global:
+	$(MAKE) -C terraform/global clean || exit $$?
+
+.PHONY: all check check-docs check-plan check-plan-accounts clean docs docs-accounts docs-envs docs-modules fmt fmt-accounts fmt-envs fmt-modules lint lint-accounts lint-envs lint-modules lint-terraform setup test
+
+-include *.mk
diff --git a/testdata/okta_provider/README.md b/testdata/okta_provider/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/okta_provider/fogg.json b/testdata/okta_provider/fogg.json
new file mode 100644
index 00000000..f339f6f3
--- /dev/null
+++ b/testdata/okta_provider/fogg.json
@@ -0,0 +1,29 @@
+{
+    "version": 2,
+    "defaults": {
+        "providers": {
+            "okta": {
+                "version": "aversion",
+                "org_name": "orgname"
+            }
+        },
+        "backend": {
+            "bucket": "bucket",
+            "region": "region",
+            "profile": "foofoo"
+        },
+        "project": "foofoo",
+        "owner": "foo@example.com",
+        "terraform_version": "1.1.1"
+    },
+    "accounts": {
+        "foo": {}
+    },
+    "envs": {
+        "bar": {
+            "components": {
+                "bam": {}
+            }
+        }
+    }
+}
diff --git a/testdata/okta_provider/scripts/common.mk b/testdata/okta_provider/scripts/common.mk
new file mode 100644
index 00000000..86c12b7e
--- /dev/null
+++ b/testdata/okta_provider/scripts/common.mk
@@ -0,0 +1,30 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+TF_VARS := $(patsubst %,-e%,$(filter TF_VAR_%,$(.VARIABLES)))
+REPO_ROOT := $(shell git rev-parse --show-toplevel)
+REPO_RELATIVE_PATH := $(shell git rev-parse --show-prefix)
+AUTO_APPROVE := false
+# We need to do this because `terraform fmt` recurses into .terraform/modules
+# and wont' accept more than one file at a time.
+TF=$(wildcard *.tf)
+
+TFENV_DIR ?= $(HOME)/.tfenv
+export PATH :=$(TFENV_DIR)/versions/$(TERRAFORM_VERSION)/:$(REPO_ROOT)/.fogg/bin:$(PATH)
+export TF_PLUGIN_CACHE_DIR=$(REPO_ROOT)/.terraform.d/plugin-cache
+sh_command ?= $(SHELL)
+export TF_IN_AUTOMATION=1
+terraform_command ?= $(TFENV_DIR)/versions/$(TERRAFORM_VERSION)/terraform
+
+
+tfenv:
+	if [ ! -d ${TFENV_DIR} ]; then \
+		echo; \
+		git clone https://github.com/tfutils/tfenv.git $(TFENV_DIR); \
+	fi
+
+.PHONY: tfenv
+
+terraform: tfenv
+	${TFENV_DIR}/bin/tfenv install $(TERRAFORM_VERSION)
+.PHONY: terraform
diff --git a/testdata/okta_provider/scripts/component.mk b/testdata/okta_provider/scripts/component.mk
new file mode 100644
index 00000000..594636c5
--- /dev/null
+++ b/testdata/okta_provider/scripts/component.mk
@@ -0,0 +1,65 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+SELF_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+
+include $(SELF_DIR)/common.mk
+
+all:
+
+check: lint check-plan
+
+fmt: terraform
+	$(sh_command) -c 'for f in $(TF); do printf .; terraform fmt $$f; done'; \
+	echo
+
+lint: terraform-validate lint-terraform-fmt lint-tflint
+
+lint-tflint: init
+	if (( $$TFLINT_ENABLED )); then \
+    $(sh_command) -c 'tflint' || exit $$?; \
+	fi \
+
+terraform-validate: terraform init
+	$(sh_command) -c 'terraform validate -check-variables=true'
+
+lint-terraform-fmt: terraform
+	$(sh_command) -c 'for f in $(TF); do printf .; terraform fmt --check=true --diff=true $$f || exit $$? ; done'
+
+get: ssh-forward terraform
+	$(terraform_command) get --update=true
+
+plan: terraform fmt get init ssh-forward
+	$(terraform_command) plan
+
+apply: terraform fmt get init ssh-forward
+	$(terraform_command) apply -auto-approve=$(AUTO_APPROVE)
+
+docs:
+	echo
+
+clean:
+	-rm -rfv .terraform/modules
+	-rm -rfv .terraform/plugins
+
+test:
+
+init: terraform ssh-forward
+	$(terraform_command) init -input=false
+
+check-plan: terraform init get ssh-forward
+	$(terraform_command) plan -detailed-exitcode; \
+	ERR=$$?; \
+	if [ $$ERR -eq 0 ] ; then \
+		echo "Success"; \
+	elif [ $$ERR -eq 1 ] ; then \
+		echo "Error in plan execution."; \
+		exit 1; \
+	elif [ $$ERR -eq 2 ] ; then \
+		echo "Diff";  \
+	fi
+
+run:
+	$(terraform_command) $(CMD)
+
+.PHONY: all apply clean docs fmt get lint plan run ssh-forward test
diff --git a/testdata/okta_provider/scripts/module.mk b/testdata/okta_provider/scripts/module.mk
new file mode 100644
index 00000000..88ee140f
--- /dev/null
+++ b/testdata/okta_provider/scripts/module.mk
@@ -0,0 +1,36 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+SELF_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+
+include $(SELF_DIR)/common.mk
+
+all: fmt lint doc
+
+fmt: terraform
+	$(sh_command) -c 'for f in $(TF); do printf .; terraform fmt $$f; done'; \
+	echo
+
+check: lint check-docs
+
+lint: lint-tf
+
+lint-tf: terraform
+	@$(sh_command) -c 'for f in $(TF); do printf .; terraform fmt --check=true --diff=true $$f || exit $$? ; done'
+
+readme:
+	bash $(REPO_ROOT)/scripts/update-readme.sh update
+
+docs: readme
+
+check-docs:
+	@bash $(REPO_ROOT)/scripts/update-readme.sh check; \
+	if [ ! $$? -eq 0 ];  then \
+		echo "Docs are out of date, run \`make docs\`"; \
+	fi
+
+clean:
+
+test:
+
+.PHONY: all check-doc clean docs fmt lint lint-tf readme test
diff --git a/testdata/okta_provider/scripts/update-readme.sh b/testdata/okta_provider/scripts/update-readme.sh
new file mode 100644
index 00000000..abe8fb47
--- /dev/null
+++ b/testdata/okta_provider/scripts/update-readme.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+# I would have written this directly in the Makefile, but that was difficult.
+
+CMD="$1"
+
+TMP=`mktemp`
+TMP2=`mktemp`
+terraform-docs md . > "$TMP"
+sed '/^<!-- START -->$/,/<!-- END -->/{//!d;}' README.md | sed "/^<!-- START -->$/r $TMP" > $TMP2
+
+case "$CMD" in
+    update)
+        mv $TMP2 README.md
+    ;;
+    check)
+        diff $TMP2 README.md >/dev/null
+    ;;
+esac
+
+exit $?
diff --git a/testdata/okta_provider/terraform/accounts/foo/Makefile b/testdata/okta_provider/terraform/accounts/foo/Makefile
new file mode 100644
index 00000000..b78db9cc
--- /dev/null
+++ b/testdata/okta_provider/terraform/accounts/foo/Makefile
@@ -0,0 +1,9 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+export TERRAFORM_VERSION := 1.1.1
+export TFLINT_ENABLED := 0
+export TF_PLUGIN_CACHE_DIR := ../../..//.terraform.d/plugin-cache
+
+
+include ../../..//scripts/component.mk
diff --git a/testdata/okta_provider/terraform/accounts/foo/fogg.tf b/testdata/okta_provider/terraform/accounts/foo/fogg.tf
new file mode 100644
index 00000000..db3aee63
--- /dev/null
+++ b/testdata/okta_provider/terraform/accounts/foo/fogg.tf
@@ -0,0 +1,48 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+// https://github.com/articulate/terraform-provider-okta
+provider "okta" {
+  version  = "~>aversion"
+  org_name = "orgname"
+}
+
+terraform {
+  required_version = "=1.1.1"
+
+  backend "s3" {
+    bucket = "bucket"
+
+    key     = "terraform/foofoo/accounts/foo.tfstate"
+    encrypt = true
+    region  = "region"
+    profile = "foofoo"
+  }
+}
+
+variable "project" {
+  type    = "string"
+  default = "foofoo"
+}
+
+variable "owner" {
+  type    = "string"
+  default = "foo@example.com"
+}
+
+variable "aws_accounts" {
+  type    = "map"
+  default = {}
+}
+
+data "terraform_remote_state" "global" {
+  backend = "s3"
+
+  config = {
+    bucket = "bucket"
+
+    key     = "terraform/foofoo/global.tfstate"
+    region  = "region"
+    profile = "foofoo"
+  }
+}
diff --git a/testdata/okta_provider/terraform/accounts/foo/terraform.d b/testdata/okta_provider/terraform/accounts/foo/terraform.d
new file mode 120000
index 00000000..29617d28
--- /dev/null
+++ b/testdata/okta_provider/terraform/accounts/foo/terraform.d
@@ -0,0 +1,1 @@
+../../../terraform.d
\ No newline at end of file
diff --git a/testdata/okta_provider/terraform/envs/bar/Makefile b/testdata/okta_provider/terraform/envs/bar/Makefile
new file mode 100644
index 00000000..163ab33b
--- /dev/null
+++ b/testdata/okta_provider/terraform/envs/bar/Makefile
@@ -0,0 +1,37 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+COMPONENTS=bam
+
+all:
+
+lint:
+	@for c in $(COMPONENTS); do \
+		$(MAKE) -C $$c lint || exit $$? ; \
+	done
+
+fmt:
+	@for c in $(COMPONENTS); do \
+		$(MAKE) -C $$c fmt || exit $$? ; \
+	done
+
+check-plan:
+	@for c in $(COMPONENTS); do \
+		$(MAKE) -C $$c check-plan || exit $$? ; \
+	done
+
+docs:
+	@for c in $(COMPONENTS); do \
+		$(MAKE) -C $$c docs || exit $$? ; \
+	done
+
+clean:
+	@for c in $(COMPONENTS); do \
+		$(MAKE) -C $$c clean || exit $$? ; \
+	done
+
+clean:
+
+test:
+
+.PHONY: all lint fmt docs clean test check-plan
diff --git a/testdata/okta_provider/terraform/envs/bar/README.md b/testdata/okta_provider/terraform/envs/bar/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/okta_provider/terraform/envs/bar/bam/Makefile b/testdata/okta_provider/terraform/envs/bar/bam/Makefile
new file mode 100644
index 00000000..f9d21e1d
--- /dev/null
+++ b/testdata/okta_provider/terraform/envs/bar/bam/Makefile
@@ -0,0 +1,8 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+export TERRAFORM_VERSION := 1.1.1
+export TFLINT_ENABLED := 0
+export TF_PLUGIN_CACHE_DIR := ../../../..//.terraform.d/plugin-cache
+
+include ../../../..//scripts/component.mk
diff --git a/testdata/okta_provider/terraform/envs/bar/bam/README.md b/testdata/okta_provider/terraform/envs/bar/bam/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/okta_provider/terraform/envs/bar/bam/fogg.tf b/testdata/okta_provider/terraform/envs/bar/bam/fogg.tf
new file mode 100644
index 00000000..de24dfda
--- /dev/null
+++ b/testdata/okta_provider/terraform/envs/bar/bam/fogg.tf
@@ -0,0 +1,86 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+// https://github.com/articulate/terraform-provider-okta
+provider "okta" {
+  version  = "~>aversion"
+  org_name = "orgname"
+}
+
+terraform {
+  required_version = "~>1.1.1"
+
+  backend "s3" {
+    bucket = "bucket"
+
+    key = "terraform/foofoo/envs/bar/components/bam.tfstate"
+
+    encrypt = true
+    region  = "region"
+    profile = "foofoo"
+  }
+}
+
+variable "env" {
+  type    = "string"
+  default = "bar"
+}
+
+variable "project" {
+  type    = "string"
+  default = "foofoo"
+}
+
+variable "component" {
+  type    = "string"
+  default = "bam"
+}
+
+variable "owner" {
+  type    = "string"
+  default = "foo@example.com"
+}
+
+variable "tags" {
+  type = "map"
+
+  default = {
+    project   = "foofoo"
+    env       = "bar"
+    service   = "bam"
+    owner     = "foo@example.com"
+    managedBy = "terraform"
+  }
+}
+
+data "terraform_remote_state" "global" {
+  backend = "s3"
+
+  config = {
+    bucket = "bucket"
+
+    key     = "terraform/foofoo/global.tfstate"
+    region  = "region"
+    profile = "foofoo"
+  }
+}
+
+# remote state for accounts
+
+data "terraform_remote_state" "foo" {
+  backend = "s3"
+
+  config = {
+    bucket = "bucket"
+
+    key     = "terraform/foofoo/accounts/foo.tfstate"
+    region  = "region"
+    profile = "foofoo"
+  }
+}
+
+# map of aws_accounts
+variable "aws_accounts" {
+  type    = "map"
+  default = {}
+}
diff --git a/testdata/okta_provider/terraform/envs/bar/bam/main.tf b/testdata/okta_provider/terraform/envs/bar/bam/main.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/testdata/okta_provider/terraform/envs/bar/bam/main.tf
@@ -0,0 +1,1 @@
+
diff --git a/testdata/okta_provider/terraform/envs/bar/bam/outputs.tf b/testdata/okta_provider/terraform/envs/bar/bam/outputs.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/testdata/okta_provider/terraform/envs/bar/bam/outputs.tf
@@ -0,0 +1,1 @@
+
diff --git a/testdata/okta_provider/terraform/envs/bar/bam/terraform.d b/testdata/okta_provider/terraform/envs/bar/bam/terraform.d
new file mode 120000
index 00000000..b482d75b
--- /dev/null
+++ b/testdata/okta_provider/terraform/envs/bar/bam/terraform.d
@@ -0,0 +1,1 @@
+../../../../terraform.d
\ No newline at end of file
diff --git a/testdata/okta_provider/terraform/envs/bar/bam/variables.tf b/testdata/okta_provider/terraform/envs/bar/bam/variables.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/testdata/okta_provider/terraform/envs/bar/bam/variables.tf
@@ -0,0 +1,1 @@
+
diff --git a/testdata/okta_provider/terraform/global/Makefile b/testdata/okta_provider/terraform/global/Makefile
new file mode 100644
index 00000000..2af76d40
--- /dev/null
+++ b/testdata/okta_provider/terraform/global/Makefile
@@ -0,0 +1,8 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+export TERRAFORM_VERSION := 1.1.1
+export TFLINT_ENABLED := 0
+export TF_PLUGIN_CACHE_DIR := ../..//.terraform.d/plugin-cache
+
+include ../..//scripts/component.mk
diff --git a/testdata/okta_provider/terraform/global/README.md b/testdata/okta_provider/terraform/global/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/okta_provider/terraform/global/fogg.tf b/testdata/okta_provider/terraform/global/fogg.tf
new file mode 100644
index 00000000..c84ee65e
--- /dev/null
+++ b/testdata/okta_provider/terraform/global/fogg.tf
@@ -0,0 +1,54 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+// https://github.com/articulate/terraform-provider-okta
+provider "okta" {
+  version  = "~>aversion"
+  org_name = "orgname"
+}
+
+terraform {
+  required_version = "~>1.1.1"
+
+  backend "s3" {
+    bucket = "bucket"
+
+    key = "terraform/foofoo/global.tfstate"
+
+    encrypt = true
+    region  = "region"
+    profile = "foofoo"
+  }
+}
+
+variable "env" {
+  type    = "string"
+  default = ""
+}
+
+variable "project" {
+  type    = "string"
+  default = "foofoo"
+}
+
+variable "component" {
+  type    = "string"
+  default = "global"
+}
+
+variable "owner" {
+  type    = "string"
+  default = "foo@example.com"
+}
+
+variable "tags" {
+  type = "map"
+
+  default = {
+    project   = "foofoo"
+    env       = ""
+    service   = "global"
+    owner     = "foo@example.com"
+    managedBy = "terraform"
+  }
+}
diff --git a/testdata/okta_provider/terraform/global/main.tf b/testdata/okta_provider/terraform/global/main.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/testdata/okta_provider/terraform/global/main.tf
@@ -0,0 +1,1 @@
+
diff --git a/testdata/okta_provider/terraform/global/outputs.tf b/testdata/okta_provider/terraform/global/outputs.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/testdata/okta_provider/terraform/global/outputs.tf
@@ -0,0 +1,1 @@
+
diff --git a/testdata/okta_provider/terraform/global/terraform.d b/testdata/okta_provider/terraform/global/terraform.d
new file mode 120000
index 00000000..a1f7d0d3
--- /dev/null
+++ b/testdata/okta_provider/terraform/global/terraform.d
@@ -0,0 +1,1 @@
+../../terraform.d
\ No newline at end of file
diff --git a/testdata/okta_provider/terraform/global/variables.tf b/testdata/okta_provider/terraform/global/variables.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/testdata/okta_provider/terraform/global/variables.tf
@@ -0,0 +1,1 @@
+
diff --git a/util/template_test.go b/util/template_test.go
index 9588e4e2..3ff77f67 100644
--- a/util/template_test.go
+++ b/util/template_test.go
@@ -32,7 +32,7 @@ func TestOpenTemplate(t *testing.T) {
 		wantErr bool
 	}{
 		// TODO: Add test cases.
-		{"foo", args{temps.Account, "Makefile.tmpl"}, 3, false},
+		{"foo", args{temps.Account, "Makefile.tmpl"}, 4, false},
 	}
 
 	for _, tt := range tests {