Repository: chanzuckerberg/fogg
Pull request #298 (2019-06-18 00:46:21)

[feature] Parse config as yaml and add a command to migrate a json config to yaml
### Summary
#### Main Additions
- **init**: Generates .yml file by default
- **apply**: Reads .yml files (Default is .yml but also support .json)
- **plan**: Reads and writes using .yml (Default is .yml but also support .json)
- **aws-config**: Supports .yml and .json
- **tests**: All tests are configured to test both .json and .yml (excluding golden_tests) 

#### Current PR Specifics
- updated apply_test.go to support v2 yaml structure 
- Added yaml specific tests to plan_test, apply_test
- reduced json type checking code duplication in v1/config.go and v2/config.go 
- added yaml folders in testdata

#### WIP:
- Need to remove v1 and v2 inside imports [**need assistance**]
- **ISSUE**: The current yaml folders in testdata use v1 fogg structure. They are .yml files but they use v1 keys and values
- removing yaml annotations in v1/config.go causes some tests to fail (related to resolvers)


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

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

```diff
@@            Coverage Diff             @@
##           master     #298      +/-   ##
==========================================
- Coverage   80.09%   79.95%   -0.15%     
==========================================
  Files          17       17              
  Lines        1422     1432      +10     
==========================================
+ Hits         1139     1145       +6     
- Misses        162      163       +1     
- Partials      121      124       +3
```


| [Impacted Files](https://codecov.io/gh/chanzuckerberg/fogg/pull/298?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [plugins/custom\_plugin.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-cGx1Z2lucy9jdXN0b21fcGx1Z2luLmdv) | `56.87% <ø> (ø)` | :arrow_up: |
| [apply/apply.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-YXBwbHkvYXBwbHkuZ28=) | `64.04% <ø> (ø)` | :arrow_up: |
| [plan/plan.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-cGxhbi9wbGFuLmdv) | `92.09% <0%> (+1.12%)` | :arrow_up: |
| [config/v1/config.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-Y29uZmlnL3YxL2NvbmZpZy5nbw==) | `87.5% <100%> (+2.88%)` | :arrow_up: |
| [util/testing.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-dXRpbC90ZXN0aW5nLmdv) | `85.71% <100%> (+2.38%)` | :arrow_up: |
| [config/config.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-Y29uZmlnL2NvbmZpZy5nbw==) | `91.95% <71.42%> (-2.1%)` | :arrow_down: |
| [config/v2/config.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-Y29uZmlnL3YyL2NvbmZpZy5nbw==) | `98.07% <75%> (-1.93%)` | :arrow_down: |
| [config/v2/validation.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-Y29uZmlnL3YyL3ZhbGlkYXRpb24uZ28=) | `80% <0%> (-1.82%)` | :arrow_down: |
| [plan/travisci.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-cGxhbi90cmF2aXNjaS5nbw==) | `94.28% <0%> (-0.16%)` | :arrow_down: |
| [config/v2/resolvers.go](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree#diff-Y29uZmlnL3YyL3Jlc29sdmVycy5nbw==) | `98.71% <0%> (-0.06%)` | :arrow_down: |
| ... and [3 more](https://codecov.io/gh/chanzuckerberg/fogg/pull/298/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/chanzuckerberg/fogg/pull/298?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/298?src=pr&el=footer). Last update [d7a5ccc...fd5b5d8](https://codecov.io/gh/chanzuckerberg/fogg/pull/298?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

edulop91: @shedninja6 there are a handful of merge conflicts, can you fix those and then I'll review?

Patch:
diff --git a/apply/apply.go b/apply/apply.go
index 745de0df..4385a2d9 100644
--- a/apply/apply.go
+++ b/apply/apply.go
@@ -12,8 +12,8 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/chanzuckerberg/fogg/config/v1"
-	"github.com/chanzuckerberg/fogg/config/v2"
+	v1 "github.com/chanzuckerberg/fogg/config/v1"
+	v2 "github.com/chanzuckerberg/fogg/config/v2"
 	"github.com/chanzuckerberg/fogg/errs"
 	"github.com/chanzuckerberg/fogg/plan"
 	"github.com/chanzuckerberg/fogg/templates"
@@ -401,7 +401,7 @@ func applyModuleInvocation(fs afero.Fs, path, moduleAddress string, box packr.Bo
 func calculateModuleAddressForSource(path, moduleAddress string) (string, error) {
 	// For cases where the module is a local path, we need to calculate the
 	// relative path from the component to the module.
-	// The module_source path in the fogg.json is relative to the repo root.
+	// The module_source path in the fogg.yml is relative to the repo root.
 	var moduleAddressForSource string
 	// getter will kinda normalize the module address, but it will actually be
 	// wrong for local file paths, so we need to calculate that ourselves below
diff --git a/cmd/apply.go b/cmd/apply.go
index c2341612..3220cba7 100644
--- a/cmd/apply.go
+++ b/cmd/apply.go
@@ -7,15 +7,15 @@ import (
 )
 
 func init() {
-	applyCmd.Flags().StringP("config", "c", "fogg.json", "Use this to override the fogg config file.")
+	applyCmd.Flags().StringP("config", "c", "fogg.yml", "Use this to override the fogg config file.")
 	applyCmd.Flags().BoolP("upgrade", "u", false, "use this when running a new version of fogg")
 	rootCmd.AddCommand(applyCmd)
 }
 
 var applyCmd = &cobra.Command{
 	Use:           "apply",
-	Short:         "Apply model defined in fogg.json to the current tree.",
-	Long:          "This command will take the model defined in fogg.json, build a plan and generate the appropriate files from templates.",
+	Short:         "Apply model defined in fogg.yml to the current tree.",
+	Long:          "This command will take the model defined in fogg.yml, build a plan and generate the appropriate files from templates.",
 	SilenceErrors: true, // If we don't silence here, cobra will print them. But we want to do that in cmd/root.go
 	RunE: func(cmd *cobra.Command, args []string) error {
 		setupDebug(debug)
diff --git a/cmd/exp/aws_config.go b/cmd/exp/aws_config.go
index d34ad799..16238342 100644
--- a/cmd/exp/aws_config.go
+++ b/cmd/exp/aws_config.go
@@ -20,7 +20,7 @@ import (
 func init() {
 	awsConfigCmd.Flags().StringP("source-profile", "p", "default", "Use this to override the base aws profile.")
 	awsConfigCmd.Flags().StringP("role", "r", "default", "Use this to override the default assume role.")
-	awsConfigCmd.Flags().StringP("config", "c", "fogg.json", "Use this to override the fogg config file.")
+	awsConfigCmd.Flags().StringP("config", "c", "fogg.yml", "Use this to override the fogg config file.")
 	awsConfigCmd.Flags().BoolP("export", "e", false, "Export whole thing to stdout.")
 	awsConfigCmd.Flags().BoolP("all", "a", false, "All profiles. Only makes sense if export=true.")
 
@@ -29,8 +29,8 @@ func init() {
 
 var awsConfigCmd = &cobra.Command{
 	Use:   "aws-config",
-	Short: "Generates an ~/.aws/config from your fogg.json",
-	Long:  "This command will help generate a ~/.aws/config from your fogg.json.",
+	Short: "Generates an ~/.aws/config from your fogg.yml",
+	Long:  "This command will help generate a ~/.aws/config from your fogg.yml",
 	RunE: func(cmd *cobra.Command, args []string) error {
 
 		// Set up fs
diff --git a/cmd/init.go b/cmd/init.go
index 8b283ac9..82ba2735 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -24,7 +24,6 @@ var initCmd = &cobra.Command{
 			return errs.WrapUser(e, "can't get pwd")
 		}
 		fs := afero.NewBasePathFs(afero.NewOsFs(), pwd)
-
 		// check that we are at root of initialized git repo
 		openGitOrExit(fs)
 
diff --git a/cmd/plan.go b/cmd/plan.go
index 86433112..529b8081 100644
--- a/cmd/plan.go
+++ b/cmd/plan.go
@@ -10,14 +10,14 @@ import (
 )
 
 func init() {
-	planCmd.Flags().StringP("config", "c", "fogg.json", "Use this to override the fogg config file.")
+	planCmd.Flags().StringP("config", "c", "fogg.yml", "Use this to override the fogg config file.")
 	rootCmd.AddCommand(planCmd)
 }
 
 var planCmd = &cobra.Command{
 	Use:           "plan",
 	Short:         "Run a plan",
-	Long:          "plan will read fogg.json, use that to generate a plan and print that plan out. It will make no changes.",
+	Long:          "plan will read fogg.yml or fogg.json, use that to generate a plan and print that plan out. It will make no changes.",
 	SilenceErrors: true, // If we don't silence here, cobra will print them. But we want to do that in cmd/root.go
 	RunE: func(cmd *cobra.Command, args []string) error {
 		setupDebug(debug)
diff --git a/cmd/setup.go b/cmd/setup.go
index 2aa2d64e..568af983 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -7,7 +7,7 @@ import (
 )
 
 func init() {
-	setupCmd.Flags().StringP("config", "c", "fogg.json", "Use this to override the fogg config file.")
+	setupCmd.Flags().StringP("config", "c", "fogg.yml", "Use this to override the fogg config file.")
 	rootCmd.AddCommand(setupCmd)
 }
 
diff --git a/cmd/upgrade.go b/cmd/upgrade.go
index b03d81f7..a7c3a2a0 100644
--- a/cmd/upgrade.go
+++ b/cmd/upgrade.go
@@ -10,7 +10,7 @@ import (
 )
 
 func init() {
-	upgrade.Flags().StringP("config", "c", "fogg.json", "Use this to override the fogg config file.")
+	upgrade.Flags().StringP("config", "c", "fogg.yml", "Use this to override the fogg config file.")
 	rootCmd.AddCommand(upgrade)
 }
 
diff --git a/cmd/util.go b/cmd/util.go
index 4bd7c784..b9f75e24 100644
--- a/cmd/util.go
+++ b/cmd/util.go
@@ -40,7 +40,7 @@ func readAndValidateConfig(fs afero.Fs, configFile string) (*v2.Config, []string
 
 func mergeConfigValidationErrors(err error) error {
 	if err != nil {
-		fmt.Println("fogg.json has error(s):")
+		fmt.Println("fogg.yml has error(s):")
 		validatonErrors, ok := err.(validator.ValidationErrors)
 		if ok {
 			var sb strings.Builder
diff --git a/cmd/yaml.go b/cmd/yaml.go
new file mode 100644
index 00000000..ad110fc2
--- /dev/null
+++ b/cmd/yaml.go
@@ -0,0 +1,39 @@
+package cmd
+
+import (
+	"os"
+
+	"github.com/chanzuckerberg/fogg/errs"
+	"github.com/chanzuckerberg/fogg/migrate"
+	"github.com/spf13/afero"
+	"github.com/spf13/cobra"
+)
+
+func init() {
+	yamlMigrateCmd.Flags().StringP("config", "c", "fogg.json", "Use this to override the fogg config file.")
+	rootCmd.AddCommand(yamlMigrateCmd)
+}
+
+var yamlMigrateCmd = &cobra.Command{
+	Use:   "yaml-migrate",
+	Short: "Converts existing fogg.json to fogg.yml",
+	Long:  "This command will convert the fogg.json to a fogg.yml file type.",
+	RunE: func(cmd *cobra.Command, args []string) error {
+
+		// Set up fs
+		pwd, err := os.Getwd()
+		if err != nil {
+			return errs.WrapUser(err, "can't get pwd")
+		}
+		fs := afero.NewBasePathFs(afero.NewOsFs(), pwd)
+
+		configFile, err := cmd.Flags().GetString("config")
+		if err != nil {
+			return errs.WrapInternal(err, "couldn't parse config flag")
+		}
+
+		openGitOrExit(fs)
+
+		return migrate.ConvertToYaml(fs, configFile)
+	},
+}
diff --git a/config/config.go b/config/config.go
index c35cd134..35fad5f9 100644
--- a/config/config.go
+++ b/config/config.go
@@ -2,8 +2,8 @@ package config
 
 import (
 	"encoding/json"
-	"io"
 	"io/ioutil"
+	"path/filepath"
 
 	v1 "github.com/chanzuckerberg/fogg/config/v1"
 	v2 "github.com/chanzuckerberg/fogg/config/v2"
@@ -11,26 +11,37 @@ import (
 	"github.com/chanzuckerberg/fogg/util"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/afero"
+	"gopkg.in/yaml.v2"
 )
 
-func InitConfig(project, region, bucket, table, awsProfile, owner, awsProviderVersion string) *v1.Config {
-	return &v1.Config{
-		Defaults: v1.Defaults{
-			AWSProfileBackend:  awsProfile,
-			AWSProfileProvider: awsProfile,
-			AWSProviderVersion: awsProviderVersion,
-			AWSRegionBackend:   region,
-			AWSRegionProvider:  region,
-			ExtraVars:          map[string]string{},
-			InfraBucket:        bucket,
-			InfraDynamoTable:   table,
-			Owner:              owner,
-			Project:            project,
-			TerraformVersion:   "0.11.7",
+//InitConfig initializes the config file using user input
+func InitConfig(project, region, bucket, table, awsProfile, owner, awsProviderVersion string) *v2.Config {
+	terraformVersion := "0.11.7"
+
+	return &v2.Config{
+		Defaults: v2.Defaults{
+			Common: v2.Common{
+				Backend: &v2.Backend{
+					Bucket:      &bucket,
+					Profile:     &awsProfile,
+					Region:      &region,
+					DynamoTable: &table,
+				},
+				Owner:   &owner,
+				Project: &project,
+				Providers: &v2.Providers{
+					AWS: &v2.AWSProvider{
+						Profile: &awsProfile,
+						Region:  &region,
+						Version: &awsProviderVersion,
+					},
+				},
+				TerraformVersion: &terraformVersion,
+			},
 		},
-		Accounts: map[string]v1.Account{},
+		Accounts: map[string]v2.Account{},
 		Docker:   false,
-		Envs:     map[string]v1.Env{},
+		Envs:     map[string]v2.Env{},
 		Modules:  map[string]v1.Module{},
 	}
 }
@@ -41,15 +52,14 @@ func FindConfig(fs afero.Fs, configFile string) ([]byte, int, error) {
 	if err != nil {
 		return nil, 0, errs.WrapUser(err, "unable to open config file")
 	}
-	reader := io.ReadCloser(f)
-	defer reader.Close()
+	defer f.Close()
 
-	b, e := ioutil.ReadAll(reader)
+	b, e := ioutil.ReadAll(f)
 	if e != nil {
 		return nil, 0, errs.WrapUser(e, "unable to read config")
 	}
 
-	v, err := detectVersion(b)
+	v, err := detectVersion(b, fs, configFile)
 	if err != nil {
 		return nil, 0, err
 	}
@@ -57,38 +67,52 @@ func FindConfig(fs afero.Fs, configFile string) ([]byte, int, error) {
 	return b, v, nil
 }
 
+//FindAndReadConfig locates config file and reads it based on the version
 func FindAndReadConfig(fs afero.Fs, configFile string) (*v2.Config, error) {
 	b, v, err := FindConfig(fs, configFile)
 	if err != nil {
 		return nil, err
 	}
+
 	switch v {
-	case 1:
+	case 1: //Upgrade the config version
 		c, err := v1.ReadConfig(b)
 		if err != nil {
 			return nil, err
 		}
-
-		c2, err := UpgradeConfigVersion(c)
-		return c2, err
-
+		return UpgradeConfigVersion(c)
 	case 2:
-
-		c2, err := v2.ReadConfig(b)
-		return c2, err
+		return v2.ReadConfig(fs, b, configFile)
 	default:
 		return nil, errs.NewUser("could not figure out config file version")
 	}
+
 }
 
 type ver struct {
-	Version int `json:"version"`
+	Version int `json:"version" yaml:"version"`
 }
 
 // detectVersion will detect the version of a config
-func detectVersion(b []byte) (int, error) {
+func detectVersion(b []byte, fs afero.Fs, configFile string) (int, error) {
 	v := &ver{}
-	err := json.Unmarshal(b, v)
+	var err error
+
+	info, err := fs.Stat(configFile)
+	if err != nil {
+		return 0, errs.WrapUserf(err, "unable to stat %s", configFile)
+	}
+
+	//Unmarshals based on file extension
+	switch filepath.Ext(info.Name()) {
+	case ".yml", ".yaml":
+		err = yaml.Unmarshal(b, v)
+	case ".json":
+		err = json.Unmarshal(b, v)
+	default:
+		return 0, errs.NewUser("File type is not supported")
+	}
+
 	if err != nil {
 		return 0, err
 	}
diff --git a/config/upgrade.go b/config/upgrade.go
index 3531c667..db93e5c5 100644
--- a/config/upgrade.go
+++ b/config/upgrade.go
@@ -1,12 +1,11 @@
 package config
 
 import (
-	"encoding/json"
-
 	v1 "github.com/chanzuckerberg/fogg/config/v1"
 	"github.com/chanzuckerberg/fogg/errs"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/afero"
+	"gopkg.in/yaml.v2"
 )
 
 // Upgrade applies in-place upgrades to a configFile
@@ -26,9 +25,9 @@ func Upgrade(fs afero.Fs, configFile string) error {
 			return err
 		}
 
-		marshalled, err := json.MarshalIndent(c2, "", "  ")
+		marshalled, err := yaml.Marshal(c2)
 		if err != nil {
-			return errs.WrapInternal(err, "Could not serialize config to json.")
+			return errs.WrapInternal(err, "Could not serialize config to yaml.")
 		}
 		err = afero.WriteFile(fs, configFile, marshalled, 0644)
 		return errs.WrapInternal(err, "Could not write config to disk")
diff --git a/config/v1/config.go b/config/v1/config.go
index 3a6d1d13..a898d5af 100644
--- a/config/v1/config.go
+++ b/config/v1/config.go
@@ -13,7 +13,7 @@ import (
 )
 
 type TfLint struct {
-	Enabled *bool `json:"enabled,omitempty"`
+	Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
 }
 
 type Defaults struct {
@@ -91,7 +91,7 @@ const (
 
 // EKSConfig is the configuration for an eks cluster
 type EKSConfig struct {
-	ClusterName string `json:"cluster_name"`
+	ClusterName string `json:"cluster_name" yaml:"cluster_name"`
 }
 
 type Component struct {
@@ -126,9 +126,9 @@ type Module struct {
 }
 
 type TravisCI struct {
-	Enabled        *bool   `json:"enabled,omitempty"`
-	AWSIAMRoleName *string `json:"aws_iam_role_name,omitempty"`
-	TestBuckets    *int    `json:"test_buckets,omitempty"`
+	Enabled        *bool   `json:"enabled,omitempty" yaml:"enabled,omitempty"`
+	AWSIAMRoleName *string `json:"aws_iam_role_name" yaml:"aws_iam_role_name"`
+	TestBuckets    *int    `json:"test_buckets" yaml:"test_buckets"`
 	Command        *string `json:"command,omitempty"`
 }
 
@@ -146,9 +146,10 @@ func ReadConfig(b []byte) (*Config, error) {
 	c := &Config{
 		Docker: true,
 	}
+
 	e := json.Unmarshal(b, c)
 	if e != nil {
-		return nil, errs.WrapUser(e, "unable to parse json config file")
+		return nil, errs.WrapUser(e, "unable to parse config file")
 	}
 	return c, nil
 }
diff --git a/config/v2/config.go b/config/v2/config.go
index 2b3a5f02..b5fca6db 100644
--- a/config/v2/config.go
+++ b/config/v2/config.go
@@ -4,54 +4,73 @@ import (
 	"encoding/json"
 	"fmt"
 	"math/rand"
+	"path/filepath"
 	"reflect"
 
 	v1 "github.com/chanzuckerberg/fogg/config/v1"
 	"github.com/chanzuckerberg/fogg/errs"
+	"github.com/spf13/afero"
+	"gopkg.in/yaml.v2"
 )
 
-func ReadConfig(b []byte) (*Config, error) {
+//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{
 		Docker: false,
 	}
-	e := json.Unmarshal(b, c)
 
-	return c, errs.WrapUser(e, "unable to parse config")
+	info, e := fs.Stat(configFile)
+	if e != nil {
+		return nil, errs.WrapUser(e, "unable to find file")
+	}
+
+	ext := filepath.Ext(info.Name())
+	//Determines the file extension
+	switch ext {
+	case ".yml", ".yaml":
+		e = yaml.Unmarshal(b, c)
+	case ".json":
+		e = json.Unmarshal(b, c)
+	default:
+		return nil, errs.NewUserf("File type %s is not supported", ext)
+	}
+	return c, e
 }
 
 type Config struct {
-	Accounts map[string]Account   `json:"accounts,omitempty"`
-	Defaults Defaults             `json:"defaults" validate:"required"`
-	Docker   bool                 `json:"docker,omitempty"`
-	Envs     map[string]Env       `json:"envs,omitempty"`
-	Global   Component            `json:"global,omitempty"`
-	Modules  map[string]v1.Module `json:"modules,omitempty"`
-	Plugins  v1.Plugins           `json:"plugins,omitempty"`
-	Version  int                  `json:"version" validate:"required,eq=2"`
+	Accounts map[string]Account   `json:"accounts,omitempty" yaml:"accounts,omitempty"`
+	Defaults Defaults             `json:"defaults" yaml:"defaults" validate:"required"`
+	Docker   bool                 `json:"docker,omitempty" yaml:"docker,omitempty"`
+	Envs     map[string]Env       `json:"envs,omitempty" yaml:"envs,omitempty"`
+	Global   Component            `json:"global,omitempty" yaml:"global,omitempty"`
+	Modules  map[string]v1.Module `json:"modules,omitempty" yaml:"modules,omitempty"`
+	Plugins  v1.Plugins           `json:"plugins,omitempty" yaml:"plugins,omitempty"`
+	Version  int                  `json:"version" yaml:"version" validate:"required,eq=2"`
 }
 
 type Common struct {
-	Backend          *Backend          `json:"backend,omitempty"`
-	ExtraVars        map[string]string `json:"extra_vars,omitempty"`
-	Owner            *string           `json:"owner,omitempty" `
-	Project          *string           `json:"project,omitempty" `
-	Providers        *Providers        `json:"providers,omitempty" `
-	TerraformVersion *string           `json:"terraform_version,omitempty"`
-	Tools            *Tools            `json:"tools,omitempty"`
+	Backend          *Backend          `json:"backend,omitempty" yaml:"backend,omitempty"`
+	ExtraVars        map[string]string `json:"extra_vars,omitempty" yaml:"extra_vars,omitempty"`
+	Owner            *string           `json:"owner,omitempty" yaml:"owner,omitempty"`
+	Project          *string           `json:"project,omitempty" yaml:"project,omitempty"`
+	Providers        *Providers        `json:"providers,omitempty" yaml:"providers,omitempty"`
+	TerraformVersion *string           `json:"terraform_version,omitempty" yaml:"terraform_version,omitempty"`
+	Tools            *Tools            `json:"tools,omitempty" yaml:"tools,omitempty"`
 }
 
 type Defaults struct {
-	Common
+	Common `json:",inline" yaml:",inline"`
 }
 
 type Account struct {
-	Common
+	Common `json:",inline" yaml:",inline"`
 }
 
 type Tools struct {
-	Atlantis *Atlantis    `json:"atlantis,omitempty"`
-	TravisCI *v1.TravisCI `json:"travis_ci,omitempty"`
-	TfLint   *v1.TfLint   `json:"tflint,omitempty"`
+	Atlantis *Atlantis    `json:"atlantis,omitempty" yaml:"atlantis,omitempty"`
+	TravisCI *v1.TravisCI `json:"travis_ci,omitempty" yaml:"travis_ci,omitempty"`
+	TfLint   *v1.TfLint   `json:"tflint,omitempty" yaml:"tflint,omitempty"`
 }
 
 type Atlantis struct {
@@ -61,64 +80,64 @@ type Atlantis struct {
 }
 
 type Env struct {
-	Common
+	Common `json:",inline" yaml:",inline"`
 
-	Components map[string]Component `json:"components,omitempty"`
+	Components map[string]Component `json:"components,omitempty" yaml:"components,omitempty"`
 }
 
 type Component struct {
-	Common
+	Common `json:",inline" yaml:",inline"`
 
-	EKS          *v1.EKSConfig     `json:"eks,omitempty"`
-	Kind         *v1.ComponentKind `json:"kind,omitempty"`
-	ModuleSource *string           `json:"module_source,omitempty"`
+	EKS          *v1.EKSConfig     `json:"eks,omitempty" yaml:"eks,omitempty"`
+	Kind         *v1.ComponentKind `json:"kind,omitempty" yaml:"kind,omitempty"`
+	ModuleSource *string           `json:"module_source,omitempty" yaml:"module_source,omitempty"`
 }
 
 type Providers struct {
-	AWS       *AWSProvider       `json:"aws,omitempty"`
-	Snowflake *SnowflakeProvider `json:"snowflake,omitempty"`
-	Bless     *BlessProvider     `json:"bless,omitempty"`
-	Okta      *OktaProvider      `json:"okta,omitempty"`
+	AWS       *AWSProvider       `json:"aws,omitempty" yaml:"aws,omitempty"`
+	Snowflake *SnowflakeProvider `json:"snowflake,omitempty" yaml:"snowflake,omitempty"`
+	Bless     *BlessProvider     `json:"bless,omitempty" yaml:"bless,omitempty"`
+	Okta      *OktaProvider      `json:"okta,omitempty" yaml:"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"`
+	OrgName *string `json:"org_name,omitempty" yaml:"org_name,omitempty"`
+	Version *string `json:"version,omitempty" yaml:"version,omitempty"`
 }
 
 // BlessProvider allows for terraform-provider-bless configuration
 type BlessProvider struct {
 	// the bless provider is optional (above) but if supplied you must set a region and aws_profile
-	AdditionalRegions []string `json:"additional_regions,omitempty"`
-	AWSProfile        *string  `json:"aws_profile,omitempty"`
-	AWSRegion         *string  `json:"aws_region,omitempty"`
-	Version           *string  `json:"version,omitempty"`
+	AdditionalRegions []string `json:"additional_regions,omitempty" yaml:"additional_regions,omitempty"`
+	AWSProfile        *string  `json:"aws_profile,omitempty" yaml:"aws_profile,omitempty"`
+	AWSRegion         *string  `json:"aws_region,omitempty" yaml:"aws_region,omitempty"`
+	Version           *string  `json:"version,omitempty" yaml:"version,omitempty"`
 }
 
 type AWSProvider struct {
 	// the aws provider is optional (above) but if supplied you must set account id and region
-	AccountID         *json.Number `json:"account_id,omitempty"`
-	AdditionalRegions []string     `json:"additional_regions,omitempty"`
-	Profile           *string      `json:"profile,omitempty"`
-	Region            *string      `json:"region,omitempty"`
-	Version           *string      `json:"version,omitempty"`
+	AccountID         *json.Number `json:"account_id,omitempty" yaml:"account_id,omitempty"`
+	AdditionalRegions []string     `json:"additional_regions,omitempty" yaml:"additional_regions,omitempty"`
+	Profile           *string      `json:"profile,omitempty" yaml:"profile,omitempty"`
+	Region            *string      `json:"region,omitempty" yaml:"region,omitempty"`
+	Version           *string      `json:"version,omitempty" yaml:"version,omitempty"`
 }
 
 type SnowflakeProvider struct {
-	Account *string `json:"account,omitempty"`
-	Role    *string `json:"role,omitempty"`
-	Region  *string `json:"region,omitempty"`
-	Version *string `json:"version,omitempty"`
+	Account *string `json:"account,omitempty" yaml:"account,omitempty"`
+	Role    *string `json:"role,omitempty" yaml:"role,omitempty"`
+	Region  *string `json:"region,omitempty" yaml:"region,omitempty"`
+	Version *string `json:"version,omitempty" yaml:"version,omitempty"`
 }
 
 type Backend struct {
-	AccountID   *string `json:"account_id,omitempty"`
-	Bucket      *string `json:"bucket,omitempty"`
-	DynamoTable *string `json:"dynamodb_table,omitempty"`
-	Profile     *string `json:"profile,omitempty"`
-	Region      *string `json:"region,omitempty"`
+	AccountID   *string `json:"account_id,omitempty" yaml:"account_id,omitempty"`
+	Bucket      *string `json:"bucket,omitempty" yaml:"bucket,omitempty"`
+	DynamoTable *string `json:"dynamodb_table,omitempty" yaml:"dynamodb_table,omitempty"`
+	Profile     *string `json:"profile,omitempty" yaml:"profile,omitempty"`
+	Region      *string `json:"region,omitempty" yaml:"region,omitempty"`
 }
 
 // Generate is used for test/quick integration. There are supposedly ways to do this without polluting the public
diff --git a/init/init.go b/init/init.go
index d093f8f0..d9f0665f 100644
--- a/init/init.go
+++ b/init/init.go
@@ -1,13 +1,12 @@
 package init
 
 import (
-	"encoding/json"
-
 	"github.com/chanzuckerberg/fogg/config"
-	"github.com/chanzuckerberg/fogg/config/v1"
+	v2 "github.com/chanzuckerberg/fogg/config/v2"
 	"github.com/chanzuckerberg/fogg/errs"
 	prompt "github.com/segmentio/go-prompt"
 	"github.com/spf13/afero"
+	"gopkg.in/yaml.v2"
 )
 
 const AWSProviderVersion = "1.27.0"
@@ -23,19 +22,21 @@ func userPrompt() (string, string, string, string, string, string) {
 	return project, region, bucket, table, profile, owner
 }
 
-func writeConfig(fs afero.Fs, config *v1.Config) error {
-	json, e := json.MarshalIndent(config, "", "  ")
-	if e != nil {
-		return errs.WrapInternal(e, "unable to marshal json")
+func writeConfig(fs afero.Fs, config *v2.Config) error {
+	yaml, err := yaml.Marshal(config)
+	if err != nil {
+		return errs.WrapInternal(err, "unable to marshal yaml")
 	}
-	configFile, e := fs.Create("fogg.json")
-	if e != nil {
-		return errs.WrapInternal(e, "unable to create config file fogg.json")
+
+	yamlConfigFile, err := fs.Create("fogg.yml")
+	if err != nil {
+		return errs.WrapInternal(err, "unable to create config file fogg.yml")
 	}
-	_, e3 := configFile.Write(json)
-	return e3
+	_, err = yamlConfigFile.Write(yaml)
+	return err
 }
 
+//Init reads user console input and generates a fogg.yml file
 func Init(fs afero.Fs) error {
 	project, region, bucket, table, profile, owner := userPrompt()
 	config := config.InitConfig(project, region, bucket, table, profile, owner, AWSProviderVersion)
diff --git a/migrate/yaml.go b/migrate/yaml.go
new file mode 100644
index 00000000..bc460666
--- /dev/null
+++ b/migrate/yaml.go
@@ -0,0 +1,26 @@
+package migrate
+
+import (
+	"github.com/chanzuckerberg/fogg/config"
+	"github.com/spf13/afero"
+	"gopkg.in/yaml.v2"
+)
+
+//ConvertToYaml method converts fogg.json to fogg.yml
+func ConvertToYaml(fs afero.Fs, configFile string) error {
+	config, err := config.FindAndReadConfig(fs, configFile)
+	if err != nil {
+		return err
+	}
+
+	yml, err := yaml.Marshal(config)
+	if err != nil {
+		return err
+	}
+
+	err = afero.WriteFile(fs, "fogg.yml", yml, 0644)
+	if err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/plan/plan.go b/plan/plan.go
index 38451668..02809490 100644
--- a/plan/plan.go
+++ b/plan/plan.go
@@ -9,18 +9,18 @@ import (
 	v2 "github.com/chanzuckerberg/fogg/config/v2"
 	"github.com/chanzuckerberg/fogg/errs"
 	"github.com/chanzuckerberg/fogg/util"
-	yaml "gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v2"
 )
 
 // Plan represents a set of actions to take
 type Plan struct {
-	Accounts map[string]Account
-	Atlantis Atlantis
-	Envs     map[string]Env
-	Global   Component
-	Modules  map[string]Module
-	TravisCI TravisCI
-	Version  string
+	Accounts map[string]Account `json:"account" yaml:"account"`
+	Atlantis Atlantis           `json:"atlantis" yaml:"atlantis"`
+	Envs     map[string]Env     `json:"envs" yaml:"envs"`
+	Global   Component          `json:"global" yaml:"global"`
+	Modules  map[string]Module  `json:"modules" yaml:"modules"`
+	TravisCI TravisCI           `json:"travis_ci" yaml:"travis_ci"`
+	Version  string             `json:"version" yaml:"version"`
 }
 
 // Common represents common fields
@@ -29,16 +29,17 @@ type Common struct {
 	TerraformVersion string `yaml:"terraform_version"`
 }
 
+//ComponentCommon represents common fields for components
 type ComponentCommon struct {
 	Common `yaml:",inline"`
 
 	Atlantis  AtlantisComponent `yaml:"atlantis"`
-	Backend   AWSBackend        `yaml:"backend"`
-	ExtraVars map[string]string `yaml:"extra_vars"`
-	Owner     string
-	Project   string
-	Providers Providers `yaml:"providers"`
-	TfLint    TfLint
+	Backend   AWSBackend        `json:"backend" yaml:"backend"`
+	ExtraVars map[string]string `json:"extra_vars" yaml:"extra_vars"`
+	Owner     string            `json:"owner" yaml:"owner"`
+	Project   string            `json:"project" yaml:"project"`
+	Providers Providers         `json:"providers" yaml:"providers"`
+	TfLint    TfLint            `json:"tf_lint" yaml:"tf_lint"`
 	TravisCI  TravisComponent
 }
 
@@ -63,6 +64,7 @@ type Providers struct {
 	Okta      *OktaProvider      `yaml:"okta"`
 }
 
+//AWSProvider represents AWS provider configuration
 type AWSProvider struct {
 	AccountID         json.Number `yaml:"account_id"`
 	Profile           string      `yaml:"profile"`
@@ -71,43 +73,46 @@ type AWSProvider struct {
 	AdditionalRegions []string    `yaml:"additional_regions"`
 }
 
+//SnowflakeProvider represents Snowflake DB provider configuration
 type SnowflakeProvider struct {
-	Account string  `yaml:"account,omitempty"`
-	Role    string  `yaml:"role,omitempty"`
-	Region  string  `yaml:"region,omitempty"`
-	Version *string `yaml:"version,omitempty"`
+	Account string  `json:"account,omitempty" yaml:"account,omitempty"`
+	Role    string  `json:"role,omitempty" yaml:"role,omitempty"`
+	Region  string  `json:"region,omitempty" yaml:"region,omitempty"`
+	Version *string `json:"version,omitempty" yaml:"version,omitempty"`
 }
 
+//OktaProvider represents Okta configuration
 type OktaProvider struct {
 	OrgName string  `json:"org_name,omitempty"`
 	Version *string `json:"version,omitempty"`
 }
 
+//BlessProvider represents Bless ssh provider configuration
 type BlessProvider struct {
-	AdditionalRegions []string `yaml:"additional_regions,omitempty"`
-	AWSProfile        string   `yaml:"aws_profile,omitempty"`
-	AWSRegion         string   `yaml:"aws_region,omitempty"`
-	Version           *string  `yaml:"version,omitempty"`
+	AdditionalRegions []string `json:"additional_regions,omitempty" yaml:"additional_regions,omitempty"`
+	AWSProfile        string   `json:"aws_profile,omitempty" yaml:"aws_profile,omitempty"`
+	AWSRegion         string   `json:"aws_region,omitempty" yaml:"aws_region,omitempty"`
+	Version           *string  `json:"version,omitempty" yaml:"version,omitempty"`
 }
 
 // AWSBackend represents aws backend configuration
 type AWSBackend struct {
 	AccountID   *string `yaml:"account_id,omitempty"`
-	AccountName string  `yaml:"account_name"`
-	Profile     string  `yaml:"profile"`
-	Region      string  `yaml:"region"`
-	Bucket      string  `yaml:"bucket"`
-	DynamoTable *string `yaml:"dynamo_table"`
+	AccountName string  `json:"account_name" yaml:"account_name"`
+	Profile     string  `json:"profile" yaml:"profile"`
+	Region      string  `json:"region" yaml:"region"`
+	Bucket      string  `json:"bucket" yaml:"bucket"`
+	DynamoTable *string `json:"dynamo_table" yaml:"dynamo_table"`
 }
 
 // Module is a module
 type Module struct {
-	Common `yaml:",inline"`
+	Common `json:",inline" yaml:",inline"`
 }
 
 // Account is an account
 type Account struct {
-	ComponentCommon `yaml:",inline"`
+	ComponentCommon `json:",inline" yaml:",inline"`
 
 	AllAccounts map[string]json.Number `yaml:"all_accounts"`
 	AccountName string                 `yaml:"account_name"`
@@ -116,29 +121,29 @@ type Account struct {
 
 // Component is a component
 type Component struct {
-	ComponentCommon `yaml:",inline"`
+	ComponentCommon `json:",inline" yaml:",inline"`
 
-	Accounts  map[string]Account // Reference accounts for remote state
-	Component string
-	EKS       *v1.EKSConfig `yaml:"eks,omitempty"`
+	Accounts  map[string]Account `json:"accounts" yaml:"accounts"` // Reference accounts for remote state
+	Component string             `json:"component" yaml:"component"`
+	EKS       *v1.EKSConfig      `json:"eks,omitempty" yaml:"eks,omitempty"`
 	Env       string
 
-	Kind            *v1.ComponentKind `yaml:"kind,omitempty"`
-	ModuleSource    *string           `yaml:"module_source"`
-	OtherComponents []string          `yaml:"other_components"`
-	Global          *Component
+	Kind            *v1.ComponentKind `json:"kind,omitempty" yaml:"kind,omitempty"`
+	ModuleSource    *string           `json:"module_source" yaml:"module_source"`
+	OtherComponents []string          `json:"other_components" yaml:"other_components"`
+	Global          *Component        `json:"global" yaml:"global"`
 }
 
 // Env is an env
 type Env struct {
-	Components map[string]Component
-	Env        string
-	EKS        *v1.EKSConfig //TODO get rid of this
+	Components map[string]Component `json:"components" yaml:"components"`
+	Env        string               `json:"env" yaml:"env"`
+	EKS        *v1.EKSConfig        `json:"eks" yaml:"eks"`
 }
 
 // TfLint containts a plan for running tflint
 type TfLint struct {
-	Enabled bool
+	Enabled bool `json:"enabled" yaml:"enabled"`
 }
 
 // Eval evaluates a config
diff --git a/plugins/custom_plugin.go b/plugins/custom_plugin.go
index b9578d1d..29bf57ac 100644
--- a/plugins/custom_plugin.go
+++ b/plugins/custom_plugin.go
@@ -35,15 +35,15 @@ const (
 
 // CustomPlugin is a custom plugin
 type CustomPlugin struct {
-	URL       string           `json:"url" validate:"required"`
-	Format    TypePluginFormat `json:"format" validate:"required"`
-	TarConfig *TarConfig       `json:"tar_config,omitempty"`
-	TargetDir string           `json:"target_dir,omitempty"`
+	URL       string           `json:"url" yaml:"url" validate:"required"`
+	Format    TypePluginFormat `json:"format" yaml:"format" validate:"required"`
+	TarConfig *TarConfig       `json:"tar_config,omitempty" yaml:"tar_config,omitempty"`
+	TargetDir string           `json:"target_dir,omitempty" yaml:"target_dir,omitempty"`
 }
 
 // TarConfig configures the tar unpacking
 type TarConfig struct {
-	StripComponents int `json:"strip_components,omitempty"`
+	StripComponents int `json:"strip_components,omitempty" yaml:"strip_components,omitempty"`
 }
 
 func (tc *TarConfig) getStripComponents() int {


Test patch:
diff --git a/apply/apply_test.go b/apply/apply_test.go
index 19abc77e..4a32df07 100644
--- a/apply/apply_test.go
+++ b/apply/apply_test.go
@@ -10,12 +10,14 @@ import (
 
 	"github.com/chanzuckerberg/fogg/config"
 	v1 "github.com/chanzuckerberg/fogg/config/v1"
+	v2 "github.com/chanzuckerberg/fogg/config/v2"
 	"github.com/chanzuckerberg/fogg/templates"
 	"github.com/chanzuckerberg/fogg/util"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v2"
 )
 
 func init() {
@@ -222,52 +224,80 @@ func TestApplySmokeTest(t *testing.T) {
 	r := require.New(t)
 	fs, _, err := util.TestFs()
 	r.NoError(err)
-	// defer os.RemoveAll(d)
+
+	yml := `
+defaults:
+  owner: foo
+  project: bar
+  terraform_version: 0
+  backend:
+    bucket: baz
+    region: qux
+    profile: quux
+version: 2
+`
+	b, e := yaml.Marshal(yml)
+	r.NoError(e)
+	e = afero.WriteFile(fs, "fogg.yml", b, 0644)
+	r.NoError(e)
+	c, e := v2.ReadConfig(fs, []byte(yml), "fogg.yml")
+	r.NoError(e)
+
+	w, e := c.Validate()
+	r.NoError(e)
+	r.Len(w, 0)
+
+	e = Apply(fs, c, templates.Templates, false)
+	r.NoError(e)
+}
+
+func TestApplySmokeTestJSON(t *testing.T) {
+	r := require.New(t)
+	fs, _, err := util.TestFs()
+	r.NoError(err)
 
 	json := `
-{
-  "defaults": {
-    "aws_region_provider": "reg",
-    "aws_region_backend": "reg",
-    "aws_profile_provider": "prof",
-    "aws_profile_backend": "prof",
-    "aws_provider_version": "0.12.0",
-    "account_id": 789,
-    "infra_s3_bucket": "buck",
-    "project": "proj",
-    "terraform_version": "0.100.0",
-		"owner": "foo@example.com",
-		"tools": {
-			  "travis_ci": {
-						"enabled": true,
-						"aws_iam_role_name": "travis",
-						"id_account_name": "id",
-						"test_buckets": 7
-			  }
+	{
+	  "defaults": {
+		"aws_region_provider": "reg",
+		"aws_region_backend": "reg",
+		"aws_profile_provider": "prof",
+		"aws_profile_backend": "prof",
+		"aws_provider_version": "0.12.0",
+		"account_id": 789,
+		"infra_s3_bucket": "buck",
+		"project": "proj",
+		"terraform_version": "0.100.0",
+		"owner": "foo@example.com"
+	  },
+	  "travis_ci": {
+		"enabled": true,
+		"aws_iam_role_name": "travis",
+			"id_account_name": "id",
+			"test_buckets": 7
+	  },
+	  "accounts": {
+		"foo": {
+		  "account_id": 123
+		},
+		"bar": {
+		  "account_id": 456
 		}
-  },
-  "accounts": {
-    "foo": {
-      "account_id": 123
-    },
-    "bar": {
-      "account_id": 456
-    }
-  },
-  "modules": {
-    "my_module": {}
-  },
-  "envs": {
-    "staging":{
-        "components": {
-            "comp1": {},
-            "comp2": {}
-        }
-    },
-    "prod": {}
-  }
-}
-`
+	  },
+	  "modules": {
+		"my_module": {}
+	  },
+	  "envs": {
+		"staging":{
+			"components": {
+				"comp1": {},
+				"comp2": {}
+			}
+		},
+		"prod": {}
+	  }
+	}
+	`
 	c, e := v1.ReadConfig([]byte(json))
 	r.NoError(e)
 	c2, e := config.UpgradeConfigVersion(c)
diff --git a/apply/golden_file_test.go b/apply/golden_file_test.go
index def2f47a..2331d637 100644
--- a/apply/golden_file_test.go
+++ b/apply/golden_file_test.go
@@ -63,18 +63,23 @@ func TestIntegration(t *testing.T) {
 				e = apply.Apply(testdataFs, conf, templates.Templates, true)
 				a.NoError(e)
 			} else {
-
+				fileName := "fogg.yml"
 				fs, _, e := util.TestFs()
 				a.NoError(e)
 
 				// copy fogg.json into the tmp test dir (so that it doesn't show up as a diff)
-				configContents, e := afero.ReadFile(testdataFs, "fogg.json")
+				configContents, e := afero.ReadFile(testdataFs, fileName)
+				if os.IsNotExist(e) { //If the error is related to the file being non-existent
+					fileName = "fogg.json"
+					configContents, e = afero.ReadFile(testdataFs, fileName)
+				}
 				a.NoError(e)
-				configMode, e := testdataFs.Stat("fogg.json")
+
+				configMode, e := testdataFs.Stat(fileName)
 				a.NoError(e)
-				a.NoError(afero.WriteFile(fs, "fogg.json", configContents, configMode.Mode()))
+				a.NoError(afero.WriteFile(fs, fileName, configContents, configMode.Mode()))
 
-				conf, e := config.FindAndReadConfig(fs, "fogg.json")
+				conf, e := config.FindAndReadConfig(fs, fileName)
 				a.NoError(e)
 				fmt.Printf("conf %#v\n", conf)
 
diff --git a/config/config_test.go b/config/config_test.go
index 11dfb908..8e1b429c 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -18,16 +18,16 @@ import (
 func TestInitConfig(t *testing.T) {
 	a := assert.New(t)
 	c := InitConfig("proj", "reg", "buck", "table", "prof", "me@foo.example", "0.99.0")
-	a.Equal("prof", c.Defaults.AWSProfileBackend)
-	a.Equal("prof", c.Defaults.AWSProfileProvider)
-	a.Equal("reg", c.Defaults.AWSRegionBackend)
-	a.Equal("reg", c.Defaults.AWSRegionProvider)
-	a.Equal("0.99.0", c.Defaults.AWSProviderVersion)
-	a.Equal("buck", c.Defaults.InfraBucket)
-	a.Equal("table", c.Defaults.InfraDynamoTable)
-	a.Equal("me@foo.example", c.Defaults.Owner)
-	a.Equal("proj", c.Defaults.Project)
-	a.Equal("0.11.7", c.Defaults.TerraformVersion)
+	a.Equal("prof", *c.Defaults.Common.Backend.Profile)
+	a.Equal("prof", *c.Defaults.Providers.AWS.Profile)
+	a.Equal("reg", *c.Defaults.Providers.AWS.Region)
+	a.Equal("reg", *c.Defaults.Providers.AWS.Region)
+	a.Equal("0.99.0", *c.Defaults.Providers.AWS.Version)
+	a.Equal("buck", *c.Defaults.Common.Backend.Bucket)
+	a.Equal("table", *c.Defaults.Common.Backend.DynamoTable)
+	a.Equal("me@foo.example", *c.Defaults.Owner)
+	a.Equal("proj", *c.Defaults.Project)
+	a.Equal("0.11.7", *c.Defaults.TerraformVersion)
 }
 
 func Test_detectVersion(t *testing.T) {
@@ -45,9 +45,24 @@ func Test_detectVersion(t *testing.T) {
 		{"explicit 2", args{[]byte(`{"version": 2}`)}, 2, false},
 		{"err", args{[]byte(`{`)}, 0, true},
 	}
+	a := assert.New(t)
+	fs, _, err := util.TestFs()
+	a.NoError(err)
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := detectVersion(tt.args.b)
+			var got int
+			var err error
+			switch tt.want {
+			case 1:
+				afero.WriteFile(fs, "fogg.json", tt.args.b, 0644)
+				got, err = detectVersion(tt.args.b, fs, "fogg.json")
+			case 2:
+				afero.WriteFile(fs, "fogg.yml", tt.args.b, 0644)
+				got, err = detectVersion(tt.args.b, fs, "fogg.yml")
+			default:
+				got, err = detectVersion(tt.args.b, fs, "fogg.yml")
+			}
+
 			if (err != nil) != tt.wantErr {
 				t.Errorf("detectVersion() error = %v, wantErr %v", err, tt.wantErr)
 				return
diff --git a/config/upgrade_test.go b/config/upgrade_test.go
index 4c25e246..541c4438 100644
--- a/config/upgrade_test.go
+++ b/config/upgrade_test.go
@@ -10,7 +10,7 @@ import (
 
 func TestUpgradeV2(t *testing.T) {
 	r := require.New(t)
-	confPath := "config"
+	confPath := "fogg.yml"
 	conf := []byte(`{"version": 1}`)
 	fs, _, err := util.TestFs()
 	r.Nil(err)
@@ -34,7 +34,7 @@ func TestUpgradeUnknownVersion(t *testing.T) {
 
 func TestUpgradeV1(t *testing.T) {
 	r := require.New(t)
-	confPath := "config"
+	confPath := "fogg.yml"
 	fs, _, err := util.TestFs()
 	r.Nil(err)
 
diff --git a/config/v1/config_test.go b/config/v1/config_test.go
index 05f1efe1..69763903 100644
--- a/config/v1/config_test.go
+++ b/config/v1/config_test.go
@@ -36,10 +36,21 @@ func TestParseDefaults(t *testing.T) {
 			"terraform_version": "0.11.0"
 		}
 	}`
+
 	r := ioutil.NopCloser(strings.NewReader(json))
 	defer r.Close()
+
 	c, e := ReadConfig([]byte(json))
 	assert.NoError(t, e)
+
+	assert.Equal(t, "czi", c.Defaults.AWSProfileBackend)
+	assert.Equal(t, "czi", c.Defaults.AWSProfileProvider)
+	assert.Equal(t, "the-bucket", c.Defaults.InfraBucket)
+	assert.Equal(t, "the-table", c.Defaults.InfraDynamoTable)
+	assert.Equal(t, "test-project", c.Defaults.Project)
+	assert.Equal(t, "0.11.0", c.Defaults.TerraformVersion)
+	assert.Equal(t, true, c.Docker)
+
 	assert.NotNil(t, c.Defaults)
 	assert.Equal(t, "us-west-2", c.Defaults.AWSRegionBackend)
 	assert.Equal(t, "us-west-1", c.Defaults.AWSRegionProvider)
@@ -83,9 +94,9 @@ func TestParse(t *testing.T) {
 	assert.NotNil(t, c.Modules)
 }
 
-func TestJsonFailure(t *testing.T) {
-	json := `foo`
-	r := ioutil.NopCloser(strings.NewReader(json))
+func TestYamlFailure(t *testing.T) {
+	yaml := `foo`
+	r := ioutil.NopCloser(strings.NewReader(yaml))
 	defer r.Close()
 	b, e := ioutil.ReadAll(r)
 	assert.NoError(t, e)
@@ -134,6 +145,7 @@ func TestExtraVarsValidation(t *testing.T) {
 			"terraform_version": "0.11.0"
 		}
 	}`
+
 	r := ioutil.NopCloser(strings.NewReader(json))
 	defer r.Close()
 
diff --git a/config/v2/config_test.go b/config/v2/config_test.go
index 0c02c4e2..cd617b45 100644
--- a/config/v2/config_test.go
+++ b/config/v2/config_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/chanzuckerberg/fogg/util"
+	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -13,7 +14,12 @@ func TestReadConfig(t *testing.T) {
 
 	b, e := util.TestFile("empty")
 	a.NoError(e)
-	c, e := ReadConfig(b)
+
+	fs, _, e := util.TestFs()
+	a.NoError(e)
+	e = afero.WriteFile(fs, "fogg.json", b, 0644)
+	a.NoError(e)
+	c, e := ReadConfig(fs, b, "fogg.json")
 	a.NoError(e)
 
 	w, e := c.Validate()
@@ -23,7 +29,7 @@ func TestReadConfig(t *testing.T) {
 	b2, e := util.TestFile("v2_minimal_valid")
 	a.NoError(e)
 
-	c, e = ReadConfig(b2)
+	c, e = ReadConfig(fs, b2, "fogg.json")
 	a.NoError(e)
 
 	w, e = c.Validate()
@@ -31,6 +37,24 @@ func TestReadConfig(t *testing.T) {
 	a.Len(w, 0)
 }
 
+func TestReadConfigYaml(t *testing.T) {
+	a := assert.New(t)
+
+	b2, e := util.TestFile("v2_minimal_valid_yaml")
+	a.NoError(e)
+
+	fs, _, e := util.TestFs()
+	a.NoError(e)
+	e = afero.WriteFile(fs, "fogg.yml", b2, 0644)
+	a.NoError(e)
+	c, e := ReadConfig(fs, b2, "fogg.yml")
+	a.NoError(e)
+
+	w, e := c.Validate()
+	a.NoError(e)
+	a.Len(w, 0)
+}
+
 func TestReadSnowflakeProvider(t *testing.T) {
 	r := require.New(t)
 
@@ -38,7 +62,37 @@ func TestReadSnowflakeProvider(t *testing.T) {
 	r.NoError(e)
 	r.NotNil(b)
 
-	c, e := ReadConfig(b)
+	fs, _, e := util.TestFs()
+	r.NoError(e)
+	e = afero.WriteFile(fs, "fogg.json", b, 0644)
+	r.NoError(e)
+	c, e := ReadConfig(fs, b, "fogg.json")
+	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.Snowflake)
+	r.Equal("foo", *c.Defaults.Providers.Snowflake.Account)
+	r.Equal("bar", *c.Defaults.Providers.Snowflake.Role)
+	r.Equal("us-west-2", *c.Defaults.Providers.Snowflake.Region)
+}
+
+func TestReadSnowflakeProviderYaml(t *testing.T) {
+	r := require.New(t)
+
+	b, e := util.TestFile("snowflake_provider_yaml")
+	r.NoError(e)
+	r.NotNil(b)
+
+	fs, _, e := util.TestFs()
+	r.NoError(e)
+	e = afero.WriteFile(fs, "fogg.yml", b, 0644)
+	r.NoError(e)
+	c, e := ReadConfig(fs, b, "fogg.yml")
 	r.NoError(e)
 	r.NotNil(c)
 
@@ -60,7 +114,11 @@ func TestReadBlessProvider(t *testing.T) {
 	r.NoError(e)
 	r.NotNil(b)
 
-	c, e := ReadConfig(b)
+	fs, _, e := util.TestFs()
+	r.NoError(e)
+	e = afero.WriteFile(fs, "fogg.json", b, 0644)
+	r.NoError(e)
+	c, e := ReadConfig(fs, b, "fogg.json")
 	r.NoError(e)
 	r.NotNil(c)
 
@@ -83,7 +141,11 @@ func TestReadOktaProvider(t *testing.T) {
 	r.NoError(e)
 	r.NotNil(b)
 
-	c, e := ReadConfig(b)
+	fs, _, e := util.TestFs()
+	r.NoError(e)
+	e = afero.WriteFile(fs, "fogg.json", b, 0644)
+	r.NoError(e)
+	c, e := ReadConfig(fs, b, "fogg.json")
 	r.NoError(e)
 	r.NotNil(c)
 
@@ -97,14 +159,18 @@ func TestReadOktaProvider(t *testing.T) {
 	r.Equal("orgname", *c.Defaults.Providers.Okta.OrgName)
 }
 
-func TestReadAtlantis(t *testing.T) {
+func TestReadBlessProviderYaml(t *testing.T) {
 	r := require.New(t)
 
-	b, e := util.TestFile("v2_full")
+	b, e := util.TestFile("bless_provider_yaml")
 	r.NoError(e)
 	r.NotNil(b)
 
-	c, e := ReadConfig(b)
+	fs, _, e := util.TestFs()
+	r.NoError(e)
+	e = afero.WriteFile(fs, "fogg.yml", b, 0644)
+	r.NoError(e)
+	c, e := ReadConfig(fs, b, "fogg.yml")
 	r.NoError(e)
 	r.NotNil(c)
 
@@ -112,6 +178,27 @@ func TestReadAtlantis(t *testing.T) {
 	r.NoError(e)
 	r.Len(w, 0)
 
+	r.NotNil(c.Defaults.Providers)
+	r.NotNil(c.Defaults.Providers.Bless)
+	r.Equal("foofoofoo", *c.Defaults.Providers.Bless.AWSProfile)
+	r.Equal("bar", *c.Defaults.Providers.Bless.AWSRegion)
+	r.Equal("0.0.0", *c.Defaults.Providers.Bless.Version)
+	r.Equal([]string{"a", "b"}, c.Defaults.Providers.Bless.AdditionalRegions)
+}
+
+func TestReadAtlantis(t *testing.T) {
+	r := require.New(t)
+
+	b, e := util.TestFile("v2_full")
+	r.NoError(e)
+	r.NotNil(b)
+
+	fs, _, e := util.TestFs()
+	r.NoError(e)
+	e = afero.WriteFile(fs, "fogg.json", b, 0644)
+	r.NoError(e)
+	c, e := ReadConfig(fs, b, "fogg.json")
+
 	r.NotNil(c.Defaults.Tools)
 	r.NotNil(c.Defaults.Tools.Atlantis)
 	r.True(*c.Defaults.Tools.Atlantis.Enabled)
diff --git a/config/v2/validation_test.go b/config/v2/validation_test.go
index 7cd558ed..f56004a8 100644
--- a/config/v2/validation_test.go
+++ b/config/v2/validation_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/chanzuckerberg/fogg/util"
+	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -164,11 +165,17 @@ func TestConfig_ValidateAWSProviders(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.fileName, func(t *testing.T) {
 			r := require.New(t)
+			fs, _, e := util.TestFs()
+			r.NoError(e)
 
 			b, e := util.TestFile(tt.fileName)
 			r.NoError(e)
-			c, e := ReadConfig(b)
+			e = afero.WriteFile(fs, "fogg.json", b, 0644)
+			r.NoError(e)
+
+			c, e := ReadConfig(fs, b, "fogg.json")
 			r.NoError(e)
+			r.NotNil(c)
 
 			if err := c.ValidateAWSProviders(); (err != nil) != tt.wantErr {
 				t.Errorf("Config.ValidateAWSProviders() error = %v, wantErr %v (err != nil) %v", err, tt.wantErr, (err != nil))
@@ -187,10 +194,15 @@ func TestConfig_ValidateTravis(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.fileName, func(t *testing.T) {
 			r := require.New(t)
+			fs, _, e := util.TestFs()
+			r.NoError(e)
 
 			b, e := util.TestFile(tt.fileName)
 			r.NoError(e)
-			c, e := ReadConfig(b)
+			e = afero.WriteFile(fs, "fogg.json", b, 0644)
+			r.NoError(e)
+
+			c, e := ReadConfig(fs, b, "fogg.json")
 			r.NoError(e)
 
 			if err := c.ValidateTravis(); (err != nil) != tt.wantErr {
diff --git a/plan/plan_test.go b/plan/plan_test.go
index 64a264b0..728414d1 100644
--- a/plan/plan_test.go
+++ b/plan/plan_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/chanzuckerberg/fogg/util"
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
+	"github.com/spf13/afero"
 )
 
 func init() {
@@ -106,7 +107,59 @@ func TestPlanBasicV2(t *testing.T) {
 	b, e := util.TestFile("v2_full")
 	assert.NoError(t, e)
 
-	c2, err := v2.ReadConfig(b)
+	fs, _, err := util.TestFs()
+	err = afero.WriteFile(fs, "fogg.json", b, 0644)
+	a.NoError(err)
+
+	c2, err := v2.ReadConfig(fs, b, "fogg.json")
+	assert.Nil(t, err)
+
+	w, err := c2.Validate()
+	a.NoError(err)
+	a.Len(w, 0)
+
+	plan, e := Eval(c2)
+	assert.Nil(t, e)
+	assert.NotNil(t, plan)
+	assert.NotNil(t, plan.Accounts)
+	assert.Len(t, plan.Accounts, 2)
+
+	assert.NotNil(t, plan.Modules)
+	assert.Len(t, plan.Modules, 1)
+	assert.Equal(t, "0.100.0", plan.Modules["my_module"].TerraformVersion)
+
+	assert.NotNil(t, plan.Envs)
+	assert.Len(t, plan.Envs, 2)
+
+	assert.NotNil(t, plan.Envs["staging"])
+
+	assert.NotNil(t, plan.Envs["staging"].Components)
+	assert.Len(t, plan.Envs["staging"].Components, 4)
+
+	assert.NotNil(t, plan.Envs["staging"])
+	assert.NotNil(t, plan.Envs["staging"].Components["vpc"])
+	logrus.Debugf("%#v\n", plan.Envs["staging"].Components["vpc"].ModuleSource)
+	assert.NotNil(t, *plan.Envs["staging"].Components["vpc"].ModuleSource)
+	assert.Equal(t, "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v1.30.0", *plan.Envs["staging"].Components["vpc"].ModuleSource)
+
+	assert.NotNil(t, plan.Envs["staging"].Components["comp1"])
+	assert.Equal(t, "0.100.0", plan.Envs["staging"].Components["comp1"].TerraformVersion)
+
+	assert.NotNil(t, plan.Envs["staging"].Components["comp_helm_template"])
+	assert.Equal(t, "k8s", plan.Envs["staging"].Components["comp_helm_template"].EKS.ClusterName)
+}
+
+func TestPlanBasicV2Yaml(t *testing.T) {
+	a := assert.New(t)
+
+	b, e := util.TestFile("v2_full_yaml")
+	assert.NoError(t, e)
+
+	fs, _, err := util.TestFs()
+	a.NoError(err)
+	err = afero.WriteFile(fs, "fogg.yml", b, 0644)
+	a.NoError(err)
+	c2, err := v2.ReadConfig(fs, b, "fogg.yml" )
 	assert.Nil(t, err)
 
 	w, err := c2.Validate()
diff --git a/testdata/bless_provider_yaml/fogg.yml b/testdata/bless_provider_yaml/fogg.yml
new file mode 100644
index 00000000..cf7af211
--- /dev/null
+++ b/testdata/bless_provider_yaml/fogg.yml
@@ -0,0 +1,29 @@
+accounts:
+  foo: {}
+defaults:
+  backend:
+    bucket: bucket
+    profile: foofoo
+    region: region
+  owner: foo@example.com
+  project: foofoo
+  providers:
+    bless:
+      additional_regions:
+      - a
+      - b
+      aws_profile: foofoofoo
+      aws_region: bar
+      version: 0.0.0
+  terraform_version: 1.1.1
+envs:
+  bar:
+    components:
+      bam: 
+        eks: 
+          cluster_name: foo
+          kind: bar
+modules:
+  foo: 
+    terraform_version: 1.1.1
+version: 2
diff --git a/testdata/snowflake_provider_yaml/fogg.yml b/testdata/snowflake_provider_yaml/fogg.yml
new file mode 100644
index 00000000..381776ce
--- /dev/null
+++ b/testdata/snowflake_provider_yaml/fogg.yml
@@ -0,0 +1,27 @@
+accounts:
+  foo: {}
+defaults:
+  backend:
+    bucket: bucket
+    profile: foo
+    region: region
+  owner: foo@example.com
+  project: foo
+  providers:
+    snowflake:
+      account: foo
+      region: us-west-2
+      role: bar
+  terraform_version: 1.1.1
+envs:
+  bar:
+    components:
+      bam:
+        eks: 
+          cluster_name: foo
+          kind: bar
+modules:
+  foo: 
+    terraform_version: 1.1.1
+        
+version: 2
diff --git a/testdata/v2_full_plan_yaml/fogg.yml b/testdata/v2_full_plan_yaml/fogg.yml
new file mode 100644
index 00000000..0c1bf61c
--- /dev/null
+++ b/testdata/v2_full_plan_yaml/fogg.yml
@@ -0,0 +1,43 @@
+accounts:
+  bar:
+    account_id: 456
+  foo:
+    account_id: 123
+defaults:
+  aws_profile_backend: prof
+  aws_profile_provider: prof
+  aws_region_backend: reg
+  aws_region_provider: reg
+  extra_vars:
+    foo: bar1
+  infra_s3_bucket: buck
+  owner: foo@example.com
+  project: proj
+  terraform_version: 0.100.0
+envs:
+  prod: {}
+  staging:
+    components:
+      comp_helm_template:
+        eks:
+          cluster_name: k8s
+        kind: helm_template
+      comp1: {}
+      comp2: {}
+      vpc:
+        extra_vars:
+          foo: bar3
+        module_source: github.com/terraform-aws-modules/terraform-aws-vpc?ref=v1.30.0
+    extra_vars:
+      foo: bar2
+modules:
+  my_module: {}
+plugins:
+  custom_plugins:
+    custom:
+      format: zip
+      url: https://example.com/custom.zip
+  terraform_providers:
+    provider:
+      format: tar
+      url: https://example.com/provider.tar.gz
diff --git a/testdata/v2_full_yaml/fogg.yml b/testdata/v2_full_yaml/fogg.yml
new file mode 100644
index 00000000..0ad081b8
--- /dev/null
+++ b/testdata/v2_full_yaml/fogg.yml
@@ -0,0 +1,55 @@
+accounts:
+  bar:
+    providers:
+      aws:
+        account_id: 456
+  foo:
+    providers:
+      aws:
+        account_id: 123
+defaults:
+  backend:
+    bucket: buck
+    profile: profile
+    region: us-west-2
+  extra_vars:
+    foo: bar1
+  owner: foo@example.com
+  project: proj
+  providers:
+    aws:
+      account_id: 456
+      profile: profile
+      region: us-west-2
+      version: 0.12.0
+  terraform_version: 0.100.0
+envs:
+  prod: {}
+  staging:
+    components:
+      comp_helm_template:
+        eks:
+          cluster_name: k8s
+        kind: helm_template
+      comp1:
+        backend:
+          profile: comp1
+      comp2: {}
+      vpc:
+        extra_vars:
+          foo: bar3
+        module_source: github.com/terraform-aws-modules/terraform-aws-vpc?ref=v1.30.0
+    extra_vars:
+      foo: bar2
+modules:
+  my_module: {}
+plugins:
+  custom_plugins:
+    custom:
+      format: zip
+      url: https://example.com/custom.zip
+  terraform_providers:
+    provider:
+      format: tar
+      url: https://example.com/provider.tar.gz
+version: 2
diff --git a/testdata/v2_minimal_valid_yaml/fogg.yml b/testdata/v2_minimal_valid_yaml/fogg.yml
new file mode 100644
index 00000000..db197a2b
--- /dev/null
+++ b/testdata/v2_minimal_valid_yaml/fogg.yml
@@ -0,0 +1,10 @@
+defaults:
+  backend:
+    bucket: bucket
+    profile: foo
+    region: region
+  owner: foo@example.com
+  project: foo
+  providers: {}
+  terraform_version: 1.1.1
+version: 2
diff --git a/testdata/v2_no_aws_provider_yaml/fogg.yml b/testdata/v2_no_aws_provider_yaml/fogg.yml
new file mode 100644
index 00000000..45d3d9f7
--- /dev/null
+++ b/testdata/v2_no_aws_provider_yaml/fogg.yml
@@ -0,0 +1,29 @@
+accounts:
+  bar: 
+  foo: 
+defaults:
+  backend:
+    bucket: buck
+    profile: profile
+    region: us-west-2
+  extra_vars:
+    foo: bar1
+  owner: foo@example.com
+  project: proj
+  providers: 
+  terraform_version: 0.100.0
+envs:
+  prod: 
+  staging:
+    components:
+      comp1: 
+      comp2: 
+      vpc:
+        extra_vars:
+          foo: bar3
+        module_source: github.com/terraform-aws-modules/terraform-aws-vpc?ref=v1.30.0
+    extra_vars:
+      foo: bar2
+modules:
+  my_module: 
+version: 2
diff --git a/util/testing.go b/util/testing.go
index 10e04ce9..b4dc715d 100644
--- a/util/testing.go
+++ b/util/testing.go
@@ -7,6 +7,7 @@ import (
 	"path/filepath"
 	"runtime"
 	"strconv"
+	"strings"
 
 	"github.com/spf13/afero"
 )
@@ -35,8 +36,13 @@ func ProjectRoot() string {
 
 func TestFile(name string) ([]byte, error) {
 	// calculate the path to repository's root
-	path := filepath.Join(ProjectRoot(), "testdata", name, "fogg.json")
-	return ioutil.ReadFile(path)
+	if strings.Contains(name, "_yaml") {
+		path := filepath.Join(ProjectRoot(), "testdata", name, "fogg.yml")
+		return ioutil.ReadFile(path)
+	} else {
+		path := filepath.Join(ProjectRoot(), "testdata", name, "fogg.json")
+		return ioutil.ReadFile(path)
+	}
 }
 
 func TestFs() (afero.Fs, string, error) {