Repository: diggerhq/digger
Pull request #332 (2023-06-07 16:02:57)

merge develop into the branch


Patch:
diff --git a/cmd/digger/main.go b/cmd/digger/main.go
index ce504415..cb635723 100644
--- a/cmd/digger/main.go
+++ b/cmd/digger/main.go
@@ -4,13 +4,15 @@ import (
 	"context"
 	"digger/pkg/azure"
 	"digger/pkg/configuration"
+	core_locking "digger/pkg/core/locking"
+	"digger/pkg/core/models"
+	core_storage "digger/pkg/core/storage"
 	"digger/pkg/digger"
 	"digger/pkg/gcp"
 	dg_github "digger/pkg/github"
 	github_models "digger/pkg/github/models"
 	"digger/pkg/gitlab"
 	"digger/pkg/locking"
-	"digger/pkg/models"
 	"digger/pkg/reporting"
 	"digger/pkg/storage"
 	"digger/pkg/usage"
@@ -22,7 +24,7 @@ import (
 	"strings"
 )
 
-func gitHubCI(lock locking.Lock) {
+func gitHubCI(lock core_locking.Lock) {
 	println("Using GitHub.")
 	githubActor := os.Getenv("GITHUB_ACTOR")
 	if githubActor != "" {
@@ -121,7 +123,7 @@ func gitHubCI(lock locking.Lock) {
 	}()
 }
 
-func gitLabCI(lock locking.Lock) {
+func gitLabCI(lock core_locking.Lock) {
 	println("Using GitLab.")
 
 	projectNamespace := os.Getenv("CI_PROJECT_NAMESPACE")
@@ -212,7 +214,7 @@ func gitLabCI(lock locking.Lock) {
 	}()
 }
 
-func azureCI(lock locking.Lock) {
+func azureCI(lock core_locking.Lock) {
 	fmt.Println("> Azure CI detected")
 	azureContext := os.Getenv("AZURE_CONTEXT")
 	azureToken := os.Getenv("AZURE_TOKEN")
@@ -260,7 +262,7 @@ func azureCI(lock locking.Lock) {
 		fmt.Printf("command: %s, project: %s\n", strings.Join(v.Commands, ", "), v.ProjectName)
 	}
 
-	var planStorage storage.PlanStorage
+	var planStorage core_storage.PlanStorage
 	diggerProjectNamespace := parsedAzureContext.BaseUrl + "/" + parsedAzureContext.ProjectName
 
 	reporter := &reporting.CiReporter{
@@ -335,8 +337,8 @@ func main() {
 	}
 }
 
-func newPlanStorage(ghToken string, ghRepoOwner string, ghRepositoryName string, requestedBy string, prNumber int) storage.PlanStorage {
-	var planStorage storage.PlanStorage
+func newPlanStorage(ghToken string, ghRepoOwner string, ghRepositoryName string, requestedBy string, prNumber int) core_storage.PlanStorage {
+	var planStorage core_storage.PlanStorage
 
 	uploadDestination := strings.ToLower(os.Getenv("PLAN_UPLOAD_DESTINATION"))
 	if uploadDestination == "github" {
@@ -367,7 +369,7 @@ func newPlanStorage(ghToken string, ghRepoOwner string, ghRepositoryName string,
 	return planStorage
 }
 
-func logImpactedProjects(projects []configuration.ProjectConfig, prNumber int) {
+func logImpactedProjects(projects []configuration.Project, prNumber int) {
 	logMessage := fmt.Sprintf("Following projects are impacted by pull request #%d\n", prNumber)
 	for _, p := range projects {
 		logMessage += fmt.Sprintf("%s\n", p.Name)
diff --git a/go.mod b/go.mod
index 7e3c7e90..dfbc40f9 100644
--- a/go.mod
+++ b/go.mod
@@ -6,12 +6,12 @@ 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.276
+	github.com/aws/aws-sdk-go v1.44.277
 	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/go-github/v53 v53.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
@@ -27,7 +27,7 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
 	github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
 	github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
-	github.com/cloudflare/circl v1.1.0 // indirect
+	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
@@ -42,9 +42,9 @@ require (
 	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	golang.org/x/crypto v0.7.0 // indirect
-	golang.org/x/net v0.9.0 // indirect
-	golang.org/x/oauth2 v0.7.0 // indirect
-	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/oauth2 v0.8.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
 	golang.org/x/text v0.9.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
diff --git a/go.sum b/go.sum
index d4331905..1f16413b 100644
--- a/go.sum
+++ b/go.sum
@@ -23,8 +23,20 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDm
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
+github.com/aws/aws-sdk-go v1.44.266 h1:MWd775dcYf7NrwgcHLtlsIbWoWkX8p4vomfNHr88zH0=
+github.com/aws/aws-sdk-go v1.44.266/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.268 h1:WoK20tlAvsvQzTcE6TajoprbXmTbcud6MjhErL4P/38=
+github.com/aws/aws-sdk-go v1.44.268/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.273 h1:CX8O0gK+cGrgUyv7bgJ6QQP9mQg7u5mweHdNzULH47c=
+github.com/aws/aws-sdk-go v1.44.273/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.274 h1:vfreSv19e/9Ka9YytOzgzJasrRZfX7dnttLlbh8NKeA=
+github.com/aws/aws-sdk-go v1.44.274/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.275 h1:VqRULgqrigvQLll4e4hXuc568EQAtZQ6jmBzLlQHzSI=
+github.com/aws/aws-sdk-go v1.44.275/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/aws/aws-sdk-go v1.44.276 h1:ywPlx9C5Yc482dUgAZ9bHpQ6onVvJvYE9FJWsNDCEy0=
 github.com/aws/aws-sdk-go v1.44.276/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+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/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=
@@ -34,6 +46,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
 github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
+github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -43,6 +57,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=
@@ -74,6 +89,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v51 v51.0.0 h1:KCjsbgPV28VoRftdP+K2mQL16jniUsLAJknsOVKwHyU=
 github.com/google/go-github/v51 v51.0.0/go.mod h1:kZj/rn/c1lSUbr/PFWl2hhusPV7a5XNYKcwPrd5L3Us=
+github.com/google/go-github/v53 v53.0.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
@@ -91,8 +107,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=
@@ -119,8 +133,12 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/xanzy/go-gitlab v0.83.0 h1:37p0MpTPNbsTMKX/JnmJtY8Ch1sFiJzVF342+RvZEGw=
+github.com/xanzy/go-gitlab v0.83.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
 github.com/xanzy/go-gitlab v0.84.0 h1:PdpCaskQSgcVDsx21c6ikf8Rfyo7SNtFAJwP9PrbCFE=
 github.com/xanzy/go-gitlab v0.84.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -149,9 +167,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
 golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -169,6 +191,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
 golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -224,10 +248,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM=
 google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/azure/azure.go b/pkg/azure/azure.go
index fad28784..aca686e1 100644
--- a/pkg/azure/azure.go
+++ b/pkg/azure/azure.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"digger/pkg/ci"
 	"digger/pkg/configuration"
-	"digger/pkg/models"
+	"digger/pkg/core/models"
 	"digger/pkg/utils"
 	"encoding/json"
 	"errors"
@@ -298,8 +298,8 @@ func (a *AzureReposService) IsClosed(prNumber int) (bool, error) {
 	return *pullRequest.Status == git.PullRequestStatusValues.Completed || *pullRequest.Status == git.PullRequestStatusValues.Abandoned, nil
 }
 
-func ProcessAzureReposEvent(azureEvent interface{}, diggerConfig *configuration.DiggerConfig, ciService ci.CIService) ([]configuration.ProjectConfig, *configuration.ProjectConfig, int, error) {
-	var impactedProjects []configuration.ProjectConfig
+func ProcessAzureReposEvent(azureEvent interface{}, diggerConfig *configuration.DiggerConfig, ciService ci.CIService) ([]configuration.Project, *configuration.Project, int, error) {
+	var impactedProjects []configuration.Project
 	var prNumber int
 
 	switch azureEvent.(type) {
@@ -340,7 +340,7 @@ func ProcessAzureReposEvent(azureEvent interface{}, diggerConfig *configuration.
 	return impactedProjects, nil, prNumber, nil
 }
 
-func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []configuration.ProjectConfig, requestedProject *configuration.ProjectConfig, 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:
@@ -351,15 +351,22 @@ func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []con
 			}
 
 			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,
 			})
@@ -373,15 +380,22 @@ func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []con
 			}
 
 			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,
 			})
@@ -395,15 +409,22 @@ 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.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,
 				})
@@ -422,7 +443,7 @@ func ConvertAzureEventToCommands(parseAzureContext Azure, impactedProjects []con
 		if requestedProject != nil {
 			if len(impactedProjects) > 1 {
 				coversAllImpactedProjects = false
-				runForProjects = []configuration.ProjectConfig{*requestedProject}
+				runForProjects = []configuration.Project{*requestedProject}
 			} else if len(impactedProjects) == 1 && impactedProjects[0].Name != requestedProject.Name {
 				return commandsPerProject, false, fmt.Errorf("requested project %v is not impacted by this PR", requestedProject.Name)
 			}
diff --git a/pkg/ci/main.go b/pkg/ci/ci.go
similarity index 100%
rename from pkg/ci/main.go
rename to pkg/ci/ci.go
diff --git a/pkg/configuration/digger_config.go b/pkg/configuration/digger_config.go
index fe491b8d..3177cd25 100644
--- a/pkg/configuration/digger_config.go
+++ b/pkg/configuration/digger_config.go
@@ -1,125 +1,81 @@
 package configuration
 
 import (
+	"digger/pkg/core/models"
 	"digger/pkg/utils"
 	"errors"
 	"fmt"
 	"github.com/bmatcuk/doublestar/v4"
-	"github.com/jinzhu/copier"
 	"gopkg.in/yaml.v3"
 	"os"
 	"path"
 	"path/filepath"
 )
 
-type DiggerConfigYaml struct {
-	Projects               []ProjectYaml               `yaml:"projects"`
-	AutoMerge              bool                        `yaml:"auto_merge"`
-	Workflows              map[string]WorkflowYaml     `yaml:"workflows"`
-	CollectUsageData       bool                        `yaml:"collect_usage_data"`
-	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       *EnvVarsYaml               `yaml:"env_vars"`
-	Plan          *StageYaml                 `yaml:"plan,omitempty"`
-	Apply         *StageYaml                 `yaml:"apply,omitempty"`
-	Configuration *WorkflowConfigurationYaml `yaml:"workflow_configuration"`
-}
-
-type WorkflowConfigurationYaml struct {
+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 StageYaml struct {
-	Steps []StepYaml `yaml:"steps"`
-}
-
-type StepYaml struct {
-	Action    string
-	Value     string
-	ExtraArgs []string `yaml:"extra_args,omitempty"`
-	Shell     string
-}
-
-type EnvVarsYaml struct {
-	State    []EnvVarConfigYaml `yaml:"state"`
-	Commands []EnvVarConfigYaml `yaml:"commands"`
+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"`
 }
 
-type EnvVarConfigYaml struct {
+type EnvVarConfig 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"`
-}
-
 type DiggerConfig struct {
-	Projects         []ProjectConfig
+	Projects         []Project
 	AutoMerge        bool
 	CollectUsageData bool
-	Workflows        map[string]WorkflowConfig
-}
-
-type ProjectConfig struct {
-	Name            string
-	Dir             string
-	Workspace       string
-	Terragrunt      bool
-	Workflow        string
-	IncludePatterns []string
-	ExcludePatterns []string
+	Workflows        map[string]Workflow
 }
 
-type WorkflowConfig struct {
-	EnvVars       *EnvVarsConfig
-	Plan          *StageConfig
-	Apply         *StageConfig
-	Configuration *WorkflowConfigurationConfig
+type GenerateProjectsConfig struct {
+	Include string `yaml:"include"`
+	Exclude string `yaml:"exclude"`
 }
 
-type WorkflowConfigurationConfig struct {
-	OnPullRequestPushed []string
-	OnPullRequestClosed []string
-	OnCommitToDefault   []string
+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 StageConfig struct {
-	Steps []StepConfig
+type Stage struct {
+	Steps []Step `yaml:"steps"`
 }
 
-type StepConfig struct {
-	Action    string
-	Value     string
-	ExtraArgs []string
-	Shell     string
+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}
 }
 
-type EnvVarsConfig struct {
-	State    []EnvVarConfigConfig
-	Commands []EnvVarConfigConfig
+type Workflow struct {
+	EnvVars       EnvVars                `yaml:"env_vars"`
+	Plan          *Stage                 `yaml:"plan,omitempty"`
+	Apply         *Stage                 `yaml:"apply,omitempty"`
+	Configuration *WorkflowConfiguration `yaml:"workflow_configuration"`
 }
 
-type EnvVarConfigConfig struct {
-	Name      string
-	ValueFrom string
-	Value     string
+type EnvVars struct {
+	State    []EnvVarConfig `yaml:"state"`
+	Commands []EnvVarConfig `yaml:"commands"`
 }
 
 type DirWalker interface {
@@ -149,8 +105,8 @@ 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 *ProjectYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	type rawProject ProjectYaml
+func (p *Project) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type rawProject Project
 	raw := rawProject{
 		Workspace:  "default",
 		Terragrunt: false,
@@ -159,20 +115,20 @@ func (p *ProjectYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if err := unmarshal(&raw); err != nil {
 		return err
 	}
-	*p = ProjectYaml(raw)
+	*p = Project(raw)
 	return nil
 }
 
-func (w *WorkflowYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	type rawWorkflow WorkflowYaml
+func (w *Workflow) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type rawWorkflow Workflow
 	raw := rawWorkflow{
-		Configuration: &WorkflowConfigurationYaml{
+		Configuration: &WorkflowConfiguration{
 			OnCommitToDefault:   []string{"digger unlock"},
 			OnPullRequestPushed: []string{"digger plan"},
 			OnPullRequestClosed: []string{"digger unlock"},
 		},
-		Plan: &StageYaml{
-			Steps: []StepYaml{
+		Plan: &Stage{
+			Steps: []Step{
 				{
 					Action: "init", ExtraArgs: []string{},
 				},
@@ -181,8 +137,8 @@ func (w *WorkflowYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
 				},
 			},
 		},
-		Apply: &StageYaml{
-			Steps: []StepYaml{
+		Apply: &Stage{
+			Steps: []Step{
 				{
 					Action: "init", ExtraArgs: []string{},
 				},
@@ -191,19 +147,35 @@ func (w *WorkflowYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
 				},
 			},
 		},
-		EnvVars: &EnvVarsYaml{
-			State:    []EnvVarConfigYaml{},
-			Commands: []EnvVarConfigYaml{},
+		EnvVars: EnvVars{
+			State:    []EnvVarConfig{},
+			Commands: []EnvVarConfig{},
 		},
 	}
 	if err := unmarshal(&raw); err != nil {
 		return err
 	}
-	*w = WorkflowYaml(raw)
+	*w = Workflow(raw)
 	return nil
 }
 
-func (s *StepYaml) UnmarshalYAML(value *yaml.Node) error {
+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)
 	}
@@ -228,7 +200,7 @@ func (s *StepYaml) UnmarshalYAML(value *yaml.Node) error {
 	return nil
 }
 
-func (s *StepYaml) extract(stepMap map[string]interface{}, action string) {
+func (s *Step) extract(stepMap map[string]interface{}, action string) {
 	if _, ok := stepMap[action]; ok {
 		s.Action = action
 		var extraArgs []string
@@ -241,15 +213,15 @@ func (s *StepYaml) extract(stepMap map[string]interface{}, action string) {
 	}
 }
 
-func defaultWorkflow() *WorkflowConfig {
-	return &WorkflowConfig{
-		Configuration: &WorkflowConfigurationConfig{
+func defaultWorkflow() *Workflow {
+	return &Workflow{
+		Configuration: &WorkflowConfiguration{
 			OnCommitToDefault:   []string{"digger unlock"},
 			OnPullRequestPushed: []string{"digger plan"},
 			OnPullRequestClosed: []string{"digger unlock"},
 		},
-		Plan: &StageConfig{
-			Steps: []StepConfig{
+		Plan: &Stage{
+			Steps: []Step{
 				{
 					Action: "init", ExtraArgs: []string{},
 				},
@@ -258,8 +230,8 @@ func defaultWorkflow() *WorkflowConfig {
 				},
 			},
 		},
-		Apply: &StageConfig{
-			Steps: []StepConfig{
+		Apply: &Stage{
+			Steps: []Step{
 				{
 					Action: "init", ExtraArgs: []string{},
 				},
@@ -271,94 +243,6 @@ func defaultWorkflow() *WorkflowConfig {
 	}
 }
 
-func copyProjects(projects []ProjectYaml) []ProjectConfig {
-	result := make([]ProjectConfig, len(projects))
-	for i, p := range projects {
-		item := ProjectConfig{p.Name,
-			p.Dir,
-			p.Workspace,
-			p.Terragrunt,
-			p.Workflow,
-			p.IncludePatterns,
-			p.ExcludePatterns,
-		}
-		result[i] = item
-	}
-	return result
-}
-
-func copyEnvVars(envVars *EnvVarsYaml) *EnvVarsConfig {
-	result := EnvVarsConfig{}
-	result.State = make([]EnvVarConfigConfig, len(envVars.State))
-	result.Commands = make([]EnvVarConfigConfig, len(envVars.Commands))
-
-	for i, s := range envVars.State {
-		item := EnvVarConfigConfig{
-			s.Name,
-			s.ValueFrom,
-			s.Value,
-		}
-		result.State[i] = item
-	}
-	for i, s := range envVars.Commands {
-		item := EnvVarConfigConfig{
-			s.Name,
-			s.ValueFrom,
-			s.Value,
-		}
-		result.Commands[i] = item
-	}
-
-	return &result
-}
-
-func copyStage(stage *StageYaml) *StageConfig {
-	result := StageConfig{}
-	result.Steps = make([]StepConfig, len(stage.Steps))
-
-	for i, s := range stage.Steps {
-		item := StepConfig{
-			s.Action,
-			s.Value,
-			s.ExtraArgs,
-			s.Shell,
-		}
-		result.Steps[i] = item
-	}
-	return &result
-}
-
-func copyWorkflowConfiguration(config *WorkflowConfigurationYaml) *WorkflowConfigurationConfig {
-	result := WorkflowConfigurationConfig{}
-	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 := copyEnvVars(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"
@@ -367,17 +251,14 @@ func ConvertDiggerYamlToConfig(diggerYaml *DiggerConfigYaml, workingDir string,
 
 	// 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
-
+		diggerConfig.Workflows = diggerYaml.Workflows
 	} else {
 		workflow := *defaultWorkflow()
-		diggerConfig.Workflows = make(map[string]WorkflowConfig)
+		diggerConfig.Workflows = make(map[string]Workflow)
 		diggerConfig.Workflows[defaultWorkflowName] = workflow
 	}
 
-	projects := copyProjects(diggerYaml.Projects)
-	diggerConfig.Projects = projects
+	diggerConfig.Projects = diggerYaml.Projects
 
 	// update project's workflow if needed
 	for _, project := range diggerConfig.Projects {
@@ -394,8 +275,11 @@ func ConvertDiggerYamlToConfig(diggerYaml *DiggerConfigYaml, workingDir string,
 		}
 		projectNames[project.Name] = true
 	}
-
-	diggerConfig.CollectUsageData = diggerYaml.CollectUsageData
+	if diggerYaml.CollectUsageData != nil {
+		diggerConfig.CollectUsageData = *diggerYaml.CollectUsageData
+	} else {
+		diggerConfig.CollectUsageData = true
+	}
 
 	if diggerYaml.GenerateProjectsConfig != nil {
 		dirs, err := walker.GetDirs(workingDir)
@@ -417,7 +301,7 @@ func ConvertDiggerYamlToConfig(diggerYaml *DiggerConfigYaml, workingDir string,
 			}
 			if includeMatch && !excludeMatch {
 				// generate a new project using default workflow
-				project := ProjectConfig{Name: filepath.Base(dir), Dir: filepath.Join(workingDir, dir), Workflow: defaultWorkflowName}
+				project := Project{Name: filepath.Base(dir), Dir: filepath.Join(workingDir, dir), Workflow: defaultWorkflowName}
 				diggerConfig.Projects = append(diggerConfig.Projects, project)
 			}
 		}
@@ -426,8 +310,7 @@ func ConvertDiggerYamlToConfig(diggerYaml *DiggerConfigYaml, workingDir string,
 }
 
 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) {
@@ -437,11 +320,39 @@ func LoadDiggerConfig(workingDir string, walker DirWalker) (*DiggerConfig, error
 
 	if fileName == "" {
 		fmt.Println("No digger config found, using default one")
-		config.Projects = make([]ProjectConfig, 1)
+		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)
@@ -449,15 +360,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
 	}
@@ -471,8 +382,8 @@ func LoadDiggerConfig(workingDir string, walker DirWalker) (*DiggerConfig, error
 	return c, nil
 }
 
-func defaultProject() ProjectConfig {
-	return ProjectConfig{
+func defaultProject() Project {
+	return Project{
 		Name:       "default",
 		Dir:        ".",
 		Workspace:  "default",
@@ -481,7 +392,7 @@ func defaultProject() ProjectConfig {
 	}
 }
 
-func (c *DiggerConfig) GetProject(projectName string) *ProjectConfig {
+func (c *DiggerConfig) GetProject(projectName string) *Project {
 	for _, project := range c.Projects {
 		if projectName == project.Name {
 			return &project
@@ -490,7 +401,7 @@ func (c *DiggerConfig) GetProject(projectName string) *ProjectConfig {
 	return nil
 }
 
-func (c *DiggerConfig) GetProjects(projectName string) []ProjectConfig {
+func (c *DiggerConfig) GetProjects(projectName string) []Project {
 	if projectName == "" {
 		return c.Projects
 	}
@@ -498,11 +409,11 @@ func (c *DiggerConfig) GetProjects(projectName string) []ProjectConfig {
 	if project == nil {
 		return nil
 	}
-	return []ProjectConfig{*project}
+	return []Project{*project}
 }
 
-func (c *DiggerConfig) GetModifiedProjects(changedFiles []string) []ProjectConfig {
-	var result []ProjectConfig
+func (c *DiggerConfig) GetModifiedProjects(changedFiles []string) []Project {
+	var result []Project
 	for _, project := range c.Projects {
 		for _, changedFile := range changedFiles {
 			// we append ** to make our directory a globable pattern
@@ -528,7 +439,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]
@@ -579,7 +490,7 @@ func retrieveConfigFile(workingDir string) (string, error) {
 	return "", nil
 }
 
-func CollectEnvVars(envs *EnvVarsConfig) (map[string]string, map[string]string) {
+func CollectEnvVars(envs EnvVars) (map[string]string, map[string]string) {
 	stateEnvVars := map[string]string{}
 
 	for _, envvar := range envs.State {
diff --git a/pkg/core/execution/execution.go b/pkg/core/execution/execution.go
new file mode 100644
index 00000000..ddd47a90
--- /dev/null
+++ b/pkg/core/execution/execution.go
@@ -0,0 +1,270 @@
+package execution
+
+import (
+	"digger/pkg/core/locking"
+	"digger/pkg/core/models"
+	"digger/pkg/core/reporting"
+	"digger/pkg/core/runners"
+	"digger/pkg/core/storage"
+	"digger/pkg/core/terraform"
+	"digger/pkg/core/utils"
+	"fmt"
+	"log"
+	"os"
+	"path"
+	"regexp"
+	"strings"
+)
+
+type DiggerExecutor struct {
+	ProjectNamespace  string
+	ProjectName       string
+	ProjectPath       string
+	StateEnvVars      map[string]string
+	CommandEnvVars    map[string]string
+	ApplyStage        *models.Stage
+	PlanStage         *models.Stage
+	CommandRunner     runners.CommandRun
+	TerraformExecutor terraform.TerraformExecutor
+	Reporter          reporting.Reporter
+	ProjectLock       locking.ProjectLock
+	PlanStorage       storage.PlanStorage
+}
+
+func (d DiggerExecutor) planFileName() string {
+	return d.ProjectNamespace + "#" + d.ProjectName + ".tfplan"
+}
+
+func (d DiggerExecutor) localPlanFilePath() string {
+	return path.Join(d.ProjectPath, d.planFileName())
+}
+
+func (d DiggerExecutor) storedPlanFilePath() string {
+	return path.Join(d.ProjectNamespace, d.planFileName())
+}
+
+func (d DiggerExecutor) Plan() (bool, error) {
+	locked, err := d.ProjectLock.Lock()
+	if err != nil {
+		return false, fmt.Errorf("error locking project: %v", err)
+	}
+	log.Printf("Lock result: %t\n", locked)
+	if locked {
+		var planSteps []models.Step
+
+		if d.PlanStage != nil {
+			planSteps = d.PlanStage.Steps
+		} else {
+			planSteps = []models.Step{
+				{
+					Action: "init",
+				},
+				{
+					Action: "plan",
+				},
+			}
+		}
+		for _, step := range planSteps {
+			if step.Action == "init" {
+				_, _, err := d.TerraformExecutor.Init(step.ExtraArgs, d.StateEnvVars)
+				if err != nil {
+					return false, fmt.Errorf("error running init: %v", err)
+				}
+			}
+			if step.Action == "plan" {
+				planArgs := []string{"-out", d.planFileName()}
+				planArgs = append(planArgs, step.ExtraArgs...)
+				isNonEmptyPlan, stdout, stderr, err := d.TerraformExecutor.Plan(planArgs, d.CommandEnvVars)
+				if err != nil {
+					return false, fmt.Errorf("error executing plan: %v", err)
+				}
+				if d.PlanStorage != nil {
+					planExists, err := d.PlanStorage.PlanExists(d.storedPlanFilePath())
+					if err != nil {
+						return false, fmt.Errorf("error checking if plan exists: %v", err)
+					}
+
+					if planExists {
+						err = d.PlanStorage.DeleteStoredPlan(d.storedPlanFilePath())
+						if err != nil {
+							return false, fmt.Errorf("error deleting plan: %v", err)
+						}
+					}
+
+					err = d.PlanStorage.StorePlan(d.localPlanFilePath(), d.storedPlanFilePath())
+					if err != nil {
+						return false, fmt.Errorf("error storing plan: %v", err)
+					}
+				}
+				plan := cleanupTerraformPlan(isNonEmptyPlan, err, stdout, stderr)
+				comment := utils.GetTerraformOutputAsCollapsibleComment("Plan for **"+d.ProjectLock.LockId()+"**", plan)
+				err = d.Reporter.Report(comment)
+				if err != nil {
+					fmt.Printf("error publishing comment: %v", err)
+				}
+			}
+			if step.Action == "run" {
+				var commands []string
+				if os.Getenv("ACTIVATE_VENV") == "true" {
+					commands = append(commands, fmt.Sprintf("source %v/.venv/bin/activate", os.Getenv("GITHUB_WORKSPACE")))
+				}
+				commands = append(commands, step.Value)
+				log.Printf("Running %v for **%v**\n", step.Value, d.ProjectLock.LockId())
+				_, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands)
+				if err != nil {
+					return false, fmt.Errorf("error running command: %v", err)
+				}
+			}
+		}
+		return true, nil
+	}
+	return false, nil
+}
+
+func (d DiggerExecutor) Apply() (bool, error) {
+	var plansFilename *string
+	if d.PlanStorage != nil {
+		var err error
+		plansFilename, err = d.PlanStorage.RetrievePlan(d.localPlanFilePath(), d.storedPlanFilePath())
+		if err != nil {
+			return false, fmt.Errorf("error retrieving plan: %v", err)
+		}
+	}
+
+	locked, err := d.ProjectLock.Lock()
+
+	if err != nil {
+		return false, fmt.Errorf("error locking project: %v", err)
+	}
+
+	if locked {
+		var applySteps []models.Step
+
+		if d.ApplyStage != nil {
+			applySteps = d.ApplyStage.Steps
+		} else {
+			applySteps = []models.Step{
+				{
+					Action: "init",
+				},
+				{
+					Action: "apply",
+				},
+			}
+		}
+
+		for _, step := range applySteps {
+			if step.Action == "init" {
+				_, _, err := d.TerraformExecutor.Init(step.ExtraArgs, d.StateEnvVars)
+				if err != nil {
+					return false, fmt.Errorf("error running init: %v", err)
+				}
+			}
+			if step.Action == "apply" {
+				stdout, stderr, err := d.TerraformExecutor.Apply(step.ExtraArgs, plansFilename, d.CommandEnvVars)
+				applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
+				comment := utils.GetTerraformOutputAsCollapsibleComment("Apply for **"+d.ProjectLock.LockId()+"**", applyOutput)
+				commentErr := d.Reporter.Report(comment)
+				if commentErr != nil {
+					fmt.Printf("error publishing comment: %v", err)
+				}
+				if err != nil {
+					commentErr = d.Reporter.Report("Error during applying.")
+					if commentErr != nil {
+						fmt.Printf("error publishing comment: %v", err)
+					}
+					return false, fmt.Errorf("error executing apply: %v", err)
+				}
+			}
+			if step.Action == "run" {
+				var commands []string
+				if os.Getenv("ACTIVATE_VENV") == "true" {
+					commands = append(commands, fmt.Sprintf("source %v/.venv/bin/activate", os.Getenv("GITHUB_WORKSPACE")))
+				}
+				commands = append(commands, step.Value)
+				log.Printf("Running %v for **%v**\n", step.Value, d.ProjectLock.LockId())
+				_, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands)
+				if err != nil {
+					return false, fmt.Errorf("error running command: %v", err)
+				}
+			}
+		}
+		return true, nil
+	} else {
+		return false, nil
+	}
+}
+
+func (d DiggerExecutor) Unlock() error {
+	err := d.ProjectLock.ForceUnlock()
+	if err != nil {
+		return fmt.Errorf("failed to aquire lock: %s, %v", d.ProjectLock.LockId(), err)
+	}
+	if d.PlanStorage != nil {
+		err = d.PlanStorage.DeleteStoredPlan(d.storedPlanFilePath())
+		if err != nil {
+			return fmt.Errorf("failed to delete stored plan file '%v':  %v", d.storedPlanFilePath(), err)
+		}
+	}
+	if err != nil {
+		return fmt.Errorf("failed to delete stored plan file '%v':  %v", d.storedPlanFilePath(), err)
+	}
+	return nil
+}
+
+func (d DiggerExecutor) Lock() error {
+	_, err := d.ProjectLock.Lock()
+	if err != nil {
+		return fmt.Errorf("failed to aquire lock: %s, %v", d.ProjectLock.LockId(), err)
+	}
+	return nil
+}
+
+func cleanupTerraformOutput(nonEmptyOutput bool, planError error, stdout string, stderr string, regexStr *string) string {
+	var errorStr, start string
+
+	// removes output of terraform -version command that terraform-exec executes on every run
+	i := strings.Index(stdout, "Initializing the backend...")
+	if i != -1 {
+		stdout = stdout[i:]
+	}
+	endPos := len(stdout)
+
+	if planError != nil {
+		if stderr != "" {
+			errorStr = stderr
+		} else if stdout != "" {
+			errorStr = stdout
+		}
+		return errorStr
+	} else if nonEmptyOutput {
+		start = "Terraform will perform the following actions:"
+	} else {
+		start = "No changes. Your infrastructure matches the configuration."
+	}
+
+	startPos := strings.Index(stdout, start)
+	if startPos == -1 {
+		startPos = 0
+	}
+
+	if regexStr != nil {
+		regex := regexp.MustCompile(*regexStr)
+		matches := regex.FindStringSubmatch(stdout)
+		if len(matches) > 0 {
+			firstMatch := matches[0]
+			endPos = strings.LastIndex(stdout, firstMatch) + len(firstMatch)
+		}
+	}
+
+	return stdout[startPos:endPos]
+}
+
+func cleanupTerraformApply(nonEmptyPlan bool, planError error, stdout string, stderr string) string {
+	return cleanupTerraformOutput(nonEmptyPlan, planError, stdout, stderr, nil)
+}
+
+func cleanupTerraformPlan(nonEmptyPlan bool, planError error, stdout string, stderr string) string {
+	regex := `───────────.+`
+	return cleanupTerraformOutput(nonEmptyPlan, planError, stdout, stderr, &regex)
+}
diff --git a/pkg/core/locking/locking.go b/pkg/core/locking/locking.go
new file mode 100644
index 00000000..5c9d23d4
--- /dev/null
+++ b/pkg/core/locking/locking.go
@@ -0,0 +1,14 @@
+package locking
+
+type Lock interface {
+	Lock(transactionId int, resource string) (bool, error)
+	Unlock(resource string) (bool, error)
+	GetLock(resource string) (*int, error)
+}
+
+type ProjectLock interface {
+	Lock() (bool, error)
+	Unlock() (bool, error)
+	ForceUnlock() error
+	LockId() string
+}
diff --git a/pkg/models/models.go b/pkg/core/models/models.go
similarity index 57%
rename from pkg/models/models.go
rename to pkg/core/models/models.go
index df526be3..75a09e0b 100644
--- a/pkg/models/models.go
+++ b/pkg/core/models/models.go
@@ -1,15 +1,24 @@
 package models
 
-import "digger/pkg/configuration"
-
 type ProjectCommand struct {
 	ProjectName      string
 	ProjectDir       string
 	ProjectWorkspace string
 	Terragrunt       bool
 	Commands         []string
-	ApplyStage       *configuration.StageConfig
-	PlanStage        *configuration.StageConfig
+	ApplyStage       *Stage
+	PlanStage        *Stage
 	StateEnvVars     map[string]string
 	CommandEnvVars   map[string]string
 }
+
+type Step struct {
+	Action    string
+	Value     string
+	ExtraArgs []string
+	Shell     string
+}
+
+type Stage struct {
+	Steps []Step
+}
diff --git a/pkg/core/reporting/reporting.go b/pkg/core/reporting/reporting.go
new file mode 100644
index 00000000..622a533e
--- /dev/null
+++ b/pkg/core/reporting/reporting.go
@@ -0,0 +1,5 @@
+package reporting
+
+type Reporter interface {
+	Report(report string) error
+}
diff --git a/pkg/core/runners/runners.go b/pkg/core/runners/runners.go
new file mode 100644
index 00000000..a1c51c2c
--- /dev/null
+++ b/pkg/core/runners/runners.go
@@ -0,0 +1,55 @@
+package runners
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+)
+
+type CommandRun interface {
+	Run(workingDir string, shell string, commands []string) (string, string, error)
+}
+
+type CommandRunner struct {
+}
+
+func (c CommandRunner) Run(workingDir string, shell string, commands []string) (string, string, error) {
+	var args []string
+	if shell == "" {
+		shell = "bash"
+		args = []string{"-eo", "pipefail"}
+	}
+
+	scriptFile, err := ioutil.TempFile("", "run-script")
+	if err != nil {
+		return "", "", fmt.Errorf("error creating script file: %v", err)
+	}
+	defer os.Remove(scriptFile.Name())
+
+	for _, command := range commands {
+		_, err := scriptFile.WriteString(command + "\n")
+		if err != nil {
+			return "", "", fmt.Errorf("error writing to script file: %v", err)
+		}
+	}
+	args = append(args, scriptFile.Name())
+
+	cmd := exec.Command(shell, args...)
+	cmd.Dir = workingDir
+
+	var stdout, stderr bytes.Buffer
+	mwout := io.MultiWriter(os.Stdout, &stdout)
+	mwerr := io.MultiWriter(os.Stderr, &stderr)
+	cmd.Stdout = mwout
+	cmd.Stderr = mwerr
+	err = cmd.Run()
+
+	if err != nil {
+		return stdout.String(), stderr.String(), fmt.Errorf("error: %v", err)
+	}
+
+	return stdout.String(), stderr.String(), err
+}
diff --git a/pkg/core/storage/plan_storage.go b/pkg/core/storage/plan_storage.go
new file mode 100644
index 00000000..3fc39686
--- /dev/null
+++ b/pkg/core/storage/plan_storage.go
@@ -0,0 +1,8 @@
+package storage
+
+type PlanStorage interface {
+	StorePlan(localPlanFilePath string, storedPlanFilePath string) error
+	RetrievePlan(localPlanFilePath string, storedPlanFilePath string) (*string, error)
+	DeleteStoredPlan(storedPlanFilePath string) error
+	PlanExists(storedPlanFilePath string) (bool, error)
+}
diff --git a/pkg/terraform/utils.go b/pkg/core/terraform/test_utils.go
similarity index 100%
rename from pkg/terraform/utils.go
rename to pkg/core/terraform/test_utils.go
diff --git a/pkg/terraform/tf.go b/pkg/core/terraform/tf.go
similarity index 100%
rename from pkg/terraform/tf.go
rename to pkg/core/terraform/tf.go
diff --git a/pkg/terraform/tf_test.go b/pkg/core/terraform/tf_test.go
similarity index 100%
rename from pkg/terraform/tf_test.go
rename to pkg/core/terraform/tf_test.go
diff --git a/pkg/utils/comments.go b/pkg/core/utils/comments.go
similarity index 100%
rename from pkg/utils/comments.go
rename to pkg/core/utils/comments.go
diff --git a/pkg/digger/digger.go b/pkg/digger/digger.go
index 5ddbeded..5942c035 100644
--- a/pkg/digger/digger.go
+++ b/pkg/digger/digger.go
@@ -1,29 +1,60 @@
 package digger
 
 import (
-	"bytes"
 	"digger/pkg/ci"
-	"digger/pkg/configuration"
+	"digger/pkg/core/execution"
+	core_locking "digger/pkg/core/locking"
+	"digger/pkg/core/models"
+	"digger/pkg/core/reporting"
+	"digger/pkg/core/runners"
+	"digger/pkg/core/storage"
+	"digger/pkg/core/terraform"
 	"digger/pkg/locking"
-	"digger/pkg/models"
-	"digger/pkg/reporting"
-	"digger/pkg/storage"
-	"digger/pkg/terraform"
 	"digger/pkg/usage"
-	"digger/pkg/utils"
 	"fmt"
-	"io"
-	"io/ioutil"
 	"log"
 	"os"
-	"os/exec"
 	"path"
-	"regexp"
-	"strings"
 	"time"
 )
 
-func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNamespace string, requestedBy string, eventName string, prNumber int, ciService ci.CIService, lock locking.Lock, reporter reporting.Reporter, planStorage storage.PlanStorage, workingDir string) (bool, bool, error) {
+type CIName string
+
+const (
+	None      = CIName("")
+	GitHub    = CIName("github")
+	GitLab    = CIName("gitlab")
+	BitBucket = CIName("bitbucket")
+	Azure     = CIName("azure")
+)
+
+func (ci CIName) String() string {
+	return string(ci)
+}
+
+func DetectCI() CIName {
+
+	notEmpty := func(key string) bool {
+		return os.Getenv(key) != ""
+	}
+
+	if notEmpty("GITHUB_ACTIONS") {
+		return GitHub
+	}
+	if notEmpty("GITLAB_CI") {
+		return GitLab
+	}
+	if notEmpty("BITBUCKET_BUILD_NUMBER") {
+		return BitBucket
+	}
+	if notEmpty("AZURE_CI") {
+		return Azure
+	}
+	return None
+
+}
+
+func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNamespace string, requestedBy string, eventName string, prNumber int, ciService ci.CIService, lock core_locking.Lock, reporter reporting.Reporter, planStorage storage.PlanStorage, workingDir string) (bool, bool, error) {
 	appliesPerProject := make(map[string]bool)
 	for _, projectCommands := range commandsPerProject {
 		for _, command := range projectCommands.Commands {
@@ -43,8 +74,8 @@ func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNa
 				terraformExecutor = terraform.Terraform{WorkingDir: projectPath, Workspace: projectCommands.ProjectWorkspace}
 			}
 
-			commandRunner := CommandRunner{}
-			diggerExecutor := DiggerExecutor{
+			commandRunner := runners.CommandRunner{}
+			diggerExecutor := execution.DiggerExecutor{
 				projectNamespace,
 				projectCommands.ProjectName,
 				projectPath,
@@ -126,9 +157,9 @@ func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNa
 	return allAppliesSuccess, atLeastOneApply, nil
 }
 
-func MergePullRequest(githubPrService ci.CIService, prNumber int) {
+func MergePullRequest(ciService ci.CIService, prNumber int) {
 	time.Sleep(5 * time.Second)
-	combinedStatus, err := githubPrService.GetCombinedPullRequestStatus(prNumber)
+	combinedStatus, err := ciService.GetCombinedPullRequestStatus(prNumber)
 
 	if err != nil {
 		log.Fatalf("failed to get combined status, %v", err)
@@ -138,7 +169,7 @@ func MergePullRequest(githubPrService ci.CIService, prNumber int) {
 		log.Fatalf("PR is not mergeable. Status: %v", combinedStatus)
 	}
 
-	prIsMergeable, err := githubPrService.IsMergeable(prNumber)
+	prIsMergeable, err := ciService.IsMergeable(prNumber)
 
 	if err != nil {
 		log.Fatalf("failed to check if PR is mergeable, %v", err)
@@ -148,344 +179,8 @@ func MergePullRequest(githubPrService ci.CIService, prNumber int) {
 		log.Fatalf("PR is not mergeable")
 	}
 
-	err = githubPrService.MergePullRequest(prNumber)
+	err = ciService.MergePullRequest(prNumber)
 	if err != nil {
 		log.Fatalf("failed to merge PR, %v", err)
 	}
 }
-
-type DiggerExecutor struct {
-	ProjectNamespace  string
-	ProjectName       string
-	ProjectPath       string
-	StateEnvVars      map[string]string
-	CommandEnvVars    map[string]string
-	ApplyStage        *configuration.StageConfig
-	PlanStage         *configuration.StageConfig
-	CommandRunner     CommandRun
-	TerraformExecutor terraform.TerraformExecutor
-	Reporter          reporting.Reporter
-	ProjectLock       locking.ProjectLock
-	PlanStorage       storage.PlanStorage
-}
-
-type CommandRun interface {
-	Run(workingDir string, shell string, commands []string) (string, string, error)
-}
-
-type CommandRunner struct {
-}
-
-func (c CommandRunner) Run(workingDir string, shell string, commands []string) (string, string, error) {
-	var args []string
-	if shell == "" {
-		shell = "bash"
-		args = []string{"-eo", "pipefail"}
-	}
-
-	scriptFile, err := ioutil.TempFile("", "run-script")
-	if err != nil {
-		return "", "", fmt.Errorf("error creating script file: %v", err)
-	}
-	defer os.Remove(scriptFile.Name())
-
-	for _, command := range commands {
-		_, err := scriptFile.WriteString(command + "\n")
-		if err != nil {
-			return "", "", fmt.Errorf("error writing to script file: %v", err)
-		}
-	}
-	args = append(args, scriptFile.Name())
-
-	cmd := exec.Command(shell, args...)
-	cmd.Dir = workingDir
-
-	var stdout, stderr bytes.Buffer
-	mwout := io.MultiWriter(os.Stdout, &stdout)
-	mwerr := io.MultiWriter(os.Stderr, &stderr)
-	cmd.Stdout = mwout
-	cmd.Stderr = mwerr
-	err = cmd.Run()
-
-	if err != nil {
-		return stdout.String(), stderr.String(), fmt.Errorf("error: %v", err)
-	}
-
-	return stdout.String(), stderr.String(), err
-}
-
-func (d DiggerExecutor) planFileName() string {
-	return d.ProjectNamespace + "#" + d.ProjectName + ".tfplan"
-}
-
-func (d DiggerExecutor) localPlanFilePath() string {
-	return path.Join(d.ProjectPath, d.planFileName())
-}
-
-func (d DiggerExecutor) storedPlanFilePath() string {
-	return path.Join(d.ProjectNamespace, d.planFileName())
-}
-
-func (d DiggerExecutor) Plan() (bool, error) {
-	locked, err := d.ProjectLock.Lock()
-	if err != nil {
-		return false, fmt.Errorf("error locking project: %v", err)
-	}
-	log.Printf("Lock result: %t\n", locked)
-	if locked {
-		var planSteps []configuration.StepConfig
-
-		// move default values to config init
-		if d.PlanStage != nil {
-			planSteps = d.PlanStage.Steps
-		} else {
-			planSteps = []configuration.StepConfig{
-				{
-					Action: "init",
-				},
-				{
-					Action: "plan",
-				},
-			}
-		}
-		for _, step := range planSteps {
-			if step.Action == "init" {
-				_, _, err := d.TerraformExecutor.Init(step.ExtraArgs, d.StateEnvVars)
-				if err != nil {
-					return false, fmt.Errorf("error running init: %v", err)
-				}
-			}
-			if step.Action == "plan" {
-				planArgs := []string{"-out", d.planFileName()}
-				planArgs = append(planArgs, step.ExtraArgs...)
-				isNonEmptyPlan, stdout, stderr, err := d.TerraformExecutor.Plan(planArgs, d.CommandEnvVars)
-				if err != nil {
-					return false, fmt.Errorf("error executing plan: %v", err)
-				}
-				if d.PlanStorage != nil {
-					planExists, err := d.PlanStorage.PlanExists(d.storedPlanFilePath())
-					if err != nil {
-						return false, fmt.Errorf("error checking if plan exists: %v", err)
-					}
-
-					if planExists {
-						err = d.PlanStorage.DeleteStoredPlan(d.storedPlanFilePath())
-						if err != nil {
-							return false, fmt.Errorf("error deleting plan: %v", err)
-						}
-					}
-
-					err = d.PlanStorage.StorePlan(d.localPlanFilePath(), d.storedPlanFilePath())
-					if err != nil {
-						return false, fmt.Errorf("error storing plan: %v", err)
-					}
-				}
-				plan := cleanupTerraformPlan(isNonEmptyPlan, err, stdout, stderr)
-				comment := utils.GetTerraformOutputAsCollapsibleComment("Plan for **"+d.ProjectLock.LockId()+"**", plan)
-				err = d.Reporter.Report(comment)
-				if err != nil {
-					fmt.Printf("error publishing comment: %v", err)
-				}
-			}
-			if step.Action == "run" {
-				var commands []string
-				if os.Getenv("ACTIVATE_VENV") == "true" {
-					commands = append(commands, fmt.Sprintf("source %v/.venv/bin/activate", os.Getenv("GITHUB_WORKSPACE")))
-				}
-				commands = append(commands, step.Value)
-				log.Printf("Running %v for **%v**\n", step.Value, d.ProjectLock.LockId())
-				_, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands)
-				if err != nil {
-					return false, fmt.Errorf("error running command: %v", err)
-				}
-			}
-		}
-		return true, nil
-	}
-	return false, nil
-}
-
-func (d DiggerExecutor) Apply() (bool, error) {
-	var plansFilename *string
-	if d.PlanStorage != nil {
-		var err error
-		plansFilename, err = d.PlanStorage.RetrievePlan(d.localPlanFilePath(), d.storedPlanFilePath())
-		if err != nil {
-			return false, fmt.Errorf("error retrieving plan: %v", err)
-		}
-	}
-
-	locked, err := d.ProjectLock.Lock()
-
-	if err != nil {
-		return false, fmt.Errorf("error locking project: %v", err)
-	}
-
-	if locked {
-		var applySteps []configuration.StepConfig
-
-		// move default values to config init
-		if d.ApplyStage != nil {
-			applySteps = d.ApplyStage.Steps
-		} else {
-			applySteps = []configuration.StepConfig{
-				{
-					Action: "init",
-				},
-				{
-					Action: "apply",
-				},
-			}
-		}
-
-		for _, step := range applySteps {
-			if step.Action == "init" {
-				_, _, err := d.TerraformExecutor.Init(step.ExtraArgs, d.StateEnvVars)
-				if err != nil {
-					return false, fmt.Errorf("error running init: %v", err)
-				}
-			}
-			if step.Action == "apply" {
-				stdout, stderr, err := d.TerraformExecutor.Apply(step.ExtraArgs, plansFilename, d.CommandEnvVars)
-				applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
-				comment := utils.GetTerraformOutputAsCollapsibleComment("Apply for **"+d.ProjectLock.LockId()+"**", applyOutput)
-				commentErr := d.Reporter.Report(comment)
-				if commentErr != nil {
-					fmt.Printf("error publishing comment: %v", err)
-				}
-				if err != nil {
-					commentErr = d.Reporter.Report("Error during applying.")
-					if commentErr != nil {
-						fmt.Printf("error publishing comment: %v", err)
-					}
-					return false, fmt.Errorf("error executing apply: %v", err)
-				}
-			}
-			if step.Action == "run" {
-				var commands []string
-				if os.Getenv("ACTIVATE_VENV") == "true" {
-					commands = append(commands, fmt.Sprintf("source %v/.venv/bin/activate", os.Getenv("GITHUB_WORKSPACE")))
-				}
-				commands = append(commands, step.Value)
-				log.Printf("Running %v for **%v**\n", step.Value, d.ProjectLock.LockId())
-				_, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands)
-				if err != nil {
-					return false, fmt.Errorf("error running command: %v", err)
-				}
-			}
-		}
-		return true, nil
-	} else {
-		return false, nil
-	}
-}
-
-func (d DiggerExecutor) Unlock() error {
-	err := d.ProjectLock.ForceUnlock()
-	if err != nil {
-		return fmt.Errorf("failed to aquire lock: %s, %v", d.ProjectLock.LockId(), err)
-	}
-	if d.PlanStorage != nil {
-		err = d.PlanStorage.DeleteStoredPlan(d.storedPlanFilePath())
-		if err != nil {
-			return fmt.Errorf("failed to delete stored plan file '%v':  %v", d.storedPlanFilePath(), err)
-		}
-	}
-	if err != nil {
-		return fmt.Errorf("failed to delete stored plan file '%v':  %v", d.storedPlanFilePath(), err)
-	}
-	return nil
-}
-
-func (d DiggerExecutor) Lock() error {
-	_, err := d.ProjectLock.Lock()
-	if err != nil {
-		return fmt.Errorf("failed to aquire lock: %s, %v", d.ProjectLock.LockId(), err)
-	}
-	return nil
-}
-
-func cleanupTerraformOutput(nonEmptyOutput bool, planError error, stdout string, stderr string, regexStr *string) string {
-	var errorStr, start string
-
-	// removes output of terraform -version command that terraform-exec executes on every run
-	i := strings.Index(stdout, "Initializing the backend...")
-	if i != -1 {
-		stdout = stdout[i:]
-	}
-	endPos := len(stdout)
-
-	if planError != nil {
-		if stderr != "" {
-			errorStr = stderr
-		} else if stdout != "" {
-			errorStr = stdout
-		}
-		return errorStr
-	} else if nonEmptyOutput {
-		start = "Terraform will perform the following actions:"
-	} else {
-		start = "No changes. Your infrastructure matches the configuration."
-	}
-
-	startPos := strings.Index(stdout, start)
-	if startPos == -1 {
-		startPos = 0
-	}
-
-	if regexStr != nil {
-		regex := regexp.MustCompile(*regexStr)
-		matches := regex.FindStringSubmatch(stdout)
-		if len(matches) > 0 {
-			firstMatch := matches[0]
-			endPos = strings.LastIndex(stdout, firstMatch) + len(firstMatch)
-		}
-	}
-
-	return stdout[startPos:endPos]
-}
-
-func cleanupTerraformApply(nonEmptyPlan bool, planError error, stdout string, stderr string) string {
-	return cleanupTerraformOutput(nonEmptyPlan, planError, stdout, stderr, nil)
-}
-
-func cleanupTerraformPlan(nonEmptyPlan bool, planError error, stdout string, stderr string) string {
-	regex := `───────────.+`
-	return cleanupTerraformOutput(nonEmptyPlan, planError, stdout, stderr, &regex)
-}
-
-type CIName string
-
-const (
-	None      = CIName("")
-	GitHub    = CIName("github")
-	GitLab    = CIName("gitlab")
-	BitBucket = CIName("bitbucket")
-	Azure     = CIName("azure")
-)
-
-func (ci CIName) String() string {
-	return string(ci)
-}
-
-func DetectCI() CIName {
-
-	notEmpty := func(key string) bool {
-		return os.Getenv(key) != ""
-	}
-
-	if notEmpty("GITHUB_ACTIONS") {
-		return GitHub
-	}
-	if notEmpty("GITLAB_CI") {
-		return GitLab
-	}
-	if notEmpty("BITBUCKET_BUILD_NUMBER") {
-		return BitBucket
-	}
-	if notEmpty("AZURE_CI") {
-		return Azure
-	}
-	return None
-
-}
diff --git a/pkg/github/github.go b/pkg/github/github.go
index 6ce4d3b6..0aeac2aa 100644
--- a/pkg/github/github.go
+++ b/pkg/github/github.go
@@ -4,8 +4,8 @@ import (
 	"context"
 	"digger/pkg/ci"
 	"digger/pkg/configuration"
+	dg_models "digger/pkg/core/models"
 	"digger/pkg/github/models"
-	dg_models "digger/pkg/models"
 	"digger/pkg/utils"
 	"encoding/json"
 	"fmt"
@@ -132,7 +132,7 @@ func GetGitHubContext(ghContext string) (*models.Github, error) {
 	return parsedGhContext, nil
 }
 
-func ConvertGithubEventToCommands(event models.Event, impactedProjects []configuration.ProjectConfig, requestedProject *configuration.ProjectConfig, 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) {
@@ -145,7 +145,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 			}
 
 			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,
 				})
@@ -196,7 +197,7 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 		if requestedProject != nil {
 			if len(impactedProjects) > 1 {
 				coversAllImpactedProjects = false
-				runForProjects = []configuration.ProjectConfig{*requestedProject}
+				runForProjects = []configuration.Project{*requestedProject}
 			} else if len(impactedProjects) == 1 && impactedProjects[0].Name != requestedProject.Name {
 				return commandsPerProject, false, fmt.Errorf("requested project %v is not impacted by this PR", requestedProject.Name)
 			}
@@ -211,7 +212,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					}
 
 					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,
 					})
@@ -240,8 +242,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 	}
 }
 
-func ProcessGitHubEvent(ghEvent models.Event, diggerConfig *configuration.DiggerConfig, ciService ci.CIService) ([]configuration.ProjectConfig, *configuration.ProjectConfig, int, error) {
-	var impactedProjects []configuration.ProjectConfig
+func ProcessGitHubEvent(ghEvent models.Event, diggerConfig *configuration.DiggerConfig, ciService ci.CIService) ([]configuration.Project, *configuration.Project, int, error) {
+	var impactedProjects []configuration.Project
 	var prNumber int
 
 	switch ghEvent.(type) {
diff --git a/pkg/gitlab/gitlab.go b/pkg/gitlab/gitlab.go
index d482ae0d..1bdedb26 100644
--- a/pkg/gitlab/gitlab.go
+++ b/pkg/gitlab/gitlab.go
@@ -2,7 +2,7 @@ package gitlab
 
 import (
 	"digger/pkg/configuration"
-	"digger/pkg/models"
+	"digger/pkg/core/models"
 	"digger/pkg/utils"
 	"fmt"
 	"github.com/caarlos0/env/v7"
@@ -83,8 +83,8 @@ func NewGitLabService(token string, gitLabContext *GitLabContext) (*GitLabServic
 	}, nil
 }
 
-func ProcessGitLabEvent(gitlabContext *GitLabContext, diggerConfig *configuration.DiggerConfig, service *GitLabService) ([]configuration.ProjectConfig, *configuration.ProjectConfig, error) {
-	var impactedProjects []configuration.ProjectConfig
+func ProcessGitLabEvent(gitlabContext *GitLabContext, diggerConfig *configuration.DiggerConfig, service *GitLabService) ([]configuration.Project, *configuration.Project, error) {
+	var impactedProjects []configuration.Project
 
 	if gitlabContext.MergeRequestIId == nil {
 		return nil, nil, fmt.Errorf("value for 'Merge Request ID' parameter is not found")
@@ -249,7 +249,7 @@ const (
 	MergeRequestComment = GitLabEventType("merge_request_commented")
 )
 
-func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContext, impactedProjects []configuration.ProjectConfig, requestedProject *configuration.ProjectConfig, 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)
@@ -262,14 +262,16 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex
 			}
 
 			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,
 			})
@@ -282,14 +284,22 @@ 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.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,
 			})
@@ -305,7 +315,7 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex
 		if requestedProject != nil {
 			if len(impactedProjects) > 1 {
 				coversAllImpactedProjects = false
-				runForProjects = []configuration.ProjectConfig{*requestedProject}
+				runForProjects = []configuration.Project{*requestedProject}
 			} else if len(impactedProjects) == 1 && impactedProjects[0].Name != requestedProject.Name {
 				return commandsPerProject, false, fmt.Errorf("requested project %v is not impacted by this PR", requestedProject.Name)
 			}
@@ -327,14 +337,22 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex
 						workspace = workspaceOverride
 					}
 					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,
 					})
diff --git a/pkg/locking/locking.go b/pkg/locking/locking.go
index 6007b4ee..dc29a12f 100644
--- a/pkg/locking/locking.go
+++ b/pkg/locking/locking.go
@@ -5,6 +5,7 @@ import (
 	"digger/pkg/aws/envprovider"
 	"digger/pkg/azure"
 	"digger/pkg/ci"
+	"digger/pkg/core/locking"
 	"digger/pkg/gcp"
 	"errors"
 	"fmt"
@@ -23,19 +24,13 @@ import (
 )
 
 type PullRequestLock struct {
-	InternalLock     Lock
+	InternalLock     locking.Lock
 	CIService        ci.CIService
 	ProjectName      string
 	ProjectNamespace string
 	PrNumber         int
 }
 
-type Lock interface {
-	Lock(transactionId int, resource string) (bool, error)
-	Unlock(resource string) (bool, error)
-	GetLock(resource string) (*int, error)
-}
-
 type NoOpLock struct {
 }
 
@@ -51,13 +46,6 @@ func (noOpLock *NoOpLock) GetLock(resource string) (*int, error) {
 	return nil, nil
 }
 
-type ProjectLock interface {
-	Lock() (bool, error)
-	Unlock() (bool, error)
-	ForceUnlock() error
-	LockId() string
-}
-
 func (projectLock *PullRequestLock) Lock() (bool, error) {
 	lockId := projectLock.LockId()
 	fmt.Printf("Lock %s\n", lockId)
@@ -191,7 +179,7 @@ func (projectLock *PullRequestLock) LockId() string {
 	return projectLock.ProjectNamespace + "#" + projectLock.ProjectName
 }
 
-func GetLock() (Lock, error) {
+func GetLock() (locking.Lock, error) {
 	awsRegion := strings.ToLower(os.Getenv("AWS_REGION"))
 	awsProfile := strings.ToLower(os.Getenv("AWS_PROFILE"))
 	lockProvider := strings.ToLower(os.Getenv("LOCK_PROVIDER"))
diff --git a/pkg/reporting/reporting.go b/pkg/reporting/reporting.go
index 3544af76..4788ab01 100644
--- a/pkg/reporting/reporting.go
+++ b/pkg/reporting/reporting.go
@@ -2,10 +2,6 @@ package reporting
 
 import "digger/pkg/ci"
 
-type Reporter interface {
-	Report(report string) error
-}
-
 type CiReporter struct {
 	CiService ci.CIService
 	PrNumber  int
diff --git a/pkg/storage/plan_storage.go b/pkg/storage/plan_storage.go
index 9bf216a3..987611d5 100644
--- a/pkg/storage/plan_storage.go
+++ b/pkg/storage/plan_storage.go
@@ -15,13 +15,6 @@ import (
 	"github.com/google/go-github/v51/github"
 )
 
-type PlanStorage interface {
-	StorePlan(localPlanFilePath string, storedPlanFilePath string) error
-	RetrievePlan(localPlanFilePath string, storedPlanFilePath string) (*string, error)
-	DeleteStoredPlan(storedPlanFilePath string) error
-	PlanExists(storedPlanFilePath string) (bool, error)
-}
-
 type PlanStorageGcp struct {
 	Client  *storage.Client
 	Bucket  *storage.BucketHandle


Test patch:
diff --git a/cmd/digger/main_test.go b/cmd/digger/main_test.go
index 3c1571cf..56276130 100644
--- a/cmd/digger/main_test.go
+++ b/cmd/digger/main_test.go
@@ -884,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)
@@ -910,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, "")
@@ -934,41 +934,41 @@ func TestGitHubNewPullRequestInMultiEnvProjectContext(t *testing.T) {
 	assert.NoError(t, err)
 	ghEvent := context.Event
 	pullRequestNumber := 11
-	dev := configuration.ProjectConfig{Name: "dev", Dir: "dev", Workflow: "dev"}
-	prod := configuration.ProjectConfig{Name: "prod", Dir: "prod", Workflow: "prod"}
-	workflows := map[string]configuration.WorkflowConfig{
+	dev := configuration.Project{Name: "dev", Dir: "dev", Workflow: "dev"}
+	prod := configuration.Project{Name: "prod", Dir: "prod", Workflow: "prod"}
+	workflows := map[string]configuration.Workflow{
 		"dev": {
-			Plan: &configuration.StageConfig{Steps: []configuration.StepConfig{
+			Plan: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "plan", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
-			Apply: &configuration.StageConfig{Steps: []configuration.StepConfig{
+			Apply: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "apply", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
-			Configuration: &configuration.WorkflowConfigurationConfig{
+			Configuration: &configuration.WorkflowConfiguration{
 				OnPullRequestPushed: []string{"digger plan"},
 				OnPullRequestClosed: []string{"digger unlock"},
 				OnCommitToDefault:   []string{"digger apply"},
 			},
 		},
 		"prod": {
-			Plan: &configuration.StageConfig{Steps: []configuration.StepConfig{
+			Plan: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "plan", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
-			Apply: &configuration.StageConfig{Steps: []configuration.StepConfig{
+			Apply: &configuration.Stage{Steps: []configuration.Step{
 				{Action: "init", ExtraArgs: []string{}},
 				{Action: "apply", ExtraArgs: []string{"-var-file=dev.tfvars"}},
 			}},
-			Configuration: &configuration.WorkflowConfigurationConfig{
+			Configuration: &configuration.WorkflowConfiguration{
 				OnPullRequestPushed: []string{"digger plan"},
 				OnPullRequestClosed: []string{"digger unlock"},
 				OnCommitToDefault:   []string{"digger apply"},
 			},
 		},
 	}
-	projects := []configuration.ProjectConfig{dev, prod}
+	projects := []configuration.Project{dev, prod}
 	diggerConfig := configuration.DiggerConfig{Projects: projects}
 
 	// PullRequestManager Mock
diff --git a/pkg/configuration/digger_config_test.go b/pkg/configuration/digger_config_test.go
index 29ba56c1..9dd42af6 100644
--- a/pkg/configuration/digger_config_test.go
+++ b/pkg/configuration/digger_config_test.go
@@ -134,7 +134,7 @@ workflows:
 
 	dg, err := LoadDiggerConfig(tempDir, &FileSystemDirWalker{})
 	assert.NoError(t, err, "expected error to be nil")
-	assert.Equal(t, StepConfig{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) {
@@ -179,10 +179,10 @@ workflows:
 
 	dg, err := LoadDiggerConfig(tempDir, &FileSystemDirWalker{})
 	assert.NoError(t, err, "expected error to be nil")
-	assert.Equal(t, []EnvVarConfigConfig{
+	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, []EnvVarConfigConfig{
+	assert.Equal(t, []EnvVarConfig{
 		{Name: "TF_VAR_command", Value: "plan"},
 	}, dg.Workflows["myworkflow"].EnvVars.Commands, "parsed struct does not match expected struct")
 }
@@ -209,9 +209,9 @@ workflows:
 
 	dg, err := LoadDiggerConfig(tempDir, &FileSystemDirWalker{})
 	assert.NoError(t, err, "expected error to be nil")
-	assert.Equal(t, StepConfig{Action: "init", ExtraArgs: nil, Shell: ""}, dg.Workflows["default"].Plan.Steps[0], "parsed struct does not match expected struct")
-	assert.Equal(t, StepConfig{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, StepConfig{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 6a4109de..92dbe88f 100644
--- a/pkg/digger/digger_test.go
+++ b/pkg/digger/digger_test.go
@@ -1,7 +1,8 @@
 package digger
 
 import (
-	"digger/pkg/configuration"
+	"digger/pkg/core/execution"
+	"digger/pkg/core/models"
 	"digger/pkg/reporting"
 	"digger/pkg/utils"
 	"sort"
@@ -162,9 +163,9 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 		CiService: prManager,
 		PrNumber:  1,
 	}
-	executor := DiggerExecutor{
-		ApplyStage: &configuration.StageConfig{
-			Steps: []configuration.StepConfig{
+	executor := execution.DiggerExecutor{
+		ApplyStage: &models.Stage{
+			Steps: []models.Step{
 				{
 					Action:    "init",
 					ExtraArgs: nil,
@@ -182,7 +183,7 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 				},
 			},
 		},
-		PlanStage:         &configuration.StageConfig{},
+		PlanStage:         &models.Stage{},
 		CommandRunner:     commandRunner,
 		TerraformExecutor: terraformExecutor,
 		Reporter:          reporter,
@@ -207,10 +208,10 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
 		CiService: prManager,
 		PrNumber:  1,
 	}
-	executor := DiggerExecutor{
-		ApplyStage: &configuration.StageConfig{},
-		PlanStage: &configuration.StageConfig{
-			Steps: []configuration.StepConfig{
+	executor := execution.DiggerExecutor{
+		ApplyStage: &models.Stage{},
+		PlanStage: &models.Stage{
+			Steps: []models.Step{
 				{
 					Action:    "init",
 					ExtraArgs: nil,
diff --git a/pkg/integration/integration_test.go b/pkg/integration/integration_test.go
index cedc96a4..9455214a 100644
--- a/pkg/integration/integration_test.go
+++ b/pkg/integration/integration_test.go
@@ -4,12 +4,12 @@ import (
 	"context"
 	"digger/pkg/aws"
 	"digger/pkg/configuration"
+	"digger/pkg/core/terraform"
 	"digger/pkg/digger"
 	dg_github "digger/pkg/github"
 	"digger/pkg/locking"
 	"digger/pkg/reporting"
 	"digger/pkg/storage"
-	"digger/pkg/terraform"
 	"digger/pkg/utils"
 	"log"
 	"math/rand"