Repository: chanzuckerberg/fogg
Pull request #730 (2022-09-16 23:22:27)

feat: fogg create tfe folder
### Summary
This PR adds a feature to fogg that adds an optional tfe/ folder to synchronize fogg env/accounts with tfe workspaces.


Patch:
diff --git a/apply/apply.go b/apply/apply.go
index 12a9c353..f5422fae 100644
--- a/apply/apply.go
+++ b/apply/apply.go
@@ -30,7 +30,7 @@ import (
 const rootPath = "terraform"
 
 // Apply will run a plan and apply all the changes to the current repo.
-func Apply(fs afero.Fs, conf *v2.Config, tmp *templates.T, upgrade bool) error {
+func Apply(fs afero.Fs, conf *v2.Config, tmpl *templates.T, upgrade bool) error {
 	if !upgrade {
 		toolVersion, err := util.VersionString()
 		if err != nil {
@@ -45,55 +45,54 @@ func Apply(fs afero.Fs, conf *v2.Config, tmp *templates.T, upgrade bool) error {
 	if err != nil {
 		return errs.WrapUser(err, "unable to evaluate plan")
 	}
-	err = applyRepo(fs, plan, tmp.Repo, tmp.Common)
+	err = applyRepo(fs, plan, tmpl.Repo, tmpl.Common)
 	if err != nil {
 		return errs.WrapUser(err, "unable to apply repo")
 	}
 
 	if plan.TravisCI.Enabled {
-		err = applyTree(fs, tmp.TravisCI, tmp.Common, "", plan.TravisCI)
+		err = applyTree(fs, tmpl.TravisCI, tmpl.Common, "", plan.TravisCI)
 		if err != nil {
 			return errs.WrapUser(err, "unable to apply travis ci")
 		}
 	}
 
 	if plan.CircleCI.Enabled {
-		err = applyTree(fs, tmp.CircleCI, tmp.Common, "", plan.CircleCI)
+		err = applyTree(fs, tmpl.CircleCI, tmpl.Common, "", plan.CircleCI)
 		if err != nil {
 			return errs.WrapUser(err, "unable to apply CircleCI")
 		}
 	}
 
 	if plan.GitHubActionsCI.Enabled {
-		err = applyTree(fs, tmp.GitHubActionsCI, tmp.Common, ".github", plan.GitHubActionsCI)
+		err = applyTree(fs, tmpl.GitHubActionsCI, tmpl.Common, ".github", plan.GitHubActionsCI)
 		if err != nil {
 			return errs.WrapUser(err, "unable to apply GitHub Actions CI")
 		}
 	}
 
-	tfBox := tmp.Components[v2.ComponentKindTerraform]
-	err = applyAccounts(fs, plan, tfBox, tmp.Common)
+	tfBox := tmpl.Components[v2.ComponentKindTerraform]
+	err = applyAccounts(fs, plan, tfBox, tmpl.Common)
 	if err != nil {
 		return errs.WrapUser(err, "unable to apply accounts")
 	}
 
-	err = applyEnvs(fs, plan, tmp.Env, tmp.Components, tmp.Common)
+	err = applyEnvs(fs, plan, tmpl.Env, tmpl.Components, tmpl.Common)
 	if err != nil {
 		return errs.WrapUser(err, "unable to apply envs")
 	}
 
-	tfBox = tmp.Components[v2.ComponentKindTerraform]
-	err = applyGlobal(fs, plan.Global, tfBox, tmp.Common)
+	tfBox = tmpl.Components[v2.ComponentKindTerraform]
+	err = applyGlobal(fs, plan.Global, tfBox, tmpl.Common)
 	if err != nil {
 		return errs.WrapUser(err, "unable to apply global")
 	}
 
-	err = applyModules(fs, plan.Modules, tmp.Module, tmp.Common)
+	err = applyModules(fs, plan.Modules, tmpl.Module, tmpl.Common)
 	if err != nil {
 		return errs.WrapUser(err, "unable to apply modules")
 	}
-
-	return errs.WrapUser(applyTFE(fs, plan), "unable to apply TFE locals.tf.json")
+	return errs.WrapUser(applyTFE(fs, plan, tmpl), "unable to apply TFE locals.tf.json")
 }
 
 type LocalsTFE struct {
@@ -189,15 +188,7 @@ func updateLocalsFromPlan(locals *LocalsTFE, plan *plan.Plan) {
 	}
 }
 
-func applyTFE(fs afero.Fs, plan *plan.Plan) error {
-	tfePath := filepath.Join("terraform", "tfe", "locals.tf.json")
-	_, err := fs.Stat(tfePath)
-	// if the repo doesn't have a locals.tf.json, don't worry about it
-	if errors.Is(err, os.ErrNotExist) {
-		return nil
-	} else if err != nil {
-		return err
-	}
+func updateLocalsTFEFile(fs afero.Fs, tfePath string, plan *plan.Plan) error {
 	read, err := fs.Open(tfePath)
 	if err != nil {
 		return errors.Wrapf(err, "unable to open locals.tf.json file %s for unmarshalling", tfePath)
@@ -217,12 +208,47 @@ func applyTFE(fs afero.Fs, plan *plan.Plan) error {
 	defer write.Close()
 	encoder := json.NewEncoder(write)
 	encoder.SetIndent("", "  ")
-	err = encoder.Encode(locals)
+	return errors.Wrap(encoder.Encode(locals), "unable to marhsal locals.tf.json")
+}
+
+func applyTFE(fs afero.Fs, plan *plan.Plan, tmpl *templates.T) error {
+	// the TFE configuration is optional
+	if plan.TFE == nil {
+		return nil
+	}
+
+	logrus.Debug("applying tfe")
+	path := fmt.Sprintf("%s/tfe", rootPath)
+	err := fs.MkdirAll(path, 0755)
+	if err != nil {
+		return errors.Wrapf(err, "unable to make directory %s", path)
+	}
+	err = applyTree(fs, tmpl.Components[v2.ComponentKindTerraform], tmpl.Common, path, plan.TFE)
+	if err != nil {
+		return err
+	}
+	err = applyTree(fs, tmpl.TFE, tmpl.Common, path, plan.TFE)
 	if err != nil {
-		return errors.Wrap(err, "unable to marhsal locals.tf.json")
+		return err
+	}
+	if plan.TFE.ModuleSource != nil {
+		downloader, err := util.MakeDownloader(*plan.TFE.ModuleSource)
+		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)
+		if err != nil {
+			return errs.WrapUser(err, "unable to apply module invocation")
+		}
 	}
 
-	return nil
+	tfePath := filepath.Join("terraform", "tfe", "locals.tf.json")
+	_, err = fs.Stat(tfePath)
+	if err != nil {
+		return errors.Wrapf(err, "unable to stat on %s", tfePath)
+	}
+
+	return updateLocalsTFEFile(fs, tfePath, plan)
 }
 
 func checkToolVersions(fs afero.Fs, current string) (bool, string, error) {
diff --git a/config/v2/config.go b/config/v2/config.go
index c8ec2f8a..64f98492 100644
--- a/config/v2/config.go
+++ b/config/v2/config.go
@@ -15,7 +15,7 @@ import (
 	yaml "gopkg.in/yaml.v3"
 )
 
-//ReadConfig take a byte array as input and outputs a json or yaml config struct
+// ReadConfig take a byte array as input and outputs a json or yaml config struct
 func ReadConfig(fs afero.Fs, b []byte, configFile string) (*Config, error) {
 	var e error
 	c := &Config{}
@@ -60,6 +60,19 @@ type Config struct {
 	Modules  map[string]Module  `yaml:"modules,omitempty"`
 	Plugins  Plugins            `yaml:"plugins,omitempty"`
 	Version  int                `validate:"required,eq=2"`
+	TFE      *TFE               `yaml:"tfe,omitempty"`
+}
+
+type TFE struct {
+	Component                      `yaml:",inline"`
+	ReadTeams                      *[]string `yaml:"read_teams,omitempty"`
+	Branch                         *string   `yaml:"branch,omitempty"`
+	GithubOrg                      *string   `yaml:"gh_org,omitempty"`
+	GithubRepo                     *string   `yaml:"gh_repo,omitempty"`
+	TFEOrg                         string    `yaml:"tfe_org,omitempty"`
+	SSHKeyName                     *string   `yaml:"ssh_key_name,omitempty"`
+	ExcludedGithubRequiredChecks   *[]string `yaml:"excluded_gh_required_checks,omitempty"`
+	AdditionalGithubRequiredChecks *[]string `yaml:"additional_gh_required_checks,omitempty"`
 }
 
 type Common struct {
@@ -145,7 +158,7 @@ type CommonProvider struct {
 	Version *string `yaml:"version,omitempty"`
 }
 
-//Auth0Provider is the terraform provider for the Auth0 service.
+// Auth0Provider is the terraform provider for the Auth0 service.
 type Auth0Provider struct {
 	Version *string `yaml:"version,omitempty"`
 	Domain  *string `yaml:"domain,omitempty"`
@@ -238,7 +251,7 @@ type KubernetesProvider struct {
 	CommonProvider `yaml:",inline"`
 }
 
-//Backend is used to configure a terraform backend
+// Backend is used to configure a terraform backend
 type Backend struct {
 	Kind *string `yaml:"kind,omitempty" validate:"omitempty,oneof=s3 remote"`
 
diff --git a/config/v2/validation.go b/config/v2/validation.go
index d308b308..524fa82a 100644
--- a/config/v2/validation.go
+++ b/config/v2/validation.go
@@ -8,6 +8,7 @@ import (
 	"github.com/chanzuckerberg/fogg/errs"
 	multierror "github.com/hashicorp/go-multierror"
 	goVersion "github.com/hashicorp/go-version"
+	"github.com/pkg/errors"
 	validator "gopkg.in/go-playground/validator.v9"
 )
 
@@ -52,6 +53,7 @@ func (c *Config) Validate() ([]string, error) {
 	errs = multierror.Append(errs, c.validateModules())
 	errs = multierror.Append(errs, c.ValidateTravis())
 	errs = multierror.Append(errs, c.ValidateGithubActionsCI())
+	errs = multierror.Append(errs, c.validateTFE())
 
 	// refactor to make it easier to manage these
 	w, e := c.ValidateToolsTfLint()
@@ -274,6 +276,16 @@ func (c *Config) ValidateTravis() error {
 	return errs
 }
 
+func (c *Config) validateTFE() error {
+	var errs *multierror.Error
+
+	if c.TFE != nil && c.TFE.TFEOrg == "" {
+		errs = multierror.Append(errs, errors.Errorf("tfe org is required"))
+	}
+
+	return errs
+}
+
 func (c *Config) ValidateGithubActionsCI() error {
 	var errs *multierror.Error
 	c.WalkComponents(func(component string, comms ...Common) {
diff --git a/go.mod b/go.mod
index 60075c3f..7d0c6f7c 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
 	github.com/davecgh/go-spew v1.1.1
 	github.com/fatih/color v1.10.0
 	github.com/go-errors/errors v1.1.1
+	github.com/go-git/go-git/v5 v5.4.2
 	github.com/google/go-github/v27 v27.0.6
 	github.com/hashicorp/go-getter v1.6.2
 	github.com/hashicorp/go-multierror v1.1.1
@@ -43,6 +44,9 @@ require (
 	cloud.google.com/go/storage v1.10.0 // indirect
 	github.com/Masterminds/goutils v1.1.0 // indirect
 	github.com/Masterminds/semver v1.5.0 // indirect
+	github.com/Microsoft/go-winio v0.4.16 // indirect
+	github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
+	github.com/acomagu/bufpipe v1.0.3 // indirect
 	github.com/agext/levenshtein v1.2.3 // indirect
 	github.com/apparentlymart/go-cidr v1.1.0 // indirect
 	github.com/apparentlymart/go-textseg v1.0.0 // indirect
@@ -50,6 +54,9 @@ require (
 	github.com/apparentlymart/go-versions v1.0.1 // indirect
 	github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
 	github.com/bmatcuk/doublestar v1.2.4 // indirect
+	github.com/emirpasic/gods v1.12.0 // indirect
+	github.com/go-git/gcfg v1.5.0 // indirect
+	github.com/go-git/go-billy/v5 v5.3.1 // indirect
 	github.com/go-playground/locales v0.13.0 // indirect
 	github.com/go-playground/universal-translator v0.17.0 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
@@ -68,10 +75,12 @@ require (
 	github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
 	github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
 	github.com/huandu/xstrings v1.3.1 // indirect
-	github.com/imdario/mergo v0.3.9 // indirect
+	github.com/imdario/mergo v0.3.12 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
+	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jstemmer/go-junit-report v0.9.1 // indirect
+	github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
 	github.com/klauspost/compress v1.11.2 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/leodido/go-urn v1.2.0 // indirect
@@ -82,9 +91,11 @@ require (
 	github.com/mitchellh/go-wordwrap v1.0.1 // indirect
 	github.com/mitchellh/reflectwalk v1.0.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/ulikunitz/xz v0.5.8 // indirect
 	github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
 	github.com/vmihailenco/tagparser v0.1.1 // indirect
+	github.com/xanzy/ssh-agent v0.3.0 // indirect
 	github.com/zclconf/go-cty v1.8.0 // indirect
 	github.com/zclconf/go-cty-yaml v1.0.2 // indirect
 	go.opencensus.io v0.22.4 // indirect
@@ -103,4 +114,5 @@ require (
 	google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
 	google.golang.org/grpc v1.31.1 // indirect
 	google.golang.org/protobuf v1.25.0 // indirect
+	gopkg.in/warnings.v0 v0.1.2 // indirect
 )
diff --git a/go.sum b/go.sum
index 9cc7b318..fbd0c8e3 100644
--- a/go.sum
+++ b/go.sum
@@ -89,9 +89,13 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0
 github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
 github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
+github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@@ -99,6 +103,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
 github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
 github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
+github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
+github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
 github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
 github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
 github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
@@ -109,6 +115,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA=
 github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
 github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
 github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
 github.com/antzucaro/matchr v0.0.0-20191224151129-ab6ba461ddec h1:uurd2LiNfcarvGB05LUzvGzPoNr5eRgC92WwdXoK7Qs=
@@ -131,6 +139,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-lambda-go v1.19.1/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU=
 github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
@@ -227,6 +237,8 @@ github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/El
 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -246,6 +258,7 @@ github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
@@ -254,11 +267,22 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
 github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
+github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
+github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
+github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
+github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
+github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -471,8 +495,9 @@ github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -480,7 +505,10 @@ github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbk
 github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
 github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
@@ -517,6 +545,8 @@ github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiD
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
 github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -564,6 +594,8 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
 github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
 github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
 github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
+github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
+github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -630,7 +662,6 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
 github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
 github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI=
 github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
@@ -706,8 +737,9 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/segmentio/go-prompt v1.2.1-0.20161017233205-f0d19b6901ad h1:EqOdoSJGI7CsBQczPcIgmpm3hJE7X8Hj3jrgI002whs=
 github.com/segmentio/go-prompt v1.2.1-0.20161017233205-f0d19b6901ad/go.mod h1:B3ehdD1xPoWDKgrQgUaGk+m8H1xb1J5TyYDfKpKNeEE=
-github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@@ -784,6 +816,8 @@ github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37w
 github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
 github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@@ -844,6 +878,8 @@ golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 h1:O8uGbHCqlTp2P6QJSLmCojM4mN6UemYv8K+dCnmHmu0=
 golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -928,6 +964,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
@@ -976,6 +1014,7 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1001,8 +1040,11 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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=
@@ -1187,8 +1229,9 @@ gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -1211,12 +1254,15 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
 gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/plan/plan.go b/plan/plan.go
index 06a61b0f..32d247ba 100644
--- a/plan/plan.go
+++ b/plan/plan.go
@@ -2,14 +2,19 @@ package plan
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
+	"path/filepath"
 	"sort"
+	"strings"
+
+	"github.com/pkg/errors"
 
 	v2 "github.com/chanzuckerberg/fogg/config/v2"
 	"github.com/chanzuckerberg/fogg/errs"
 	"github.com/chanzuckerberg/fogg/util"
 	"github.com/chanzuckerberg/go-misc/ptr"
+
+	"github.com/go-git/go-git/v5"
 	"github.com/sirupsen/logrus"
 	yaml "gopkg.in/yaml.v3"
 )
@@ -24,6 +29,7 @@ type Plan struct {
 	CircleCI        CircleCIConfig        `yaml:"circleci_ci"`
 	GitHubActionsCI GitHubActionsCIConfig `yaml:"github_actions_ci"`
 	Version         string                `yaml:"version"`
+	TFE             *TFEConfig            `yaml:"tfe"`
 }
 
 // Common represents common fields
@@ -32,7 +38,7 @@ type Common struct {
 	TerraformVersion string `yaml:"terraform_version"`
 }
 
-//ComponentCommon represents common fields for components
+// ComponentCommon represents common fields for components
 type ComponentCommon struct {
 	Common `yaml:",inline"`
 
@@ -177,7 +183,7 @@ var utilityProviders = map[string]ProviderVersion{
 	},
 }
 
-//AWSProvider represents AWS provider configuration
+// AWSProvider represents AWS provider configuration
 type AWSProvider struct {
 	AccountID json.Number `yaml:"account_id"`
 	Alias     *string     `yaml:"alias"`
@@ -217,20 +223,20 @@ type AssertProvider struct {
 	Version string `yaml:"version,omitempty"`
 }
 
-//SnowflakeProvider represents Snowflake DB provider configuration
+// SnowflakeProvider represents Snowflake DB provider configuration
 type SnowflakeProvider struct {
 	Account string `yaml:"account,omitempty"`
 	Role    string `yaml:"role,omitempty"`
 	Region  string `yaml:"region,omitempty"`
 }
 
-//OktaProvider represents Okta configuration
+// OktaProvider represents Okta configuration
 type OktaProvider struct {
 	OrgName string  `yaml:"org_name,omitempty"`
 	BaseURL *string `yaml:"base_url,omitempty"`
 }
 
-//BlessProvider represents Bless ssh provider configuration
+// BlessProvider represents Bless ssh provider configuration
 type BlessProvider struct {
 	AdditionalRegions []string `yaml:"additional_regions,omitempty"`
 	AWSProfile        *string  `yaml:"aws_profile,omitempty"`
@@ -269,8 +275,8 @@ const (
 	BackendKindRemote BackendKind = "remote"
 )
 
-//Backend represents a plan for configuring the terraform backend. Only one struct member can be
-//non-nil at a time
+// Backend represents a plan for configuring the terraform backend. Only one struct member can be
+// non-nil at a time
 type Backend struct {
 	Kind   BackendKind    `yaml:"kind"`
 	S3     *S3Backend     `yaml:"s3,omitempty"`
@@ -356,7 +362,10 @@ func Eval(c *v2.Config) (*Plan, error) {
 	p.TravisCI = p.buildTravisCIConfig(c, v)
 	p.CircleCI = p.buildCircleCIConfig(c, v)
 	p.GitHubActionsCI = p.buildGitHubActionsConfig(c, v)
-
+	p.TFE, err = p.buildTFE(c)
+	if err != nil {
+		return p, err
+	}
 	return p, nil
 }
 
@@ -370,6 +379,94 @@ func Print(p *Plan) error {
 	return nil
 }
 
+func parseGithubOrgRepoFromGit(path string) (string, string, error) {
+	repo, err := git.PlainOpen(path)
+	if err != nil {
+		return "", "", errors.Wrap(err, "unable to open the git repo")
+	}
+	remotes, err := repo.Remotes()
+	if err != nil {
+		return "", "", errors.Wrap(err, "unable to list the remotes of the repo")
+	}
+	for _, remote := range remotes {
+		if remote.Config().Name != "origin" {
+			continue
+		}
+
+		for _, u := range remote.Config().URLs {
+			remoteSplit := strings.Split(u, ":")
+			if len(remoteSplit) != 2 {
+				return "", "", errors.Errorf("unexpected syntax in git remote URL %s", u)
+			}
+			baseName := filepath.Base(remoteSplit[1])
+			return filepath.Dir(remoteSplit[1]), strings.TrimSuffix(baseName, filepath.Ext(remoteSplit[1])), nil
+		}
+	}
+
+	return "", "", errors.New("unable to find a valid origin remote URL")
+}
+
+func (p *Plan) buildTFE(c *v2.Config) (*TFEConfig, error) {
+	if c.TFE == nil {
+		return nil, nil
+	}
+
+	tfeConfig := &TFEConfig{
+		ReadTeams:                      []string{},
+		Branch:                         "main",
+		GithubOrg:                      "",
+		GithubRepo:                     "",
+		TFEOrg:                         c.TFE.TFEOrg,
+		SSHKeyName:                     "fogg-ssh-key",
+		ExcludedGithubRequiredChecks:   []string{},
+		AdditionalGithubRequiredChecks: []string{},
+	}
+	tfeConfig.ComponentCommon = resolveComponentCommon(c.Defaults.Common, c.Global.Common, c.TFE.Common)
+	tfeConfig.ModuleSource = c.TFE.ModuleSource
+	tfeConfig.ModuleName = c.TFE.ModuleName
+
+	if tfeConfig.ComponentCommon.Backend.Kind == BackendKindS3 {
+		tfeConfig.ComponentCommon.Backend.S3.KeyPath = fmt.Sprintf("terraform/%s/%s.tfstate", "tfe", "tfe")
+	} else if tfeConfig.ComponentCommon.Backend.Kind == BackendKindRemote {
+		tfeConfig.ComponentCommon.Backend.Remote.Workspace = "tfe"
+	} else {
+		panic(fmt.Sprintf("Invalid backend kind of %s", tfeConfig.ComponentCommon.Backend.Kind))
+	}
+
+	if c.TFE.ReadTeams != nil {
+		tfeConfig.ReadTeams = *c.TFE.ReadTeams
+	}
+	if c.TFE.Branch != nil {
+		tfeConfig.Branch = *c.TFE.Branch
+	}
+	if c.TFE.GithubOrg != nil {
+		tfeConfig.GithubOrg = *c.TFE.GithubOrg
+	}
+	if c.TFE.GithubRepo != nil {
+		tfeConfig.GithubRepo = *c.TFE.GithubRepo
+	}
+	if c.TFE.SSHKeyName != nil {
+		tfeConfig.SSHKeyName = *c.TFE.SSHKeyName
+	}
+	if c.TFE.ExcludedGithubRequiredChecks != nil {
+		tfeConfig.ExcludedGithubRequiredChecks = *c.TFE.ExcludedGithubRequiredChecks
+	}
+	if c.TFE.AdditionalGithubRequiredChecks != nil {
+		tfeConfig.AdditionalGithubRequiredChecks = *c.TFE.AdditionalGithubRequiredChecks
+	}
+
+	if tfeConfig.GithubOrg == "" || tfeConfig.GithubRepo == "" {
+		org, repo, err := parseGithubOrgRepoFromGit(".")
+		if err != nil {
+			return nil, err
+		}
+		tfeConfig.GithubOrg = org
+		tfeConfig.GithubRepo = repo
+	}
+
+	return tfeConfig, nil
+}
+
 func (p *Plan) buildAccounts(c *v2.Config) map[string]Account {
 	defaults := c.Defaults
 
diff --git a/plan/tfe.go b/plan/tfe.go
new file mode 100644
index 00000000..ac7a5aa2
--- /dev/null
+++ b/plan/tfe.go
@@ -0,0 +1,13 @@
+package plan
+
+type TFEConfig struct {
+	Component
+	ReadTeams                      []string
+	Branch                         string
+	GithubOrg                      string
+	GithubRepo                     string
+	TFEOrg                         string
+	SSHKeyName                     string
+	ExcludedGithubRequiredChecks   []string
+	AdditionalGithubRequiredChecks []string
+}
diff --git a/templates/templates.go b/templates/templates.go
index f1e55255..ecd16454 100644
--- a/templates/templates.go
+++ b/templates/templates.go
@@ -9,8 +9,10 @@ import (
 )
 
 // NOTE(el): due to a design decision of go embed, we enumerate files starting with
-//           the following set of characters: {.} giving explicit attention to directories with ONLY
-//           files starting with said characters
+//
+//	the following set of characters: {.} giving explicit attention to directories with ONLY
+//	files starting with said characters
+//
 //go:embed templates/.github/*
 //go:embed templates/circleci/.circleci/*
 //go:embed templates/common/*
@@ -28,6 +30,7 @@ import (
 //go:embed templates/repo/terraform.d/.keep.touch
 //go:embed templates/repo/.terraform.d/plugin-cache/.gitignore
 //go:embed templates/travis-ci/.travis.yml.tmpl
+//go:embed templates/tfe/*
 var templates embed.FS
 
 type T struct {
@@ -40,6 +43,7 @@ type T struct {
 	TravisCI         fs.FS
 	CircleCI         fs.FS
 	GitHubActionsCI  fs.FS
+	TFE              fs.FS
 }
 
 // we control the inputs so should never panic
@@ -64,4 +68,5 @@ var Templates = &T{
 	TravisCI:         mustFSSub("templates/travis-ci"),
 	CircleCI:         mustFSSub("templates/circleci"),
 	GitHubActionsCI:  mustFSSub("templates/.github"),
+	TFE:              mustFSSub("templates/tfe"),
 }
diff --git a/templates/templates/tfe/locals.tf.json.tmpl b/templates/templates/tfe/locals.tf.json.tmpl
new file mode 100644
index 00000000..f3f88725
--- /dev/null
+++ b/templates/templates/tfe/locals.tf.json.tmpl
@@ -0,0 +1,9 @@
+{{- $tfeConfig := . }}
+
+{
+  "locals": {
+    "accounts": {},
+    "envs": {},
+    "default_terraform_version": "{{ $tfeConfig.TerraformVersion }}"
+  }
+}
diff --git a/templates/templates/tfe/locals.tf.tmpl b/templates/templates/tfe/locals.tf.tmpl
new file mode 100644
index 00000000..b918594d
--- /dev/null
+++ b/templates/templates/tfe/locals.tf.tmpl
@@ -0,0 +1,6 @@
+{{- $tfeConfig := . }}
+
+locals {
+  organization = "{{ $tfeConfig.TFEOrg }}"
+  repo = "{{ $tfeConfig.GithubOrg }}/{{ $tfeConfig.GithubRepo }}"
+}


Test patch:
diff --git a/apply/golden_file_test.go b/apply/golden_file_test.go
index f814ac62..755171fe 100644
--- a/apply/golden_file_test.go
+++ b/apply/golden_file_test.go
@@ -34,6 +34,7 @@ func TestIntegration(t *testing.T) {
 		{"circleci"},
 		{"tfe_provider_yaml"},
 		{"remote_backend_yaml"},
+		{"tfe_config"},
 	}
 
 	for _, test := range testCases {
diff --git a/testdata/tfe_config/.fogg-version b/testdata/tfe_config/.fogg-version
new file mode 100644
index 00000000..64e78fd8
--- /dev/null
+++ b/testdata/tfe_config/.fogg-version
@@ -0,0 +1,1 @@
+undefined-pre+undefined.dirty
\ No newline at end of file
diff --git a/testdata/tfe_config/.gitattributes b/testdata/tfe_config/.gitattributes
new file mode 100644
index 00000000..07338539
--- /dev/null
+++ b/testdata/tfe_config/.gitattributes
@@ -0,0 +1,6 @@
+fogg.tf linguist-generated
+Makefile linguist-generated
+.travis.yml linguist-generated
+.circleci/config.yml linguist-generated
+.terraformignore linguist-generated
+.github/workflows/fogg_ci.yml linguist-generated
diff --git a/testdata/tfe_config/.gitignore b/testdata/tfe_config/.gitignore
new file mode 100644
index 00000000..99b345e3
--- /dev/null
+++ b/testdata/tfe_config/.gitignore
@@ -0,0 +1,39 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+# Compiled files
+*.tfstate
+*.tfstate.*.backup
+*.tfstate.backup
+*tfvars
+.terraform.lock.hcl
+
+# Module directory
+.terraform/
+
+# Pycharm folder
+.idea
+
+# Editor Swap Files
+*.swp
+*.swo
+*.swn
+*.swm
+*.swl
+*.swk
+
+.fogg
+/terraform.d/plugins
+/terraform.d/modules
+
+.DS_Store
+.vscode
+.envrc
+
+# Scala language server
+.metals
+
+buildevents.plan
+check-plan.output
+
+venv
diff --git a/testdata/tfe_config/.terraform.d/plugin-cache/.gitignore b/testdata/tfe_config/.terraform.d/plugin-cache/.gitignore
new file mode 100644
index 00000000..504d4d91
--- /dev/null
+++ b/testdata/tfe_config/.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/tfe_config/.terraformignore b/testdata/tfe_config/.terraformignore
new file mode 100644
index 00000000..e472f375
--- /dev/null
+++ b/testdata/tfe_config/.terraformignore
@@ -0,0 +1,10 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+
+.fogg/
+terraform.d/**
+**/terraform.d
+**/.terraform.d/**
+**/.terraform/**
+**/.git/**
diff --git a/testdata/tfe_config/Makefile b/testdata/tfe_config/Makefile
new file mode 100644
index 00000000..fee163ba
--- /dev/null
+++ b/testdata/tfe_config/Makefile
@@ -0,0 +1,150 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+include scripts/common.mk
+
+ENVS=comp1 comp2
+MODULES=
+ACCOUNTS=account
+
+all: check
+
+setup: ## set up working directory by installing dependencies
+	curl -s https://raw.githubusercontent.com/chanzuckerberg/fogg/vundefined-pre+undefined.dirty/download.sh | bash -s -- -b .fogg/bin vundefined-pre+undefined.dirty
+	.fogg/bin/fogg setup
+.PHONY: setup
+
+lint: lint-terraform ## lint the code in this repo
+.PHONY: lint
+
+lint-terraform: lint-accounts lint-envs lint-modules ## lint the terraform code in this repo
+.PHONY: lint-terraform
+
+lint-accounts:  ## lint the terrarform code in terraform/accounts
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account lint || exit $$?; \
+	done
+.PHONY: lint-accounts
+
+lint-envs: ## lint the terraform code in terraform/envs
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env lint || exit $$?; \
+	done; \
+	$(MAKE) -C terraform/global lint || exit $$?
+.PHONY: lint-envs
+
+lint-modules: ## lint the terraform code in terraform/modules
+	@for module in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module lint || exit $$?; \
+	done
+.PHONY: lint-modules
+
+fmt: fmt-fogg fmt-accounts fmt-envs fmt-global fmt-modules ## format code in this repo
+.PHONY: fmt
+
+fmt-fogg:
+	.fogg/bin/fogg fmt
+.PHONY: fmt-fogg
+
+fmt-accounts: ## format code in terraform/accounts
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account fmt || exit $$?; \
+	done
+.PHONY: fmt-accounts
+
+fmt-envs: ## format the code in teraform/envs
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env fmt || exit $$?; \
+	done
+.PHONY: fmt-envs
+
+fmt-global: ## format the code in terraform/global
+	$(MAKE) -C terraform/global fmt || exit $$?
+.PHONY: fmt-global
+
+fmt-modules:  ## format the code in terraform/modules
+	@for module in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module fmt || exit $$?; \
+	done
+.PHONY: fmt-modules
+
+docs: docs-envs docs-global docs-modules ## update docs
+.PHONY: docs
+
+docs-accounts: ## update docs in terraform/accounts
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account docs || exit $$?; \
+	done
+.PHONY: docs-accounts
+
+docs-envs: ## update the docs in terraform/envs
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env docs || exit $$?; \
+	done
+.PHONY: docs-envs
+
+docs-global: ## update the docs in terraform/global
+	$(MAKE) -C terraform/global docs || exit $$?;
+.PHONY: docs-global
+
+docs-modules: ## update the docs in terraform/modules
+	@for module in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module docs || exit $$?; \
+	done
+.PHONY: docs-modules
+
+test:
+.PHONY: test
+
+check: check-plan check-docs ## check all code in the repo
+.PHONY: check
+
+check-plan: check-plan-accounts check-plan-envs check-plan-global ## check terraform plans across all components
+.PHONY: check-plan
+
+check-plan-accounts: ## check terraform plans in terraform/accounts
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account check-plan || exit $$?; \
+	done
+.PHONY: check-plan-accounts
+
+check-plan-envs: ## check terraform plans in terraform/envs
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env check-plan || exit $$?; \
+	done
+.PHONY: check-plan-envs
+
+check-plan-global: ## check terraform plans in terraform/global
+	$(MAKE) -C terraform/global check-plan || exit $$?
+.PHONY: check-plan-global
+
+check-docs: ## check terraform docs
+	@for mod in $(MODULES); do \
+		$(MAKE) -C terraform/modules/$$module check-docs || exit $$?; \
+	done
+.PHONY: check-docs
+
+clean: clean-accounts clean-envs clean-global ## clean the repo
+.PHONY: clean
+
+clean-accounts: ## clean in terraform/accounts
+	@for account in $(ACCOUNTS); do \
+		$(MAKE) -C terraform/accounts/$$account clean || exit $$?; \
+	done
+.PHONY: clean-accounts
+
+clean-envs: ## clean in terraform/envs
+	@for env in $(ENVS); do \
+		$(MAKE) -C terraform/envs/$$env clean || exit $$?; \
+	done
+.PHONY: clean-envs
+
+clean-global: ## clean in terraform/global
+	$(MAKE) -C terraform/global clean || exit $$?
+.PHONY: clean-global
+
+
+help: ## display help for this makefile
+	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+.PHONY: help
+
diff --git a/testdata/tfe_config/README.md b/testdata/tfe_config/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/fogg.yml b/testdata/tfe_config/fogg.yml
new file mode 100644
index 00000000..7d34c630
--- /dev/null
+++ b/testdata/tfe_config/fogg.yml
@@ -0,0 +1,39 @@
+defaults:
+  backend:
+    kind: remote
+    host_name: si.prod.tfe.czi.technology
+    organization: shared-infra
+  owner: foo@example.com
+  project: foo
+  terraform_version: 1.1.1
+  providers:
+    aws:
+      account_id: "626314663667"
+      region: us-west-2
+      role: tfe-si
+      version: 3.63.0
+envs:
+  comp1: {}
+  comp2: {}
+accounts:
+  account: {}
+tfe:
+  terraform_version: 1.2.6
+  gh_org: chanzuckerberg
+  gh_repo: fogg
+  tfe_org: si.prod.tfe.czi.technology
+  module_source:  github.com/chanzuckerberg/cztack//aws-params-reader-policy?ref=v0.15.1
+  extra_vars:
+    TFE_AWS_ACCESS_KEY_ID: ""
+    TFE_AWS_SECRET_ACCESS_KEY: ""
+  providers:
+    tfe:
+      enabled: true
+      hostname: si.prod.tfe.czi.technology
+      version: 0.33.0
+    aws:
+      account_id: "626314663666"
+      region: us-west-2
+      role: tfe-si
+      version: 3.30.0
+version: 2
diff --git a/testdata/tfe_config/scripts/common.mk b/testdata/tfe_config/scripts/common.mk
new file mode 100644
index 00000000..0a154791
--- /dev/null
+++ b/testdata/tfe_config/scripts/common.mk
@@ -0,0 +1,32 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+SHELL=/bin/bash -o pipefail
+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
+export AWS_SDK_LOAD_CONFIG := 1
+
+TFENV_DIR ?= $(REPO_ROOT)/.fogg/tfenv
+export PATH :=$(TFENV_DIR)/libexec:$(TFENV_DIR)/versions/$(TERRAFORM_VERSION)/:$(REPO_ROOT)/.fogg/bin:$(PATH)
+export TF_PLUGIN_CACHE_DIR=$(REPO_ROOT)/.terraform.d/plugin-cache
+export TF_IN_AUTOMATION=1
+terraform_command ?= $(TFENV_DIR)/versions/$(TERRAFORM_VERSION)/terraform
+
+ifeq ($(TF_BACKEND_KIND),remote)
+	REFRESH := true
+else
+	REFRESH := false
+endif
+
+
+tfenv: ## install the tfenv tool
+	@if [ ! -d ${TFENV_DIR} ]; then \
+		git clone -q https://github.com/chanzuckerberg/tfenv.git $(TFENV_DIR); \
+	fi
+.PHONY: tfenv
+
+terraform: tfenv ## ensure that the proper version of terraform is installed
+	@${TFENV_DIR}/bin/tfenv install $(TERRAFORM_VERSION)
+.PHONY: terraform
diff --git a/testdata/tfe_config/scripts/component.mk b/testdata/tfe_config/scripts/component.mk
new file mode 100644
index 00000000..0caacd7f
--- /dev/null
+++ b/testdata/tfe_config/scripts/component.mk
@@ -0,0 +1,127 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+SELF_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+CHECK_PLANFILE_PATH ?= check-plan.output
+
+include $(SELF_DIR)/common.mk
+
+all:
+.PHONY: all
+
+setup: ## set up local dependencies for this repo
+	$(MAKE) -C $(REPO_ROOT) setup
+.PHONY: setup
+
+check: lint check-plan ## run all checks for this component
+.PHONY: check
+
+fmt: terraform ## format code in this component
+	$(terraform_command) fmt $(TF_ARGS)
+.PHONY: fmt
+
+lint: lint-terraform-fmt lint-tflint ## run all linters for this component
+.PHONY: lint
+
+lint-tflint: ## run the tflint linter for this component
+	@printf "tflint: "
+ifeq ($(TFLINT_ENABLED),1)
+	@tflint || exit $$?;
+else
+	@echo "disabled"
+endif
+.PHONY: lint-tflint
+
+lint-terraform-fmt: terraform ## run `terraform fmt` in check mode
+	$(terraform_command) fmt $(TF_ARGS) --check=true --diff=true
+.PHONY: lint-terraform-fmt
+
+check-auth: check-auth-aws check-auth-heroku ## check that authentication is properly set up for this component
+.PHONY: check-auth
+
+check-auth-aws:
+	@for p in $(AWS_BACKEND_PROFILE) $(AWS_PROVIDER_PROFILE); do \
+		aws --profile $$p sts get-caller-identity > /dev/null || (echo "AWS AUTH error. This component is configured to use a profile named '$$p'. Please add one to your ~/.aws/config" && exit -1); \
+	done
+	@for r in $(AWS_BACKEND_ROLE_ARN) $(AWS_PROVIDER_ROLE_ARN); do \
+		aws sts assume-role --role-arn $$r --role-session-name fogg-auth-test > /dev/null || (echo "AWS AUTH error. This component is configured to use a role named '$$r'." && exit -1); \
+	done
+.PHONY: check-auth-aws
+
+check-auth-heroku:
+ifeq ($(HEROKU_PROVIDER),1)
+	@echo "Checking heroku auth..."
+	@if command heroku >/dev/null; then \
+		heroku auth:whoami || timeout 15 heroku auth:login || (echo "Not authenticated to heroku. For SSO accounts, run 'heroku login', for non-sso accounts set HEROKU_EMAIL and HEROKU_API_KEY" && exit -1); \
+	else \
+		echo "Heroku CLI not installed, can't check auth."; \
+	fi
+endif
+.PHONY: check-auth-heroku
+
+refresh:
+	@if [ "$(TF_BACKEND_KIND)" != "remote" ]; then \
+		$(terraform_command) refresh $(TF_ARGS); \
+		date +%s > .terraform/refreshed_at; \
+	else \
+		echo "remote backend does not support the refresh command, skipping"; \
+	fi
+.PHONY: refresh
+
+refresh-cached:
+	@last_refresh=`cat .terraform/refreshed_at 2>/dev/null || echo '0'`; \
+	current_time=`date +%s`; \
+	if (( current_time - last_refresh > 600 )); then \
+		echo "It has been awhile since the last refresh. It is time."; \
+		$(MAKE) refresh; \
+	else \
+		echo "Not time to refresh yet."; \
+	fi;
+.PHONY: refresh-cached
+
+plan: check-auth init fmt refresh-cached ## run a terraform plan
+	$(terraform_command) plan $(TF_ARGS) -refresh=$(REFRESH) -input=false
+.PHONY: plan
+
+apply: check-auth init refresh ## run a terraform apply
+	@$(terraform_command) apply $(TF_ARGS) -refresh=$(REFRESH) -auto-approve=$(AUTO_APPROVE)
+.PHONY: apply
+
+docs:
+	echo
+.PHONY: docs
+
+clean: ## clean modules and plugins for this component
+	-rm -rfv .terraform/modules
+	-rm -rfv .terraform/plugins
+.PHONY: clean
+
+test:
+.PHONY: test
+
+init: terraform check-auth ## run terraform init for this component
+	@$(terraform_command) init -input=false
+.PHONY: init
+
+check-plan: check-auth init refresh-cached ## run a terraform plan and check that it does not fail
+	@if [ "$(TF_BACKEND_KIND)" != "remote" ]; then \
+		$(terraform_command) plan $(TF_ARGS) -detailed-exitcode -lock=false -refresh=$(REFRESH) -out=$(CHECK_PLANFILE_PATH) ; \
+		ERR=$$?; \
+		rm $(CHECK_PLANFILE_PATH) 2>/dev/null; \
+	else \
+		$(terraform_command) plan $(TF_ARGS) -detailed-exitcode -lock=false; \
+		ERR=$$?; \
+	fi; \
+	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;
+.PHONY: check-plan
+
+run: check-auth ## run an arbitrary terraform command, CMD. ex `make run CMD='show'`
+	@$(terraform_command) $(CMD)
+.PHONY: run
diff --git a/testdata/tfe_config/scripts/failed_output_only b/testdata/tfe_config/scripts/failed_output_only
new file mode 100644
index 00000000..a21d3dab
--- /dev/null
+++ b/testdata/tfe_config/scripts/failed_output_only
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+t=`mktemp` && \
+exec $@  2>&1 > $t || \
+(
+    a=$?;
+    echo $a;
+    cat $t;
+    rm $t;
+    exit $a
+)
+
+
diff --git a/testdata/tfe_config/scripts/module.mk b/testdata/tfe_config/scripts/module.mk
new file mode 100644
index 00000000..3de23397
--- /dev/null
+++ b/testdata/tfe_config/scripts/module.mk
@@ -0,0 +1,44 @@
+# 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
+.PHONY: all
+
+fmt: terraform ## run terraform fmt on this module
+	@$(terraform_command) fmt $(TF_ARGS)
+.PHONY: fmt
+
+check: lint check-docs ## run all checks on this module
+.PHONY: check
+
+lint: lint-tf check-docs ## run all linters on this module
+.PHONY: lint
+
+lint-tf: terraform ## run terraform linters on this module
+	$(terraform_command) fmt $(TF_ARGS) --check=true --diff=true
+.PHONY: lint-tf
+
+readme: ## update this module's README.md
+	bash $(REPO_ROOT)/scripts/update-readme.sh update
+.PHONY: readme
+
+docs: readme ## update all docs for this module
+.PHONY: docs
+
+check-docs: ## check that this module's docs are up-to-date
+	@bash $(REPO_ROOT)/scripts/update-readme.sh check; \
+	if [ ! $$? -eq 0 ];  then \
+		echo "Docs are out of date, run \`make docs\`"; \
+		exit 1 ; \
+	fi
+.PHONY: check-docs
+
+clean:
+.PHONY: clean
+
+test:
+.PHONY: test
diff --git a/testdata/tfe_config/scripts/update-readme.sh b/testdata/tfe_config/scripts/update-readme.sh
new file mode 100644
index 00000000..abe8fb47
--- /dev/null
+++ b/testdata/tfe_config/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/tfe_config/terraform.d/.keep b/testdata/tfe_config/terraform.d/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/accounts/account/Makefile b/testdata/tfe_config/terraform/accounts/account/Makefile
new file mode 100644
index 00000000..41ea6768
--- /dev/null
+++ b/testdata/tfe_config/terraform/accounts/account/Makefile
@@ -0,0 +1,16 @@
+# 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
+export TF_BACKEND_KIND := remote
+
+include ../../..//scripts/component.mk
+
+
+help: ## display help for this makefile
+	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+.PHONY: help
+
diff --git a/testdata/tfe_config/terraform/accounts/account/README.md b/testdata/tfe_config/terraform/accounts/account/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/accounts/account/fogg.tf b/testdata/tfe_config/terraform/accounts/account/fogg.tf
new file mode 100644
index 00000000..5704260a
--- /dev/null
+++ b/testdata/tfe_config/terraform/accounts/account/fogg.tf
@@ -0,0 +1,160 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+provider "aws" {
+
+  region = "us-west-2"
+
+
+  assume_role {
+    role_arn = "arn:aws:iam::626314663667:role/tfe-si"
+  }
+
+  allowed_account_ids = ["626314663667"]
+}
+# Aliased Providers (for doing things in every region).
+
+terraform {
+  required_version = "=1.1.1"
+
+  backend "remote" {
+
+    hostname     = "si.prod.tfe.czi.technology"
+    organization = "shared-infra"
+    workspaces {
+      name = "accounts-account"
+    }
+
+  }
+  required_providers {
+
+    archive = {
+      source = "hashicorp/archive"
+
+      version = "~> 2.0"
+
+    }
+
+    assert = {
+      source = "bwoznicki/assert"
+
+      version = "~> 0.0.1"
+
+    }
+
+    aws = {
+      source = "hashicorp/aws"
+
+      version = "3.63.0"
+
+    }
+
+    local = {
+      source = "hashicorp/local"
+
+      version = "~> 2.0"
+
+    }
+
+    null = {
+      source = "hashicorp/null"
+
+      version = "~> 3.0"
+
+    }
+
+    okta-head = {
+      source = "okta/okta"
+
+      version = "~> 3.30"
+
+    }
+
+    random = {
+      source = "hashicorp/random"
+
+      version = "~> 2.2"
+
+    }
+
+    template = {
+      source = "hashicorp/template"
+
+      version = "~> 2.2"
+
+    }
+
+    tls = {
+      source = "hashicorp/tls"
+
+      version = "~> 3.0"
+
+    }
+
+  }
+}
+variable "env" {
+  type    = string
+  default = "accounts"
+}
+variable "project" {
+  type    = string
+  default = "foo"
+}
+variable "region" {
+  type    = string
+  default = "us-west-2"
+}
+variable "component" {
+  type    = string
+  default = "account"
+}
+variable "account" {
+  type    = string
+  default = "account"
+}
+variable "owner" {
+  type    = string
+  default = "foo@example.com"
+}
+variable "tags" {
+  type = object({ project : string, env : string, service : string, owner : string, managedBy : string })
+  default = {
+    project   = "foo"
+    env       = "accounts"
+    service   = "account"
+    owner     = "foo@example.com"
+    managedBy = "terraform"
+  }
+}
+data "terraform_remote_state" "global" {
+  backend = "remote"
+  config = {
+
+
+    hostname     = "si.prod.tfe.czi.technology"
+    organization = "shared-infra"
+    workspaces = {
+      name = "global"
+    }
+
+  }
+}
+data "terraform_remote_state" "account" {
+  backend = "remote"
+  config = {
+
+
+    hostname     = "si.prod.tfe.czi.technology"
+    organization = "shared-infra"
+    workspaces = {
+      name = "accounts-account"
+    }
+
+  }
+}
+variable "aws_accounts" {
+  type = map(string)
+  default = {
+
+  }
+}
diff --git a/testdata/tfe_config/terraform/accounts/account/main.tf b/testdata/tfe_config/terraform/accounts/account/main.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/accounts/account/outputs.tf b/testdata/tfe_config/terraform/accounts/account/outputs.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/accounts/account/terraform.d b/testdata/tfe_config/terraform/accounts/account/terraform.d
new file mode 120000
index 00000000..29617d28
--- /dev/null
+++ b/testdata/tfe_config/terraform/accounts/account/terraform.d
@@ -0,0 +1,1 @@
+../../../terraform.d
\ No newline at end of file
diff --git a/testdata/tfe_config/terraform/accounts/account/variables.tf b/testdata/tfe_config/terraform/accounts/account/variables.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/envs/comp1/Makefile b/testdata/tfe_config/terraform/envs/comp1/Makefile
new file mode 100644
index 00000000..341a07dc
--- /dev/null
+++ b/testdata/tfe_config/terraform/envs/comp1/Makefile
@@ -0,0 +1,51 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+COMPONENTS=
+
+all:
+.PHONY: all
+
+lint: ## lint all components in the env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c lint || exit $$? ; \
+	done
+.PHONY: lint
+
+fmt: ## format terraform code in all components in this env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c fmt || exit $$? ; \
+	done
+.PHONY: fmt
+
+check-plan: ## check all plans in this environment
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c check-plan || exit $$? ; \
+	done
+.PHONY: check-plan
+
+docs: ## generate docs for all components in this env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c docs || exit $$? ; \
+	done
+.PHONY: docs
+
+clean: ## clean all components in this env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c clean || exit $$? ; \
+	done
+.PHONY: clean
+
+test:
+.PHONY: test
+
+
+help: ## display help for this makefile
+	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+.PHONY: help
+
diff --git a/testdata/tfe_config/terraform/envs/comp1/README.md b/testdata/tfe_config/terraform/envs/comp1/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/envs/comp2/Makefile b/testdata/tfe_config/terraform/envs/comp2/Makefile
new file mode 100644
index 00000000..341a07dc
--- /dev/null
+++ b/testdata/tfe_config/terraform/envs/comp2/Makefile
@@ -0,0 +1,51 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+COMPONENTS=
+
+all:
+.PHONY: all
+
+lint: ## lint all components in the env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c lint || exit $$? ; \
+	done
+.PHONY: lint
+
+fmt: ## format terraform code in all components in this env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c fmt || exit $$? ; \
+	done
+.PHONY: fmt
+
+check-plan: ## check all plans in this environment
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c check-plan || exit $$? ; \
+	done
+.PHONY: check-plan
+
+docs: ## generate docs for all components in this env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c docs || exit $$? ; \
+	done
+.PHONY: docs
+
+clean: ## clean all components in this env
+	@for c in $(COMPONENTS); do \
+		echo $$c; \
+		$(MAKE) -C $$c clean || exit $$? ; \
+	done
+.PHONY: clean
+
+test:
+.PHONY: test
+
+
+help: ## display help for this makefile
+	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+.PHONY: help
+
diff --git a/testdata/tfe_config/terraform/envs/comp2/README.md b/testdata/tfe_config/terraform/envs/comp2/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/global/Makefile b/testdata/tfe_config/terraform/global/Makefile
new file mode 100644
index 00000000..b6c6beac
--- /dev/null
+++ b/testdata/tfe_config/terraform/global/Makefile
@@ -0,0 +1,16 @@
+# 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
+export TF_BACKEND_KIND := remote
+
+include ../..//scripts/component.mk
+
+
+help: ## display help for this makefile
+	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+.PHONY: help
+
diff --git a/testdata/tfe_config/terraform/global/README.md b/testdata/tfe_config/terraform/global/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/global/fogg.tf b/testdata/tfe_config/terraform/global/fogg.tf
new file mode 100644
index 00000000..78a824b0
--- /dev/null
+++ b/testdata/tfe_config/terraform/global/fogg.tf
@@ -0,0 +1,130 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+provider "aws" {
+
+  region = "us-west-2"
+
+
+  assume_role {
+    role_arn = "arn:aws:iam::626314663667:role/tfe-si"
+  }
+
+  allowed_account_ids = ["626314663667"]
+}
+# Aliased Providers (for doing things in every region).
+
+terraform {
+  required_version = "=1.1.1"
+
+  backend "remote" {
+
+    hostname     = "si.prod.tfe.czi.technology"
+    organization = "shared-infra"
+    workspaces {
+      name = "global"
+    }
+
+  }
+  required_providers {
+
+    archive = {
+      source = "hashicorp/archive"
+
+      version = "~> 2.0"
+
+    }
+
+    assert = {
+      source = "bwoznicki/assert"
+
+      version = "~> 0.0.1"
+
+    }
+
+    aws = {
+      source = "hashicorp/aws"
+
+      version = "3.63.0"
+
+    }
+
+    local = {
+      source = "hashicorp/local"
+
+      version = "~> 2.0"
+
+    }
+
+    null = {
+      source = "hashicorp/null"
+
+      version = "~> 3.0"
+
+    }
+
+    okta-head = {
+      source = "okta/okta"
+
+      version = "~> 3.30"
+
+    }
+
+    random = {
+      source = "hashicorp/random"
+
+      version = "~> 2.2"
+
+    }
+
+    template = {
+      source = "hashicorp/template"
+
+      version = "~> 2.2"
+
+    }
+
+    tls = {
+      source = "hashicorp/tls"
+
+      version = "~> 3.0"
+
+    }
+
+  }
+}
+variable "env" {
+  type    = string
+  default = ""
+}
+variable "project" {
+  type    = string
+  default = "foo"
+}
+variable "region" {
+  type    = string
+  default = "us-west-2"
+}
+variable "component" {
+  type    = string
+  default = "global"
+}
+variable "owner" {
+  type    = string
+  default = "foo@example.com"
+}
+variable "tags" {
+  type = object({ project : string, env : string, service : string, owner : string, managedBy : string })
+  default = {
+    project   = "foo"
+    env       = ""
+    service   = "global"
+    owner     = "foo@example.com"
+    managedBy = "terraform"
+  }
+}
+variable "aws_accounts" {
+  type = map(string)
+  default = {
+
+  }
+}
diff --git a/testdata/tfe_config/terraform/global/main.tf b/testdata/tfe_config/terraform/global/main.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/global/outputs.tf b/testdata/tfe_config/terraform/global/outputs.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/global/terraform.d b/testdata/tfe_config/terraform/global/terraform.d
new file mode 120000
index 00000000..a1f7d0d3
--- /dev/null
+++ b/testdata/tfe_config/terraform/global/terraform.d
@@ -0,0 +1,1 @@
+../../terraform.d
\ No newline at end of file
diff --git a/testdata/tfe_config/terraform/global/variables.tf b/testdata/tfe_config/terraform/global/variables.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/tfe/Makefile b/testdata/tfe_config/terraform/tfe/Makefile
new file mode 100644
index 00000000..ab59784a
--- /dev/null
+++ b/testdata/tfe_config/terraform/tfe/Makefile
@@ -0,0 +1,16 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+
+export TERRAFORM_VERSION := 1.2.6
+export TFLINT_ENABLED := 0
+export TF_PLUGIN_CACHE_DIR := /.terraform.d/plugin-cache
+export TF_BACKEND_KIND := remote
+
+include /scripts/component.mk
+
+
+help: ## display help for this makefile
+	@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+.PHONY: help
+
diff --git a/testdata/tfe_config/terraform/tfe/README.md b/testdata/tfe_config/terraform/tfe/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/testdata/tfe_config/terraform/tfe/fogg.tf b/testdata/tfe_config/terraform/tfe/fogg.tf
new file mode 100644
index 00000000..cbd0add9
--- /dev/null
+++ b/testdata/tfe_config/terraform/tfe/fogg.tf
@@ -0,0 +1,149 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+provider "aws" {
+
+  region = "us-west-2"
+
+
+  assume_role {
+    role_arn = "arn:aws:iam::626314663666:role/tfe-si"
+  }
+
+  allowed_account_ids = ["626314663666"]
+}
+# Aliased Providers (for doing things in every region).
+
+
+provider "tfe" {
+  hostname = "si.prod.tfe.czi.technology"
+}
+terraform {
+  required_version = "=1.2.6"
+
+  backend "remote" {
+
+    hostname     = "si.prod.tfe.czi.technology"
+    organization = "shared-infra"
+    workspaces {
+      name = "tfe"
+    }
+
+  }
+  required_providers {
+
+    archive = {
+      source = "hashicorp/archive"
+
+      version = "~> 2.0"
+
+    }
+
+    assert = {
+      source = "bwoznicki/assert"
+
+      version = "~> 0.0.1"
+
+    }
+
+    aws = {
+      source = "hashicorp/aws"
+
+      version = "3.30.0"
+
+    }
+
+    local = {
+      source = "hashicorp/local"
+
+      version = "~> 2.0"
+
+    }
+
+    null = {
+      source = "hashicorp/null"
+
+      version = "~> 3.0"
+
+    }
+
+    okta-head = {
+      source = "okta/okta"
+
+      version = "~> 3.30"
+
+    }
+
+    random = {
+      source = "hashicorp/random"
+
+      version = "~> 2.2"
+
+    }
+
+    template = {
+      source = "hashicorp/template"
+
+      version = "~> 2.2"
+
+    }
+
+    tfe = {
+      source = "hashicorp/tfe"
+
+      version = "0.33.0"
+
+    }
+
+    tls = {
+      source = "hashicorp/tls"
+
+      version = "~> 3.0"
+
+    }
+
+  }
+}
+variable "env" {
+  type    = string
+  default = ""
+}
+variable "project" {
+  type    = string
+  default = "foo"
+}
+variable "region" {
+  type    = string
+  default = "us-west-2"
+}
+variable "component" {
+  type    = string
+  default = ""
+}
+variable "owner" {
+  type    = string
+  default = "foo@example.com"
+}
+variable "tags" {
+  type = object({ project : string, env : string, service : string, owner : string, managedBy : string })
+  default = {
+    project   = "foo"
+    env       = ""
+    service   = ""
+    owner     = "foo@example.com"
+    managedBy = "terraform"
+  }
+}
+variable "TFE_AWS_ACCESS_KEY_ID" {
+  type    = string
+  default = ""
+}
+variable "TFE_AWS_SECRET_ACCESS_KEY" {
+  type    = string
+  default = ""
+}
+variable "aws_accounts" {
+  type = map(string)
+  default = {
+
+  }
+}
diff --git a/testdata/tfe_config/terraform/tfe/locals.tf b/testdata/tfe_config/terraform/tfe/locals.tf
new file mode 100644
index 00000000..d5c8d0d5
--- /dev/null
+++ b/testdata/tfe_config/terraform/tfe/locals.tf
@@ -0,0 +1,5 @@
+
+locals {
+  organization = "si.prod.tfe.czi.technology"
+  repo         = "chanzuckerberg/fogg"
+}
diff --git a/testdata/tfe_config/terraform/tfe/locals.tf.json b/testdata/tfe_config/terraform/tfe/locals.tf.json
new file mode 100644
index 00000000..00ffe8bb
--- /dev/null
+++ b/testdata/tfe_config/terraform/tfe/locals.tf.json
@@ -0,0 +1,18 @@
+{
+  "locals": {
+    "accounts": {
+      "account": {
+        "trigger_prefixes": [],
+        "terraform_version": "1.1.1",
+        "branch": "main",
+        "auto_apply": true,
+        "remote_apply": true
+      }
+    },
+    "envs": {
+      "comp1": {},
+      "comp2": {}
+    },
+    "default_terraform_version": "1.2.6"
+  }
+}
diff --git a/testdata/tfe_config/terraform/tfe/main.tf b/testdata/tfe_config/terraform/tfe/main.tf
new file mode 100644
index 00000000..e62d4da7
--- /dev/null
+++ b/testdata/tfe_config/terraform/tfe/main.tf
@@ -0,0 +1,13 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+module "aws-params-reader-policy" {
+  source                    = "github.com/chanzuckerberg/cztack//aws-params-reader-policy?ref=v0.15.1"
+  env                       = local.env
+  parameter_store_key_alias = local.parameter_store_key_alias
+  project                   = local.project
+  region                    = local.region
+  role_name                 = local.role_name
+  service                   = local.service
+
+}
diff --git a/testdata/tfe_config/terraform/tfe/outputs.tf b/testdata/tfe_config/terraform/tfe/outputs.tf
new file mode 100644
index 00000000..46c8bbfa
--- /dev/null
+++ b/testdata/tfe_config/terraform/tfe/outputs.tf
@@ -0,0 +1,4 @@
+# Auto-generated by fogg. Do not edit
+# Make improvements in fogg, so that everyone can benefit.
+
+
diff --git a/testdata/tfe_config/terraform/tfe/terraform.d b/testdata/tfe_config/terraform/tfe/terraform.d
new file mode 120000
index 00000000..a1f7d0d3
--- /dev/null
+++ b/testdata/tfe_config/terraform/tfe/terraform.d
@@ -0,0 +1,1 @@
+../../terraform.d
\ No newline at end of file