Repository: diggerhq/digger
Pull request #363 (2023-06-13 11:09:30)

merge develop into the branch


Patch:
diff --git a/README.md b/README.md
index f99031f8..335ed9e0 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Digger runs terraform natively in your CI and takes care of the other bits - loc
 
 ## Roadmap
 
-Need a feature that's not listed? Book a [community feedback call](https://calendly.com/diggerdev/digger-community-feedback) - we ship fast ✅
+Need a feature that's not listed? Book a [community feedback call](https://calendly.com/diggerdev/community-feedback) - we ship fast ✅
 
 - ✅ GCP support. Store PR locks in GCP storage buckets. Shipped in [#50](https://github.com/diggerhq/digger/pull/50)
 - ✅ Workspaces support. Allow usage of Terraform CLI Workspaces. Shipped in [#72](https://github.com/diggerhq/digger/pull/72)
@@ -39,7 +39,7 @@ Need a feature that's not listed? Book a [community feedback call](https://calen
 
 This is demo flow with a sample repo using local state - for real world scenario you'll need to configure remote backend (S3 + DynamoDB) and add a [workflow file](https://github.com/diggerhq/digger_demo/blob/main/.github/workflows/plan.yml) to the root of the repo.
 
-1. Fork the [demo repository](https://github.com/diggerhq/digger_demo)
+1. Fork the [demo repository](https://github.com/diggerhq/digger_demo_multienv)
 2. Enable Actions (by default workflows won't trigger in a fork)
 
 <img width="1441" alt="Screenshot 2023-02-24 at 20 24 08" src="https://user-images.githubusercontent.com/1280498/221291130-6831d45a-008f-452f-91d3-37ba133d7cbb.png">
diff --git a/action.yml b/action.yml
index 2ec9c35b..f40f085c 100644
--- a/action.yml
+++ b/action.yml
@@ -71,7 +71,10 @@ inputs:
     description: Disable locking
     required: false
     default: 'false'
-
+  setup-tfenv:
+    description: Setup tfenv
+    required: false
+    default: 'false'
 outputs:
   output:
     value: ${{ steps.digger.outputs.output }}
@@ -142,6 +145,10 @@ runs:
         terraform_wrapper: false
       if: inputs.setup-terraform == 'true'
 
+    - name: Setup tfenv
+      uses: rhythmictech/actions-setup-tfenv@v0.0.2
+      if: inputs.setup-tfenv == 'true'
+
     - name: Setup Terragrunt
       uses: autero1/action-terragrunt@v1.3.2
       with:
diff --git a/go.mod b/go.mod
index 938c0878..67964ec2 100644
--- a/go.mod
+++ b/go.mod
@@ -6,12 +6,11 @@ require (
 	cloud.google.com/go/storage v1.30.1
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
 	github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1
-	github.com/aws/aws-sdk-go v1.44.277
+	github.com/aws/aws-sdk-go v1.44.279
 	github.com/bmatcuk/doublestar/v4 v4.6.0
 	github.com/caarlos0/env/v7 v7.1.0
 	github.com/google/go-github/v51 v51.0.0
 	github.com/google/uuid v1.3.0
-	github.com/jinzhu/copier v0.3.5
 	github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
 	github.com/stretchr/testify v1.8.4
 	github.com/xanzy/go-gitlab v0.84.0
diff --git a/go.sum b/go.sum
index 4416fd95..31390a25 100644
--- a/go.sum
+++ b/go.sum
@@ -25,6 +25,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwF
 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
 github.com/aws/aws-sdk-go v1.44.277 h1:YHmyzBPARTJ7LLYV1fxbfEbQOaUh3kh52hb7nBvX3BQ=
 github.com/aws/aws-sdk-go v1.44.277/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
+github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
 github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@@ -44,6 +46,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
 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=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -92,8 +95,6 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
 github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
 github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
 github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
-github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
-github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
diff --git a/pkg/azure/azure.go b/pkg/azure/azure.go
index 6f651c6f..aca686e1 100644
--- a/pkg/azure/azure.go
+++ b/pkg/azure/azure.go
@@ -340,7 +340,7 @@ func ProcessAzureReposEvent(azureEvent interface{}, diggerConfig *configuration.
 	return impactedProjects, nil, prNumber, nil
 }
 
-func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []configuration.Project, requestedProject *configuration.Project, workflows map[string]configuration.WorkflowConfig) ([]models.ProjectCommand, bool, error) {
+func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []configuration.Project, requestedProject *configuration.Project, workflows map[string]configuration.Workflow) ([]models.ProjectCommand, bool, error) {
 	commandsPerProject := make([]models.ProjectCommand, 0)
 	switch parseAzureContext.EventType {
 	case AzurePrCreated, AzurePrUpdated, AzurePrReopened:
@@ -350,15 +350,23 @@ func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []con
 				return nil, false, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 			}
 
-			stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+			stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+			var coreApplyStage models.Stage
+			if workflow.Apply != nil {
+				coreApplyStage = workflow.Apply.ToCoreStage()
+			}
+			var corePlanStage models.Stage
+			if workflow.Plan != nil {
+				corePlanStage = workflow.Plan.ToCoreStage()
+			}
 			commandsPerProject = append(commandsPerProject, models.ProjectCommand{
 				ProjectName:      project.Name,
 				ProjectDir:       project.Dir,
 				ProjectWorkspace: project.Workspace,
 				Terragrunt:       project.Terragrunt,
 				Commands:         workflow.Configuration.OnPullRequestPushed,
-				ApplyStage:       workflow.Apply,
-				PlanStage:        workflow.Plan,
+				ApplyStage:       &coreApplyStage,
+				PlanStage:        &corePlanStage,
 				CommandEnvVars:   commandEnvVars,
 				StateEnvVars:     stateEnvVars,
 			})
@@ -371,15 +379,23 @@ func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []con
 				return nil, false, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 			}
 
-			stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+			stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+			var coreApplyStage models.Stage
+			if workflow.Apply != nil {
+				coreApplyStage = workflow.Apply.ToCoreStage()
+			}
+			var corePlanStage models.Stage
+			if workflow.Plan != nil {
+				corePlanStage = workflow.Plan.ToCoreStage()
+			}
 			commandsPerProject = append(commandsPerProject, models.ProjectCommand{
 				ProjectName:      project.Name,
 				ProjectDir:       project.Dir,
 				ProjectWorkspace: project.Workspace,
 				Terragrunt:       project.Terragrunt,
 				Commands:         workflow.Configuration.OnPullRequestClosed,
-				ApplyStage:       workflow.Apply,
-				PlanStage:        workflow.Plan,
+				ApplyStage:       &coreApplyStage,
+				PlanStage:        &corePlanStage,
 				CommandEnvVars:   commandEnvVars,
 				StateEnvVars:     stateEnvVars,
 			})
@@ -392,15 +408,23 @@ func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []con
 				if !ok {
 					return nil, false, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 				}
-				stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+				stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+				var coreApplyStage models.Stage
+				if workflow.Apply != nil {
+					coreApplyStage = workflow.Apply.ToCoreStage()
+				}
+				var corePlanStage models.Stage
+				if workflow.Plan != nil {
+					corePlanStage = workflow.Plan.ToCoreStage()
+				}
 				commandsPerProject = append(commandsPerProject, models.ProjectCommand{
 					ProjectName:      project.Name,
 					ProjectDir:       project.Dir,
 					ProjectWorkspace: project.Workspace,
 					Terragrunt:       project.Terragrunt,
 					Commands:         workflow.Configuration.OnCommitToDefault,
-					ApplyStage:       workflow.Apply,
-					PlanStage:        workflow.Plan,
+					ApplyStage:       &coreApplyStage,
+					PlanStage:        &corePlanStage,
 					CommandEnvVars:   commandEnvVars,
 					StateEnvVars:     stateEnvVars,
 				})
@@ -441,7 +465,7 @@ func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []con
 					if !ok {
 						return nil, false, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 					}
-					stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+					stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
 
 					commandsPerProject = append(commandsPerProject, models.ProjectCommand{
 						ProjectName:      project.Name,
diff --git a/pkg/configuration/config.go b/pkg/configuration/config.go
deleted file mode 100644
index e29d1ad9..00000000
--- a/pkg/configuration/config.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package configuration
-
-import "digger/pkg/core/models"
-
-type DiggerConfig struct {
-	Projects         []Project
-	AutoMerge        bool
-	CollectUsageData bool
-	Workflows        map[string]WorkflowConfig
-}
-
-type Project struct {
-	Name            string
-	Dir             string
-	Workspace       string
-	Terragrunt      bool
-	Workflow        string
-	IncludePatterns []string
-	ExcludePatterns []string
-}
-
-type WorkflowConfig struct {
-	EnvVars       *TerraformEnvConfig
-	Plan          *models.Stage
-	Apply         *models.Stage
-	Configuration *WorkflowConfiguration
-}
-
-type WorkflowConfiguration struct {
-	OnPullRequestPushed []string
-	OnPullRequestClosed []string
-	OnCommitToDefault   []string
-}
-
-type TerraformEnvConfig struct {
-	State    []EnvVar
-	Commands []EnvVar
-}
-
-type EnvVar struct {
-	Name      string
-	ValueFrom string
-	Value     string
-}
-
-func defaultWorkflow() *WorkflowConfig {
-	return &WorkflowConfig{
-		Configuration: &WorkflowConfiguration{
-			OnCommitToDefault:   []string{"digger unlock"},
-			OnPullRequestPushed: []string{"digger plan"},
-			OnPullRequestClosed: []string{"digger unlock"},
-		},
-		Plan: &models.Stage{
-			Steps: []models.Step{
-				{
-					Action: "init", ExtraArgs: []string{},
-				},
-				{
-					Action: "plan", ExtraArgs: []string{},
-				},
-			},
-		},
-		Apply: &models.Stage{
-			Steps: []models.Step{
-				{
-					Action: "init", ExtraArgs: []string{},
-				},
-				{
-					Action: "apply", ExtraArgs: []string{},
-				},
-			},
-		},
-		EnvVars: &TerraformEnvConfig{},
-	}
-}
diff --git a/pkg/configuration/converters.go b/pkg/configuration/converters.go
deleted file mode 100644
index bab51c26..00000000
--- a/pkg/configuration/converters.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package configuration
-
-import (
-	"digger/pkg/core/models"
-	"digger/pkg/utils"
-	"fmt"
-	"github.com/jinzhu/copier"
-	"path/filepath"
-)
-
-func copyProjects(projects []*ProjectYaml) []Project {
-	result := make([]Project, len(projects))
-	for i, p := range projects {
-		item := Project{p.Name,
-			p.Dir,
-			p.Workspace,
-			p.Terragrunt,
-			p.Workflow,
-			p.IncludePatterns,
-			p.ExcludePatterns,
-		}
-		result[i] = item
-	}
-	return result
-}
-
-func copyTerraformEnvConfig(terraformEnvConfig *TerraformEnvConfigYaml) *TerraformEnvConfig {
-	if terraformEnvConfig == nil {
-		return &TerraformEnvConfig{}
-	}
-	result := TerraformEnvConfig{}
-	result.State = make([]EnvVar, len(terraformEnvConfig.State))
-	result.Commands = make([]EnvVar, len(terraformEnvConfig.Commands))
-
-	for i, s := range terraformEnvConfig.State {
-		item := EnvVar{
-			s.Name,
-			s.ValueFrom,
-			s.Value,
-		}
-		result.State[i] = item
-	}
-	for i, s := range terraformEnvConfig.Commands {
-		item := EnvVar{
-			s.Name,
-			s.ValueFrom,
-			s.Value,
-		}
-		result.Commands[i] = item
-	}
-
-	return &result
-}
-
-func copyStage(stage *StageYaml) *models.Stage {
-	result := models.Stage{}
-	result.Steps = make([]models.Step, len(stage.Steps))
-
-	for i, s := range stage.Steps {
-		item := models.Step{
-			Action:    s.Action,
-			Value:     s.Value,
-			ExtraArgs: s.ExtraArgs,
-			Shell:     s.Shell,
-		}
-		result.Steps[i] = item
-	}
-	return &result
-}
-
-func copyWorkflowConfiguration(config *WorkflowConfigurationYaml) *WorkflowConfiguration {
-	result := WorkflowConfiguration{}
-	result.OnPullRequestClosed = make([]string, len(config.OnPullRequestClosed))
-	result.OnPullRequestPushed = make([]string, len(config.OnPullRequestPushed))
-	result.OnCommitToDefault = make([]string, len(config.OnCommitToDefault))
-
-	result.OnPullRequestClosed = config.OnPullRequestClosed
-	result.OnPullRequestPushed = config.OnPullRequestPushed
-	result.OnCommitToDefault = config.OnCommitToDefault
-	return &result
-}
-
-func copyWorkflows(workflows map[string]*WorkflowYaml) map[string]WorkflowConfig {
-	result := make(map[string]WorkflowConfig, len(workflows))
-	for i, w := range workflows {
-		envVars := copyTerraformEnvConfig(w.EnvVars)
-		plan := copyStage(w.Plan)
-		apply := copyStage(w.Apply)
-		configuration := copyWorkflowConfiguration(w.Configuration)
-		item := WorkflowConfig{
-			envVars,
-			plan,
-			apply,
-			configuration,
-		}
-		copier.Copy(&w, &item)
-		result[i] = item
-	}
-	return result
-}
-
-func ConvertDiggerYamlToConfig(diggerYaml *DiggerConfigYaml, workingDir string, walker DirWalker) (*DiggerConfig, error) {
-	var diggerConfig DiggerConfig
-	const defaultWorkflowName = "default"
-
-	if diggerYaml.AutoMerge != nil {
-		diggerConfig.AutoMerge = *diggerYaml.AutoMerge
-	} else {
-		diggerConfig.AutoMerge = false
-	}
-
-	if diggerYaml.CollectUsageData != nil {
-		diggerConfig.CollectUsageData = *diggerYaml.CollectUsageData
-	} else {
-		diggerConfig.CollectUsageData = true
-	}
-
-	// if workflow block is not specified in yaml we create a default one, and add it to every project
-	if diggerYaml.Workflows != nil {
-		workflows := copyWorkflows(diggerYaml.Workflows)
-		diggerConfig.Workflows = workflows
-
-		// provide default workflow if not specified
-		if _, ok := diggerConfig.Workflows[defaultWorkflowName]; !ok {
-			workflow := *defaultWorkflow()
-			diggerConfig.Workflows[defaultWorkflowName] = workflow
-		}
-	} else {
-		workflow := *defaultWorkflow()
-		diggerConfig.Workflows = make(map[string]WorkflowConfig)
-		diggerConfig.Workflows[defaultWorkflowName] = workflow
-	}
-
-	projects := copyProjects(diggerYaml.Projects)
-	diggerConfig.Projects = projects
-
-	// update project's workflow if needed
-	for _, project := range diggerConfig.Projects {
-		if project.Workflow == "" {
-			project.Workflow = defaultWorkflowName
-		}
-	}
-
-	// check for project name duplicates
-	projectNames := make(map[string]bool)
-	for _, project := range diggerConfig.Projects {
-		if projectNames[project.Name] {
-			return nil, fmt.Errorf("project name '%s' is duplicated", project.Name)
-		}
-		projectNames[project.Name] = true
-	}
-
-	if diggerYaml.GenerateProjectsConfig != nil {
-		dirs, err := walker.GetDirs(workingDir)
-		if err != nil {
-			return nil, err
-		}
-
-		for _, dir := range dirs {
-			includePattern := diggerYaml.GenerateProjectsConfig.Include
-			excludePattern := diggerYaml.GenerateProjectsConfig.Exclude
-			if utils.MatchIncludeExcludePatternsToFile(dir, []string{includePattern}, []string{excludePattern}) {
-				project := Project{Name: filepath.Base(dir), Dir: dir, Workflow: defaultWorkflowName, Workspace: "default"}
-				diggerConfig.Projects = append(diggerConfig.Projects, project)
-			}
-		}
-	}
-	return &diggerConfig, nil
-}
diff --git a/pkg/configuration/digger_config.go b/pkg/configuration/digger_config.go
index a5361a00..f6a4d103 100644
--- a/pkg/configuration/digger_config.go
+++ b/pkg/configuration/digger_config.go
@@ -1,6 +1,7 @@
 package configuration
 
 import (
+	"digger/pkg/core/models"
 	"digger/pkg/utils"
 	"errors"
 	"fmt"
@@ -11,29 +12,77 @@ import (
 	"regexp"
 )
 
-type DirWalker interface {
-	GetDirs(workingDir string) ([]string, error)
+type WorkflowConfiguration struct {
+	OnPullRequestPushed []string `yaml:"on_pull_request_pushed"`
+	OnPullRequestClosed []string `yaml:"on_pull_request_closed"`
+	OnCommitToDefault   []string `yaml:"on_commit_to_default"`
 }
 
-type FileSystemDirWalker struct {
+type DiggerConfigYaml struct {
+	Projects               []Project               `yaml:"projects"`
+	AutoMerge              bool                    `yaml:"auto_merge"`
+	Workflows              map[string]Workflow     `yaml:"workflows"`
+	CollectUsageData       *bool                   `yaml:"collect_usage_data,omitempty"`
+	GenerateProjectsConfig *GenerateProjectsConfig `yaml:"generate_projects"`
 }
 
-func GetFilesWithExtension(workingDir string, ext string) ([]string, error) {
-	var files []string
-	listOfFiles, err := os.ReadDir(workingDir)
-	if err != nil {
-		return nil, errors.New(fmt.Sprintf("error reading directory %s: %v", workingDir, err))
-	}
-	for _, f := range listOfFiles {
-		if !f.IsDir() {
-			r, err := regexp.MatchString(ext, f.Name())
-			if err == nil && r {
-				files = append(files, f.Name())
-			}
-		}
+type EnvVarConfig struct {
+	Name      string `yaml:"name"`
+	ValueFrom string `yaml:"value_from"`
+	Value     string `yaml:"value"`
+}
+
+type DiggerConfig struct {
+	Projects         []Project
+	AutoMerge        bool
+	CollectUsageData bool
+	Workflows        map[string]Workflow
+}
+
+type GenerateProjectsConfig struct {
+	Include string `yaml:"include"`
+	Exclude string `yaml:"exclude"`
+}
+
+type Project struct {
+	Name            string   `yaml:"name"`
+	Dir             string   `yaml:"dir"`
+	Workspace       string   `yaml:"workspace"`
+	Terragrunt      bool     `yaml:"terragrunt"`
+	Workflow        string   `yaml:"workflow"`
+	IncludePatterns []string `yaml:"include_patterns,omitempty"`
+	ExcludePatterns []string `yaml:"exclude_patterns,omitempty"`
+}
+
+type Stage struct {
+	Steps []Step `yaml:"steps"`
+}
+
+func (s *Stage) ToCoreStage() models.Stage {
+	var steps []models.Step
+	for _, step := range s.Steps {
+		steps = append(steps, step.ToCoreStep())
 	}
+	return models.Stage{Steps: steps}
+}
 
-	return files, nil
+type Workflow struct {
+	EnvVars       EnvVars                `yaml:"env_vars"`
+	Plan          *Stage                 `yaml:"plan,omitempty"`
+	Apply         *Stage                 `yaml:"apply,omitempty"`
+	Configuration *WorkflowConfiguration `yaml:"workflow_configuration"`
+}
+
+type EnvVars struct {
+	State    []EnvVarConfig `yaml:"state"`
+	Commands []EnvVarConfig `yaml:"commands"`
+}
+
+type DirWalker interface {
+	GetDirs(workingDir string) ([]string, error)
+}
+
+type FileSystemDirWalker struct {
 }
 
 func GetFilesWithExtension(workingDir string, ext string) ([]string, error) {
@@ -79,10 +128,207 @@ func (walker *FileSystemDirWalker) GetDirs(workingDir string) ([]string, error)
 
 var ErrDiggerConfigConflict = errors.New("more than one digger config file detected, please keep either 'digger.yml' or 'digger.yaml'")
 
+func (p *Project) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type rawProject Project
+	raw := rawProject{
+		Workspace:  "default",
+		Terragrunt: false,
+		Workflow:   "default",
+	}
+	if err := unmarshal(&raw); err != nil {
+		return err
+	}
+	*p = Project(raw)
+	return nil
+}
+
+func (w *Workflow) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type rawWorkflow Workflow
+	raw := rawWorkflow{
+		Configuration: &WorkflowConfiguration{
+			OnCommitToDefault:   []string{"digger unlock"},
+			OnPullRequestPushed: []string{"digger plan"},
+			OnPullRequestClosed: []string{"digger unlock"},
+		},
+		Plan: &Stage{
+			Steps: []Step{
+				{
+					Action: "init", ExtraArgs: []string{},
+				},
+				{
+					Action: "plan", ExtraArgs: []string{},
+				},
+			},
+		},
+		Apply: &Stage{
+			Steps: []Step{
+				{
+					Action: "init", ExtraArgs: []string{},
+				},
+				{
+					Action: "apply", ExtraArgs: []string{},
+				},
+			},
+		},
+		EnvVars: EnvVars{
+			State:    []EnvVarConfig{},
+			Commands: []EnvVarConfig{},
+		},
+	}
+	if err := unmarshal(&raw); err != nil {
+		return err
+	}
+	*w = Workflow(raw)
+	return nil
+}
+
+type Step struct {
+	Action    string
+	Value     string
+	ExtraArgs []string `yaml:"extra_args,omitempty"`
+	Shell     string
+}
+
+func (s *Step) ToCoreStep() models.Step {
+	return models.Step{
+		Action:    s.Action,
+		Value:     s.Value,
+		ExtraArgs: s.ExtraArgs,
+		Shell:     s.Shell,
+	}
+}
+
+func (s *Step) UnmarshalYAML(value *yaml.Node) error {
+	if value.Kind == yaml.ScalarNode {
+		return value.Decode(&s.Action)
+	}
+
+	var stepMap map[string]interface{}
+	if err := value.Decode(&stepMap); err != nil {
+		return err
+	}
+
+	if _, ok := stepMap["run"]; ok {
+		s.Action = "run"
+		s.Value = stepMap["run"].(string)
+		if _, ok := stepMap["shell"]; ok {
+			s.Shell = stepMap["shell"].(string)
+		}
+		return nil
+	}
+
+	s.extract(stepMap, "plan")
+	s.extract(stepMap, "apply")
+
+	return nil
+}
+
+func (s *Step) extract(stepMap map[string]interface{}, action string) {
+	if _, ok := stepMap[action]; ok {
+		s.Action = action
+		var extraArgs []string
+		if v, ok := stepMap["extra_args"]; ok {
+			for _, v := range v.([]interface{}) {
+				extraArgs = append(extraArgs, v.(string))
+			}
+			s.ExtraArgs = extraArgs
+		}
+	}
+}
+
+func defaultWorkflow() *Workflow {
+	return &Workflow{
+		Configuration: &WorkflowConfiguration{
+			OnCommitToDefault:   []string{"digger unlock"},
+			OnPullRequestPushed: []string{"digger plan"},
+			OnPullRequestClosed: []string{"digger unlock"},
+		},
+		Plan: &Stage{
+			Steps: []Step{
+				{
+					Action: "init", ExtraArgs: []string{},
+				},
+				{
+					Action: "plan", ExtraArgs: []string{},
+				},
+			},
+		},
+		Apply: &Stage{
+			Steps: []Step{
+				{
+					Action: "init", ExtraArgs: []string{},
+				},
+				{
+					Action: "apply", ExtraArgs: []string{},
+				},
+			},
+		},
+	}
+}
+
+func ConvertDiggerYamlToConfig(diggerYaml *DiggerConfigYaml, workingDir string, walker DirWalker) (*DiggerConfig, error) {
+	var diggerConfig DiggerConfig
+	const defaultWorkflowName = "default"
+
+	diggerConfig.AutoMerge = diggerYaml.AutoMerge
+
+	// if workflow block is not specified in yaml we create a default one, and add it to every project
+	if diggerYaml.Workflows != nil {
+		diggerConfig.Workflows = diggerYaml.Workflows
+		// provide default workflow if not specified
+		if _, ok := diggerConfig.Workflows[defaultWorkflowName]; !ok {
+			workflow := *defaultWorkflow()
+			diggerConfig.Workflows[defaultWorkflowName] = workflow
+		}
+	} else {
+		workflow := *defaultWorkflow()
+		diggerConfig.Workflows = make(map[string]Workflow)
+		diggerConfig.Workflows[defaultWorkflowName] = workflow
+	}
+
+	diggerConfig.Projects = diggerYaml.Projects
+
+	// update project's workflow if needed
+	for _, project := range diggerConfig.Projects {
+		if project.Workflow == "" {
+			project.Workflow = defaultWorkflowName
+		}
+	}
+
+	// check for project name duplicates
+	projectNames := make(map[string]bool)
+	for _, project := range diggerConfig.Projects {
+		if projectNames[project.Name] {
+			return nil, fmt.Errorf("project name '%s' is duplicated", project.Name)
+		}
+		projectNames[project.Name] = true
+	}
+	if diggerYaml.CollectUsageData != nil {
+		diggerConfig.CollectUsageData = *diggerYaml.CollectUsageData
+	} else {
+		diggerConfig.CollectUsageData = true
+	}
+
+	if diggerYaml.GenerateProjectsConfig != nil {
+		dirs, err := walker.GetDirs(workingDir)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, dir := range dirs {
+			includePattern := diggerYaml.GenerateProjectsConfig.Include
+			excludePattern := diggerYaml.GenerateProjectsConfig.Exclude
+			if utils.MatchIncludeExcludePatternsToFile(dir, []string{includePattern}, []string{excludePattern}) {
+				project := Project{Name: filepath.Base(dir), Dir: dir, Workflow: defaultWorkflowName, Workspace: "default"}
+				diggerConfig.Projects = append(diggerConfig.Projects, project)
+			}
+		}
+	}
+	return &diggerConfig, nil
+}
 
 func LoadDiggerConfig(workingDir string, walker DirWalker) (*DiggerConfig, error) {
-	configYaml := &DiggerConfigYaml{}
-	config := &DiggerConfig{}
+	config := &DiggerConfigYaml{}
 	fileName, err := retrieveConfigFile(workingDir)
 	if err != nil {
 		if errors.Is(err, ErrDiggerConfigConflict) {
@@ -94,9 +340,37 @@ func LoadDiggerConfig(workingDir string, walker DirWalker) (*DiggerConfig, error
 		fmt.Println("No digger config found, using default one")
 		config.Projects = make([]Project, 1)
 		config.Projects[0] = defaultProject()
-		config.Workflows = make(map[string]WorkflowConfig)
-		config.Workflows["default"] = *defaultWorkflow()
-		return config, nil
+		config.Workflows = make(map[string]Workflow)
+		config.Workflows["default"] = Workflow{
+			Plan: &Stage{
+				Steps: []Step{{
+					Action:    "init",
+					ExtraArgs: []string{},
+				}, {
+					Action:    "plan",
+					ExtraArgs: []string{},
+				}},
+			},
+			Apply: &Stage{
+				Steps: []Step{{
+					Action:    "init",
+					ExtraArgs: []string{},
+				}, {
+					Action:    "apply",
+					ExtraArgs: []string{},
+				}},
+			},
+			Configuration: &WorkflowConfiguration{
+				OnPullRequestPushed: []string{"digger plan"},
+				OnPullRequestClosed: []string{"digger unlock"},
+				OnCommitToDefault:   []string{"digger apply"},
+			},
+		}
+		c, err := ConvertDiggerYamlToConfig(config, workingDir, walker)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read config file %s: %v", fileName, err)
+		}
+		return c, nil
 	}
 
 	data, err := os.ReadFile(fileName)
@@ -104,15 +378,15 @@ func LoadDiggerConfig(workingDir string, walker DirWalker) (*DiggerConfig, error
 		return nil, fmt.Errorf("failed to read config file %s: %v", fileName, err)
 	}
 
-	if err := yaml.Unmarshal(data, configYaml); err != nil {
+	if err := yaml.Unmarshal(data, config); err != nil {
 		return nil, fmt.Errorf("error parsing '%s': %v", fileName, err)
 	}
 
-	if (configYaml.Projects == nil || len(configYaml.Projects) == 0) && configYaml.GenerateProjectsConfig == nil {
+	if (config.Projects == nil || len(config.Projects) == 0) && config.GenerateProjectsConfig == nil {
 		return nil, fmt.Errorf("no projects configuration found in '%s'", fileName)
 	}
 
-	c, err := ConvertDiggerYamlToConfig(configYaml, workingDir, walker)
+	c, err := ConvertDiggerYamlToConfig(config, workingDir, walker)
 	if err != nil {
 		return nil, err
 	}
@@ -183,7 +457,7 @@ func (c *DiggerConfig) GetDirectory(projectName string) string {
 	return project.Dir
 }
 
-func (c *DiggerConfig) GetWorkflow(workflowName string) *WorkflowConfig {
+func (c *DiggerConfig) GetWorkflow(workflowName string) *Workflow {
 	workflows := c.Workflows
 
 	workflow, ok := workflows[workflowName]
@@ -234,27 +508,25 @@ func retrieveConfigFile(workingDir string) (string, error) {
 	return "", nil
 }
 
-func CollectTerraformEnvConfig(envs *TerraformEnvConfig) (map[string]string, map[string]string) {
+func CollectEnvVars(envs EnvVars) (map[string]string, map[string]string) {
 	stateEnvVars := map[string]string{}
-	commandEnvVars := map[string]string{}
 
-	if envs != nil {
-		for _, envvar := range envs.State {
-			if envvar.Value != "" {
-				stateEnvVars[envvar.Name] = envvar.Value
-			} else if envvar.ValueFrom != "" {
-				stateEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
-			}
+	for _, envvar := range envs.State {
+		if envvar.Value != "" {
+			stateEnvVars[envvar.Name] = envvar.Value
+		} else if envvar.ValueFrom != "" {
+			stateEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
 		}
+	}
 
-		for _, envvar := range envs.Commands {
-			if envvar.Value != "" {
-				commandEnvVars[envvar.Name] = envvar.Value
-			} else if envvar.ValueFrom != "" {
-				commandEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
-			}
+	commandEnvVars := map[string]string{}
+
+	for _, envvar := range envs.Commands {
+		if envvar.Value != "" {
+			commandEnvVars[envvar.Name] = envvar.Value
+		} else if envvar.ValueFrom != "" {
+			commandEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
 		}
 	}
-
 	return stateEnvVars, commandEnvVars
 }
diff --git a/pkg/configuration/yaml.go b/pkg/configuration/yaml.go
deleted file mode 100644
index 415d5277..00000000
--- a/pkg/configuration/yaml.go
+++ /dev/null
@@ -1,174 +0,0 @@
-package configuration
-
-import (
-	"digger/pkg/core/models"
-	"gopkg.in/yaml.v3"
-)
-
-type DiggerConfigYaml struct {
-	Projects               []*ProjectYaml              `yaml:"projects"`
-	AutoMerge              *bool                       `yaml:"auto_merge"`
-	Workflows              map[string]*WorkflowYaml    `yaml:"workflows"`
-	CollectUsageData       *bool                       `yaml:"collect_usage_data,omitempty"`
-	GenerateProjectsConfig *GenerateProjectsConfigYaml `yaml:"generate_projects"`
-}
-
-type ProjectYaml struct {
-	Name            string   `yaml:"name"`
-	Dir             string   `yaml:"dir"`
-	Workspace       string   `yaml:"workspace"`
-	Terragrunt      bool     `yaml:"terragrunt"`
-	Workflow        string   `yaml:"workflow"`
-	IncludePatterns []string `yaml:"include_patterns,omitempty"`
-	ExcludePatterns []string `yaml:"exclude_patterns,omitempty"`
-}
-
-type WorkflowYaml struct {
-	EnvVars       *TerraformEnvConfigYaml    `yaml:"env_vars"`
-	Plan          *StageYaml                 `yaml:"plan,omitempty"`
-	Apply         *StageYaml                 `yaml:"apply,omitempty"`
-	Configuration *WorkflowConfigurationYaml `yaml:"workflow_configuration"`
-}
-
-type WorkflowConfigurationYaml struct {
-	OnPullRequestPushed []string `yaml:"on_pull_request_pushed"`
-	OnPullRequestClosed []string `yaml:"on_pull_request_closed"`
-	OnCommitToDefault   []string `yaml:"on_commit_to_default"`
-}
-
-func (s *StageYaml) ToCoreStage() models.Stage {
-	var steps []models.Step
-	for _, step := range s.Steps {
-		steps = append(steps, step.ToCoreStep())
-	}
-	return models.Stage{Steps: steps}
-}
-
-type StageYaml struct {
-	Steps []StepYaml `yaml:"steps"`
-}
-
-type StepYaml struct {
-	Action    string
-	Value     string
-	ExtraArgs []string `yaml:"extra_args,omitempty"`
-	Shell     string
-}
-
-type TerraformEnvConfigYaml struct {
-	State    []EnvVarYaml `yaml:"state"`
-	Commands []EnvVarYaml `yaml:"commands"`
-}
-
-type EnvVarYaml struct {
-	Name      string `yaml:"name"`
-	ValueFrom string `yaml:"value_from"`
-	Value     string `yaml:"value"`
-}
-
-type GenerateProjectsConfigYaml struct {
-	Include string `yaml:"include"`
-	Exclude string `yaml:"exclude"`
-}
-
-func (p *ProjectYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	type rawProject ProjectYaml
-	raw := rawProject{
-		Workspace:  "default",
-		Terragrunt: false,
-		Workflow:   "default",
-	}
-	if err := unmarshal(&raw); err != nil {
-		return err
-	}
-	*p = ProjectYaml(raw)
-	return nil
-}
-
-func (w *WorkflowYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	type rawWorkflow WorkflowYaml
-	raw := rawWorkflow{
-		Configuration: &WorkflowConfigurationYaml{
-			OnCommitToDefault:   []string{"digger unlock"},
-			OnPullRequestPushed: []string{"digger plan"},
-			OnPullRequestClosed: []string{"digger unlock"},
-		},
-		Plan: &StageYaml{
-			Steps: []StepYaml{
-				{
-					Action: "init", ExtraArgs: []string{},
-				},
-				{
-					Action: "plan", ExtraArgs: []string{},
-				},
-			},
-		},
-		Apply: &StageYaml{
-			Steps: []StepYaml{
-				{
-					Action: "init", ExtraArgs: []string{},
-				},
-				{
-					Action: "apply", ExtraArgs: []string{},
-				},
-			},
-		},
-		EnvVars: &TerraformEnvConfigYaml{
-			State:    []EnvVarYaml{},
-			Commands: []EnvVarYaml{},
-		},
-	}
-	if err := unmarshal(&raw); err != nil {
-		return err
-	}
-	*w = WorkflowYaml(raw)
-	return nil
-}
-
-func (s *StepYaml) UnmarshalYAML(value *yaml.Node) error {
-
-	if value.Kind == yaml.ScalarNode {
-		return value.Decode(&s.Action)
-	}
-
-	var stepMap map[string]interface{}
-	if err := value.Decode(&stepMap); err != nil {
-		return err
-	}
-
-	if _, ok := stepMap["run"]; ok {
-		s.Action = "run"
-		s.Value = stepMap["run"].(string)
-		if _, ok := stepMap["shell"]; ok {
-			s.Shell = stepMap["shell"].(string)
-		}
-		return nil
-	}
-
-	s.extract(stepMap, "plan")
-	s.extract(stepMap, "apply")
-
-	return nil
-}
-
-func (s *StepYaml) ToCoreStep() models.Step {
-	return models.Step{
-		Action:    s.Action,
-		Value:     s.Value,
-		ExtraArgs: s.ExtraArgs,
-		Shell:     s.Shell,
-	}
-}
-
-func (s *StepYaml) extract(stepMap map[string]interface{}, action string) {
-	if _, ok := stepMap[action]; ok {
-		s.Action = action
-		var extraArgs []string
-		if v, ok := stepMap["extra_args"]; ok {
-			for _, v := range v.([]interface{}) {
-				extraArgs = append(extraArgs, v.(string))
-			}
-			s.ExtraArgs = extraArgs
-		}
-	}
-}
diff --git a/pkg/core/terraform/tf.go b/pkg/core/terraform/tf.go
index 90176302..0363ca21 100644
--- a/pkg/core/terraform/tf.go
+++ b/pkg/core/terraform/tf.go
@@ -47,8 +47,8 @@ func (terragrunt Terragrunt) Plan(params []string, envs map[string]string) (bool
 func (terragrunt Terragrunt) runTerragruntCommand(command string, envs map[string]string, arg ...string) (string, string, error) {
 	args := []string{command}
 	args = append(args, arg...)
-	args = append(args, "--terragrunt-working-dir", terragrunt.WorkingDir)
 	cmd := exec.Command("terragrunt", args...)
+	cmd.Dir = terragrunt.WorkingDir
 
 	env := os.Environ()
 	env = append(env, "TF_CLI_ARGS=-no-color")
@@ -104,8 +104,7 @@ func (tf Terraform) Apply(params []string, plan *string, envs map[string]string)
 }
 
 func (tf Terraform) runTerraformCommand(command string, envs map[string]string, arg ...string) (string, string, int, error) {
-	args := []string{"-chdir=" + tf.WorkingDir}
-	args = append(args, command)
+	args := []string{command}
 	args = append(args, arg...)
 
 	var stdout, stderr bytes.Buffer
@@ -113,6 +112,7 @@ func (tf Terraform) runTerraformCommand(command string, envs map[string]string,
 	mwerr := io.MultiWriter(os.Stderr, &stderr)
 
 	cmd := exec.Command("terraform", args...)
+	cmd.Dir = tf.WorkingDir
 
 	env := os.Environ()
 	for k, v := range envs {
diff --git a/pkg/github/github.go b/pkg/github/github.go
index c0c64e9f..0aeac2aa 100644
--- a/pkg/github/github.go
+++ b/pkg/github/github.go
@@ -132,7 +132,7 @@ func GetGitHubContext(ghContext string) (*models.Github, error) {
 	return parsedGhContext, nil
 }
 
-func ConvertGithubEventToCommands(event models.Event, impactedProjects []configuration.Project, requestedProject *configuration.Project, workflows map[string]configuration.WorkflowConfig) ([]dg_models.ProjectCommand, bool, error) {
+func ConvertGithubEventToCommands(event models.Event, impactedProjects []configuration.Project, requestedProject *configuration.Project, workflows map[string]configuration.Workflow) ([]dg_models.ProjectCommand, bool, error) {
 	commandsPerProject := make([]dg_models.ProjectCommand, 0)
 
 	switch event.(type) {
@@ -144,8 +144,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 				return nil, false, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 			}
 
-			stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
-
+			stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+			coreApplyStage := workflow.Apply.ToCoreStage()
+			corePlanStage := workflow.Plan.ToCoreStage()
 			if event.Action == "closed" && event.PullRequest.Merged && event.PullRequest.Base.Ref == event.Repository.DefaultBranch {
 				commandsPerProject = append(commandsPerProject, dg_models.ProjectCommand{
 					ProjectName:      project.Name,
@@ -153,8 +154,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					ProjectWorkspace: project.Workspace,
 					Terragrunt:       project.Terragrunt,
 					Commands:         workflow.Configuration.OnCommitToDefault,
-					ApplyStage:       workflow.Apply,
-					PlanStage:        workflow.Plan,
+					ApplyStage:       &coreApplyStage,
+					PlanStage:        &corePlanStage,
 					CommandEnvVars:   commandEnvVars,
 					StateEnvVars:     stateEnvVars,
 				})
@@ -165,8 +166,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					ProjectWorkspace: project.Workspace,
 					Terragrunt:       project.Terragrunt,
 					Commands:         workflow.Configuration.OnPullRequestPushed,
-					ApplyStage:       workflow.Apply,
-					PlanStage:        workflow.Plan,
+					ApplyStage:       &coreApplyStage,
+					PlanStage:        &corePlanStage,
 					CommandEnvVars:   commandEnvVars,
 					StateEnvVars:     stateEnvVars,
 				})
@@ -177,8 +178,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					ProjectWorkspace: project.Workspace,
 					Terragrunt:       project.Terragrunt,
 					Commands:         workflow.Configuration.OnPullRequestClosed,
-					ApplyStage:       workflow.Apply,
-					PlanStage:        workflow.Plan,
+					ApplyStage:       &coreApplyStage,
+					PlanStage:        &corePlanStage,
 					CommandEnvVars:   commandEnvVars,
 					StateEnvVars:     stateEnvVars,
 				})
@@ -210,8 +211,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 						return nil, false, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 					}
 
-					stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
-
+					stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+					coreApplyStage := workflow.Apply.ToCoreStage()
+					corePlanStage := workflow.Plan.ToCoreStage()
 					workspace := project.Workspace
 					workspaceOverride, err := utils.ParseWorkspace(event.Comment.Body)
 					if err != nil {
@@ -226,8 +228,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 						ProjectWorkspace: workspace,
 						Terragrunt:       project.Terragrunt,
 						Commands:         []string{command},
-						ApplyStage:       workflow.Apply,
-						PlanStage:        workflow.Plan,
+						ApplyStage:       &coreApplyStage,
+						PlanStage:        &corePlanStage,
 						CommandEnvVars:   commandEnvVars,
 						StateEnvVars:     stateEnvVars,
 					})
diff --git a/pkg/gitlab/gitlab.go b/pkg/gitlab/gitlab.go
index bd8f95ef..1bdedb26 100644
--- a/pkg/gitlab/gitlab.go
+++ b/pkg/gitlab/gitlab.go
@@ -249,7 +249,7 @@ const (
 	MergeRequestComment = GitLabEventType("merge_request_commented")
 )
 
-func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContext, impactedProjects []configuration.Project, requestedProject *configuration.Project, workflows map[string]configuration.WorkflowConfig) ([]models.ProjectCommand, bool, error) {
+func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContext, impactedProjects []configuration.Project, requestedProject *configuration.Project, workflows map[string]configuration.Workflow) ([]models.ProjectCommand, bool, error) {
 	commandsPerProject := make([]models.ProjectCommand, 0)
 
 	fmt.Printf("ConvertGitLabEventToCommands, event.EventType: %s\n", event.EventType)
@@ -261,15 +261,17 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex
 				return nil, true, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 			}
 
-			stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+			stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+			coreApplyStage := workflow.Apply.ToCoreStage()
+			corePlanStage := workflow.Plan.ToCoreStage()
 			commandsPerProject = append(commandsPerProject, models.ProjectCommand{
 				ProjectName:      project.Name,
 				ProjectDir:       project.Dir,
 				ProjectWorkspace: project.Workspace,
 				Terragrunt:       project.Terragrunt,
 				Commands:         workflow.Configuration.OnPullRequestPushed,
-				ApplyStage:       workflow.Apply,
-				PlanStage:        workflow.Plan,
+				ApplyStage:       &coreApplyStage,
+				PlanStage:        &corePlanStage,
 				CommandEnvVars:   commandEnvVars,
 				StateEnvVars:     stateEnvVars,
 			})
@@ -281,15 +283,23 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex
 			if !ok {
 				return nil, true, fmt.Errorf("failed to find workflow config '%s' for project '%s'", project.Workflow, project.Name)
 			}
-			stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+			stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+			var coreApplyStage models.Stage
+			if workflow.Apply != nil {
+				coreApplyStage = workflow.Apply.ToCoreStage()
+			}
+			var corePlanStage models.Stage
+			if workflow.Plan != nil {
+				corePlanStage = workflow.Plan.ToCoreStage()
+			}
 			commandsPerProject = append(commandsPerProject, models.ProjectCommand{
 				ProjectName:      project.Name,
 				ProjectDir:       project.Dir,
 				ProjectWorkspace: project.Workspace,
 				Terragrunt:       project.Terragrunt,
 				Commands:         workflow.Configuration.OnPullRequestClosed,
-				ApplyStage:       workflow.Apply,
-				PlanStage:        workflow.Plan,
+				ApplyStage:       &coreApplyStage,
+				PlanStage:        &corePlanStage,
 				CommandEnvVars:   commandEnvVars,
 				StateEnvVars:     stateEnvVars,
 			})
@@ -326,15 +336,23 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex
 					if workspaceOverride != "" {
 						workspace = workspaceOverride
 					}
-					stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+					stateEnvVars, commandEnvVars := configuration.CollectEnvVars(workflow.EnvVars)
+					var coreApplyStage models.Stage
+					if workflow.Apply != nil {
+						coreApplyStage = workflow.Apply.ToCoreStage()
+					}
+					var corePlanStage models.Stage
+					if workflow.Plan != nil {
+						corePlanStage = workflow.Plan.ToCoreStage()
+					}
 					commandsPerProject = append(commandsPerProject, models.ProjectCommand{
 						ProjectName:      project.Name,
 						ProjectDir:       project.Dir,
 						ProjectWorkspace: workspace,
 						Terragrunt:       project.Terragrunt,
 						Commands:         []string{command},
-						ApplyStage:       workflow.Apply,
-						PlanStage:        workflow.Plan,
+						ApplyStage:       &coreApplyStage,
+						PlanStage:        &corePlanStage,
 						CommandEnvVars:   commandEnvVars,
 						StateEnvVars:     stateEnvVars,
 					})


Test patch:
diff --git a/cmd/digger/main_test.go b/cmd/digger/main_test.go
index 3817a56e..56276130 100644
--- a/cmd/digger/main_test.go
+++ b/cmd/digger/main_test.go
@@ -2,10 +2,9 @@ package main
 
 import (
 	"digger/pkg/configuration"
-	"digger/pkg/core/models"
 	"digger/pkg/digger"
 	"digger/pkg/github"
-	gh_models "digger/pkg/github/models"
+	"digger/pkg/github/models"
 	"digger/pkg/reporting"
 	"digger/pkg/utils"
 	"fmt"
@@ -885,7 +884,7 @@ func TestGitHubNewPullRequestContext(t *testing.T) {
 		CiService: prManager,
 		PrNumber:  prNumber,
 	}
-	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.WorkflowConfig{})
+	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.Workflow{})
 	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, "")
 
 	assert.NoError(t, err)
@@ -895,7 +894,7 @@ func TestGitHubNewPullRequestContext(t *testing.T) {
 }
 
 func TestGitHubNewCommentContext(t *testing.T) {
-	context, err := gh_models.GetGitHubContext(githubContextCommentJson)
+	context, err := models.GetGitHubContext(githubContextCommentJson)
 	assert.NoError(t, err)
 	if err != nil {
 		fmt.Println(err)
@@ -911,7 +910,7 @@ func TestGitHubNewCommentContext(t *testing.T) {
 		CiService: prManager,
 		PrNumber:  prNumber,
 	}
-	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.WorkflowConfig{})
+	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.Workflow{})
 	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, "")
 
 	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, "")
@@ -923,7 +922,7 @@ func TestGitHubNewCommentContext(t *testing.T) {
 }
 
 func TestInvalidGitHubContext(t *testing.T) {
-	_, err := gh_models.GetGitHubContext(githubInvalidContextJson)
+	_, err := models.GetGitHubContext(githubInvalidContextJson)
 	require.Error(t, err)
 	if err != nil {
 		fmt.Println(err)
@@ -937,13 +936,13 @@ func TestGitHubNewPullRequestInMultiEnvProjectContext(t *testing.T) {
 	pullRequestNumber := 11
 	dev := configuration.Project{Name: "dev", Dir: "dev", Workflow: "dev"}
 	prod := configuration.Project{Name: "prod", Dir: "prod", Workflow: "prod"}
-	workflows := map[string]configuration.WorkflowConfig{
+	workflows := map[string]configuration.Workflow{
 		"dev": {
-			Plan: &models.Stage{Steps: []models.Step{
+			Plan: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "plan", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
-			Apply: &models.Stage{Steps: []models.Step{
+			Apply: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "apply", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
@@ -954,11 +953,11 @@ func TestGitHubNewPullRequestInMultiEnvProjectContext(t *testing.T) {
 			},
 		},
 		"prod": {
-			Plan: &models.Stage{Steps: []models.Step{
+			Plan: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "plan", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
-			Apply: &models.Stage{Steps: []models.Step{
+			Apply: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "apply", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
diff --git a/pkg/configuration/digger_config_test.go b/pkg/configuration/digger_config_test.go
index c2fe89e6..d7d9f641 100644
--- a/pkg/configuration/digger_config_test.go
+++ b/pkg/configuration/digger_config_test.go
@@ -1,7 +1,6 @@
 package configuration
 
 import (
-	"digger/pkg/core/models"
 	"log"
 	"os"
 	"path"
@@ -73,42 +72,6 @@ projects:
 	assert.Equal(t, "path/to/module/test", dg.GetDirectory("prod"))
 }
 
-func TestDefaultDiggerConfig(t *testing.T) {
-	tempDir, teardown := setUp()
-	defer teardown()
-
-	diggerCfg := `
-projects:
-- name: prod
-  branch: /main/
-  dir: path/to/module/test
-  workspace: default
-`
-	deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg)
-	defer deleteFile()
-
-	dg, err := LoadDiggerConfig(tempDir, &FileSystemDirWalker{})
-
-	assert.NoError(t, err, "expected error to be nil")
-	assert.NotNil(t, dg, "expected digger config to be not nil")
-	assert.Equal(t, 1, len(dg.Projects))
-	assert.Equal(t, false, dg.AutoMerge)
-	assert.Equal(t, true, dg.CollectUsageData)
-	assert.Equal(t, 1, len(dg.Workflows))
-
-	workflow := dg.Workflows["default"]
-	assert.NotNil(t, workflow, "expected workflow to be not nil")
-	assert.NotNil(t, workflow.Plan)
-	assert.NotNil(t, workflow.Plan.Steps)
-
-	assert.NotNil(t, workflow.Apply)
-	assert.NotNil(t, workflow.Apply.Steps)
-	assert.NotNil(t, workflow.EnvVars)
-	assert.NotNil(t, workflow.Configuration)
-
-	assert.Equal(t, "path/to/module/test", dg.GetDirectory("prod"))
-}
-
 func TestDiggerConfigDefaultWorkflow(t *testing.T) {
 	tempDir, teardown := setUp()
 	defer teardown()
@@ -171,7 +134,7 @@ workflows:
 
 	dg, err := LoadDiggerConfig(tempDir, &FileSystemDirWalker{})
 	assert.NoError(t, err, "expected error to be nil")
-	assert.Equal(t, models.Step{Action: "run", Value: "echo \"hello\"", Shell: ""}, dg.Workflows["myworkflow"].Plan.Steps[0], "parsed struct does not match expected struct")
+	assert.Equal(t, Step{Action: "run", Value: "echo \"hello\"", Shell: ""}, dg.Workflows["myworkflow"].Plan.Steps[0], "parsed struct does not match expected struct")
 }
 
 func TestEnvVarsConfiguration(t *testing.T) {
@@ -216,10 +179,10 @@ workflows:
 
 	dg, err := LoadDiggerConfig(tempDir, &FileSystemDirWalker{})
 	assert.NoError(t, err, "expected error to be nil")
-	assert.Equal(t, []EnvVar{
+	assert.Equal(t, []EnvVarConfig{
 		{Name: "TF_VAR_state", Value: "s3://mybucket/terraform.tfstate"},
 	}, dg.Workflows["myworkflow"].EnvVars.State, "parsed struct does not match expected struct")
-	assert.Equal(t, []EnvVar{
+	assert.Equal(t, []EnvVarConfig{
 		{Name: "TF_VAR_command", Value: "plan"},
 	}, dg.Workflows["myworkflow"].EnvVars.Commands, "parsed struct does not match expected struct")
 }
@@ -246,9 +209,9 @@ workflows:
 
 	dg, err := LoadDiggerConfig(tempDir, &FileSystemDirWalker{})
 	assert.NoError(t, err, "expected error to be nil")
-	assert.Equal(t, models.Step{Action: "init", ExtraArgs: nil, Shell: ""}, dg.Workflows["default"].Plan.Steps[0], "parsed struct does not match expected struct")
-	assert.Equal(t, models.Step{Action: "plan", ExtraArgs: []string{"-var-file=terraform.tfvars"}, Shell: ""}, dg.Workflows["default"].Plan.Steps[1], "parsed struct does not match expected struct")
-	assert.Equal(t, models.Step{Action: "run", Value: "echo \"hello\"", Shell: "zsh"}, dg.Workflows["default"].Plan.Steps[2], "parsed struct does not match expected struct")
+	assert.Equal(t, Step{Action: "init", ExtraArgs: nil, Shell: ""}, dg.Workflows["default"].Plan.Steps[0], "parsed struct does not match expected struct")
+	assert.Equal(t, Step{Action: "plan", ExtraArgs: []string{"-var-file=terraform.tfvars"}, Shell: ""}, dg.Workflows["default"].Plan.Steps[1], "parsed struct does not match expected struct")
+	assert.Equal(t, Step{Action: "run", Value: "echo \"hello\"", Shell: "zsh"}, dg.Workflows["default"].Plan.Steps[2], "parsed struct does not match expected struct")
 }
 
 func TestDiggerGenerateProjects(t *testing.T) {
diff --git a/pkg/digger/digger_test.go b/pkg/digger/digger_test.go
index 1421e76b..92dbe88f 100644
--- a/pkg/digger/digger_test.go
+++ b/pkg/digger/digger_test.go
@@ -208,7 +208,6 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
 		CiService: prManager,
 		PrNumber:  1,
 	}
-
 	executor := execution.DiggerExecutor{
 		ApplyStage: &models.Stage{},
 		PlanStage: &models.Stage{