Repository: diggerhq/digger
Pull request #194 (2023-05-02 17:37:01)

chore(deps): update aws-actions/configure-aws-credentials action to v2
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [aws-actions/configure-aws-credentials](https://togithub.com/aws-actions/configure-aws-credentials) | action | major | `v1` -> `v2` |

---

### Release Notes

<details>
<summary>aws-actions/configure-aws-credentials</summary>

### [`v2`](https://togithub.com/aws-actions/configure-aws-credentials/releases/tag/v2)

[Compare Source](https://togithub.com/aws-actions/configure-aws-credentials/compare/v1...v2)

This tag tracks the latest v2.x.x release.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/diggerhq/digger).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS42Ni4zIiwidXBkYXRlZEluVmVyIjoiMzUuNjkuMyIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->



Patch:
diff --git a/.github/renovate.json b/.github/renovate.json
new file mode 100644
index 00000000..7012ba42
--- /dev/null
+++ b/.github/renovate.json
@@ -0,0 +1,11 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+  "extends": [
+    "config:base",
+    ":automergeMinor"
+  ],
+  "labels": ["dependencies"],
+  "vulnerabilityAlerts": {
+    "labels": ["security"]
+  }
+}
diff --git a/.gitignore b/.gitignore
index bcc05484..e8ae7727 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
 venv/
 **/__pycache__/
 __azurite*
+digger
diff --git a/README.md b/README.md
index 6b614072..e73f4dd1 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
   <img width="733" alt="Screenshot 2023-02-28 at 11 25 48" src="https://user-images.githubusercontent.com/1280498/221849642-ae6cb056-5b5b-478f-8cfb-42790e1739e7.png">
 </h1>
 <h2 align="center">
-  <a href="https://join.slack.com/t/diggertalk/shared_invite/zt-1sqckde6q-iRNgEQDjsLW6l5Z~Bwwt6g">Slack</a> |
+  <a href="https://join.slack.com/t/diggertalk/shared_invite/zt-1tocl4w0x-E3RkpPiK7zQkehl8O78g8Q">Slack</a> |
   <a href="https://digger.dev">Website</a> |
-  <a href="https://diggerhq.gitbook.io/digger-docs/">Docs</a>
+  <a href="https://docs.digger.dev/">Docs</a>
 </h2>
 
 Digger runs Terraform jobs in the CI/CD system you already have, such as Github Actions.
diff --git a/action.yml b/action.yml
index 0fa50375..ecaf1082 100644
--- a/action.yml
+++ b/action.yml
@@ -65,16 +65,6 @@ outputs:
 runs:
   using: composite
   steps:
-    - name: Validate General Input Configuration
-      run: |
-        if [[ "${{ inputs.setup-aws }}" == "false" && "${{ inputs.setup-google-cloud }}" == "false" ]]; then
-          echo "Either 'setup-aws' or 'setup-google-cloud' input must be set to 'true'"
-        else
-          exit 0
-        fi
-        exit 1
-      shell: bash
-
     - name: Validate Input Configuration for Google
       run: |
         if [[ -z ${{ toJSON(inputs.google-auth-credentials) }} && -z "${{ inputs.google-workload-identity-provider }}" ]]; then
@@ -116,7 +106,7 @@ runs:
       if: inputs.setup-google-cloud == 'true'
 
     - name: Configure Test AWS credentials
-      uses: aws-actions/configure-aws-credentials@v1
+      uses: aws-actions/configure-aws-credentials@v2
       with:
         aws-access-key-id: ${{ inputs.aws-access-key-id }}
         aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
@@ -124,7 +114,7 @@ runs:
       if: inputs.setup-aws == 'true'
 
     - name: Setup Terraform
-      uses: hashicorp/setup-terraform@v1
+      uses: hashicorp/setup-terraform@v2
       with:
         terraform_version: ${{ inputs.terraform-version }}
         terraform_wrapper: false
diff --git a/go.mod b/go.mod
index a28d51b3..431e1f40 100644
--- a/go.mod
+++ b/go.mod
@@ -6,9 +6,10 @@ require (
 	cloud.google.com/go/storage v1.30.1
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0
 	github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1
-	github.com/aws/aws-sdk-go v1.44.254
+	github.com/aws/aws-sdk-go v1.44.260
 	github.com/bmatcuk/doublestar/v4 v4.6.0
 	github.com/google/go-github/v51 v51.0.0
+	github.com/google/go-github/v52 v52.0.0
 	github.com/google/uuid v1.3.0
 	github.com/stretchr/testify v1.8.2
 	gopkg.in/yaml.v3 v3.0.1
@@ -36,10 +37,10 @@ require (
 	github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	golang.org/x/crypto v0.7.0 // indirect
-	golang.org/x/net v0.8.0 // indirect
-	golang.org/x/oauth2 v0.6.0 // indirect
-	golang.org/x/sys v0.6.0 // indirect
-	golang.org/x/text v0.8.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/text v0.9.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/api v0.114.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
diff --git a/go.sum b/go.sum
index 13a6008a..b5f27116 100644
--- a/go.sum
+++ b/go.sum
@@ -33,6 +33,12 @@ github.com/aws/aws-sdk-go v1.44.219 h1:YOFxTUQZvdRzgwb6XqLFRwNHxoUdKBuunITC7IFhv
 github.com/aws/aws-sdk-go v1.44.219/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/aws/aws-sdk-go v1.44.254 h1:8baW4yal2xGiM/Wm5/ZU10drS8sd+BVjMjPFjJx2ooc=
 github.com/aws/aws-sdk-go v1.44.254/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.255 h1:tOd7OP5V6BeHhANksc7CFB/ILS2mHj3kRhTfZKFnsS0=
+github.com/aws/aws-sdk-go v1.44.255/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.257 h1:HwelXYZZ8c34uFFhgVw3ybu2gB5fkk8KLj2idTvzZb8=
+github.com/aws/aws-sdk-go v1.44.257/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.260 h1:78IJkDpDPXvLXvIkNAKDP/i3z8Vj+3sTAtQYw/v/2o8=
+github.com/aws/aws-sdk-go v1.44.260/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=
@@ -87,6 +93,7 @@ github.com/google/go-github/v50 v50.1.0 h1:hMUpkZjklC5GJ+c3GquSqOP/T4BNsB7XohaPh
 github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA=
 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/v52 v52.0.0/go.mod h1:WJV6VEEUPuMo5pXqqa2ZCZEdbQqua4zAk2MZTIo+m+4=
 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=
@@ -182,9 +189,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.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
 golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
+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/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=
@@ -202,6 +213,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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.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/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=
@@ -213,6 +226,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
diff --git a/pkg/configuration/digger_config.go b/pkg/configuration/digger_config.go
index fa54583b..1bd608cf 100644
--- a/pkg/configuration/digger_config.go
+++ b/pkg/configuration/digger_config.go
@@ -25,6 +25,12 @@ type DiggerConfigYaml struct {
 	GenerateProjectsConfig *GenerateProjectsConfig `yaml:"generate_projects"`
 }
 
+type EnvVarConfig struct {
+	Name      string `yaml:"name"`
+	ValueFrom string `yaml:"value_from"`
+	Value     string `yaml:"value"`
+}
+
 type DiggerConfig struct {
 	Projects         []Project
 	AutoMerge        bool
@@ -50,11 +56,17 @@ type Stage struct {
 }
 
 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)
 }
@@ -125,6 +137,10 @@ func (w *Workflow) UnmarshalYAML(unmarshal func(interface{}) error) error {
 				},
 			},
 		},
+		EnvVars: EnvVars{
+			State:    []EnvVarConfig{},
+			Commands: []EnvVarConfig{},
+		},
 	}
 	if err := unmarshal(&raw); err != nil {
 		return err
diff --git a/pkg/digger/digger.go b/pkg/digger/digger.go
index 39d69387..a9dca538 100644
--- a/pkg/digger/digger.go
+++ b/pkg/digger/digger.go
@@ -73,17 +73,21 @@ func RunCommandsPerProject(commandsPerProject []ProjectCommand, repoOwner string
 			}
 
 			var terraformExecutor terraform.TerraformExecutor
-
+			projectPath := path.Join(workingDir, projectCommands.ProjectDir)
 			if projectCommands.Terragrunt {
-				terraformExecutor = terraform.Terragrunt{WorkingDir: path.Join(workingDir, projectCommands.ProjectDir)}
+				terraformExecutor = terraform.Terragrunt{WorkingDir: projectPath}
 			} else {
-				terraformExecutor = terraform.Terraform{WorkingDir: path.Join(workingDir, projectCommands.ProjectDir), Workspace: projectCommands.ProjectWorkspace}
+				terraformExecutor = terraform.Terraform{WorkingDir: projectPath, Workspace: projectCommands.ProjectWorkspace}
 			}
 
 			commandRunner := CommandRunner{}
-
 			diggerExecutor := DiggerExecutor{
+				repoOwner,
+				repoName,
 				projectCommands.ProjectName,
+				projectPath,
+				projectCommands.StateEnvVars,
+				projectCommands.CommandEnvVars,
 				projectCommands.ApplyStage,
 				projectCommands.PlanStage,
 				commandRunner,
@@ -186,6 +190,8 @@ type ProjectCommand struct {
 	Commands         []string
 	ApplyStage       *configuration.Stage
 	PlanStage        *configuration.Stage
+	StateEnvVars     map[string]string
+	CommandEnvVars   map[string]string
 }
 
 func ConvertGithubEventToCommands(event models.Event, impactedProjects []configuration.Project, workflows map[string]configuration.Workflow) ([]ProjectCommand, error) {
@@ -199,6 +205,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 			if !ok {
 				workflow = *defaultWorkflow()
 			}
+
+			stateEnvVars, commandEnvVars := collectEnvVars(workflow.EnvVars)
+
 			if event.Action == "closed" && event.PullRequest.Merged && event.PullRequest.Base.Ref == event.Repository.DefaultBranch {
 				commandsPerProject = append(commandsPerProject, ProjectCommand{
 					ProjectName:      project.Name,
@@ -208,6 +217,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					Commands:         workflow.Configuration.OnCommitToDefault,
 					ApplyStage:       workflow.Apply,
 					PlanStage:        workflow.Plan,
+					CommandEnvVars:   commandEnvVars,
+					StateEnvVars:     stateEnvVars,
 				})
 			} else if event.Action == "opened" || event.Action == "reopened" || event.Action == "synchronize" {
 				commandsPerProject = append(commandsPerProject, ProjectCommand{
@@ -218,6 +229,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					Commands:         workflow.Configuration.OnPullRequestPushed,
 					ApplyStage:       workflow.Apply,
 					PlanStage:        workflow.Plan,
+					CommandEnvVars:   commandEnvVars,
+					StateEnvVars:     stateEnvVars,
 				})
 			} else if event.Action == "closed" {
 				commandsPerProject = append(commandsPerProject, ProjectCommand{
@@ -228,6 +241,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					Commands:         workflow.Configuration.OnPullRequestClosed,
 					ApplyStage:       workflow.Apply,
 					PlanStage:        workflow.Plan,
+					CommandEnvVars:   commandEnvVars,
+					StateEnvVars:     stateEnvVars,
 				})
 			}
 		}
@@ -243,6 +258,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 					if !ok {
 						workflow = *defaultWorkflow()
 					}
+
+					stateEnvVars, commandEnvVars := collectEnvVars(workflow.EnvVars)
+
 					workspace := project.Workspace
 					workspaceOverride, err := parseWorkspace(event.Comment.Body)
 					if err != nil {
@@ -259,6 +277,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 						Commands:         []string{command},
 						ApplyStage:       workflow.Apply,
 						PlanStage:        workflow.Plan,
+						CommandEnvVars:   commandEnvVars,
+						StateEnvVars:     stateEnvVars,
 					})
 				}
 			}
@@ -269,6 +289,29 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 	}
 }
 
+func collectEnvVars(envs configuration.EnvVars) (map[string]string, map[string]string) {
+	stateEnvVars := map[string]string{}
+
+	for _, envvar := range envs.State {
+		if envvar.Value != "" {
+			stateEnvVars[envvar.Name] = envvar.Value
+		} else if envvar.ValueFrom != "" {
+			stateEnvVars[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
+}
+
 func parseWorkspace(comment string) (string, error) {
 	re := regexp.MustCompile(`-w(?:\s+(\S+)|$)`)
 	matches := re.FindAllStringSubmatch(comment, -1)
@@ -298,7 +341,12 @@ func parseProjectName(comment string) string {
 }
 
 type DiggerExecutor struct {
+	repoOwner         string
+	repoName          string
 	projectName       string
+	projectPath       string
+	stateEnvVars      map[string]string
+	commandEnvVars    map[string]string
 	applyStage        *configuration.Stage
 	planStage         *configuration.Stage
 	commandRunner     CommandRun
@@ -336,11 +384,18 @@ func (c CommandRunner) Run(command string) (string, string, error) {
 }
 
 func (d DiggerExecutor) planFileName() string {
-	return d.projectName + ".tfplan"
+	return d.repoName + "#" + d.projectName + ".tfplan"
 }
 
-func (d DiggerExecutor) Plan(prNumber int) error {
+func (d DiggerExecutor) localPlanFilePath() string {
+	return path.Join(d.projectPath, d.planFileName())
+}
 
+func (d DiggerExecutor) storedPlanFilePath() string {
+	return path.Join(d.repoOwner, d.planFileName())
+}
+
+func (d DiggerExecutor) Plan(prNumber int) error {
 	res, err := d.lock.Lock(prNumber)
 	if err != nil {
 		return fmt.Errorf("error locking project: %v", err)
@@ -363,7 +418,7 @@ func (d DiggerExecutor) Plan(prNumber int) error {
 		}
 		for _, step := range planSteps {
 			if step.Action == "init" {
-				_, _, err := d.terraformExecutor.Init(step.ExtraArgs)
+				_, _, err := d.terraformExecutor.Init(step.ExtraArgs, d.stateEnvVars)
 				if err != nil {
 					return fmt.Errorf("error running init: %v", err)
 				}
@@ -371,12 +426,12 @@ func (d DiggerExecutor) Plan(prNumber int) error {
 			if step.Action == "plan" {
 				planArgs := []string{"-out", d.planFileName()}
 				planArgs = append(planArgs, step.ExtraArgs...)
-				isNonEmptyPlan, stdout, stderr, err := d.terraformExecutor.Plan(planArgs)
+				isNonEmptyPlan, stdout, stderr, err := d.terraformExecutor.Plan(planArgs, d.commandEnvVars)
 				if err != nil {
 					return fmt.Errorf("error executing plan: %v", err)
 				}
 				if d.planStorage != nil {
-					err = d.planStorage.StorePlan(d.planFileName())
+					err = d.planStorage.StorePlan(d.localPlanFilePath(), d.storedPlanFilePath())
 					if err != nil {
 						return fmt.Errorf("error storing plan: %v", err)
 					}
@@ -401,7 +456,7 @@ func (d DiggerExecutor) Apply(prNumber int) error {
 	var plansFilename *string
 	if d.planStorage != nil {
 		var err error
-		plansFilename, err = d.planStorage.RetrievePlan(d.planFileName())
+		plansFilename, err = d.planStorage.RetrievePlan(d.localPlanFilePath(), d.storedPlanFilePath())
 		if err != nil {
 			return fmt.Errorf("error retrieving plan: %v", err)
 		}
@@ -435,23 +490,18 @@ func (d DiggerExecutor) Apply(prNumber int) error {
 
 			for _, step := range applySteps {
 				if step.Action == "init" {
-					_, _, err := d.terraformExecutor.Init(step.ExtraArgs)
+					_, _, err := d.terraformExecutor.Init(step.ExtraArgs, d.stateEnvVars)
 					if err != nil {
 						return fmt.Errorf("error running init: %v", err)
 					}
 				}
 				if step.Action == "apply" {
-					stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs, plansFilename)
+					stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs, plansFilename, d.commandEnvVars)
 					applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
 					comment := utils.GetTerraformOutputAsCollapsibleComment("Apply for **"+d.lock.LockId()+"**", applyOutput)
 					d.prManager.PublishComment(prNumber, comment)
-					if err == nil {
-						_, err := d.lock.Unlock(prNumber)
-						if err != nil {
-							return fmt.Errorf("error unlocking project: %v", err)
-						}
-					} else {
-						d.prManager.PublishComment(prNumber, "Error during applying. Project lock will persist")
+					if err != nil {
+						d.prManager.PublishComment(prNumber, "Error during applying.")
 					}
 				}
 				if step.Action == "run" {
@@ -472,6 +522,10 @@ func (d DiggerExecutor) Unlock(prNumber int) error {
 	if err != nil {
 		return fmt.Errorf("failed to aquire lock: %s, %v", d.lock.LockId(), err)
 	}
+	err = d.planStorage.DeleteStoredPlan(d.storedPlanFilePath())
+	if err != nil {
+		return fmt.Errorf("failed to delete stored plan file '%v':  %v", d.storedPlanFilePath(), err)
+	}
 	return nil
 }
 
diff --git a/pkg/gcp/gcp_lock.go b/pkg/gcp/gcp_lock.go
index de08b30e..57843e16 100644
--- a/pkg/gcp/gcp_lock.go
+++ b/pkg/gcp/gcp_lock.go
@@ -1,12 +1,13 @@
 package gcp
 
 import (
-	"cloud.google.com/go/storage"
 	"context"
 	"fmt"
 	"log"
 	"strconv"
 	"time"
+
+	"cloud.google.com/go/storage"
 )
 
 type GoogleStorageLock struct {
@@ -16,14 +17,6 @@ type GoogleStorageLock struct {
 }
 
 func (googleLock *GoogleStorageLock) Lock(transactionId int, resource string) (bool, error) {
-	existingLockTransactionId, err := googleLock.GetLock(resource)
-	if err != nil {
-		fmt.Printf("failed to get lock: %v\n", err)
-	}
-	if existingLockTransactionId != nil {
-		return false, nil
-	}
-
 	now := time.Now().Format(time.RFC3339)
 	fileName := resource
 
diff --git a/pkg/github/github.go b/pkg/github/github.go
index 38965e00..026ade5d 100644
--- a/pkg/github/github.go
+++ b/pkg/github/github.go
@@ -28,6 +28,7 @@ type PullRequestManager interface {
 	GetCombinedPullRequestStatus(prNumber int) (string, error)
 	MergePullRequest(prNumber int) error
 	IsMergeable(prNumber int) (bool, string, error)
+	IsClosed(prNumber int) (bool, error)
 }
 
 func (svc *GithubService) GetChangedFiles(prNumber int) ([]string, error) {
@@ -100,3 +101,12 @@ func (svc *GithubService) IsMergeable(prNumber int) (bool, string, error) {
 
 	return pr.GetMergeable(), pr.GetMergeableState(), nil
 }
+
+func (svc *GithubService) IsClosed(prNumber int) (bool, error) {
+	pr, _, err := svc.Client.PullRequests.Get(context.Background(), svc.Owner, svc.RepoName, prNumber)
+	if err != nil {
+		log.Fatalf("error getting pull request: %v", err)
+	}
+
+	return pr.GetState() == "closed", nil
+}
diff --git a/pkg/github/pullrequestmanager_mock.go b/pkg/github/pullrequestmanager_mock.go
index 4484b34b..5b299956 100644
--- a/pkg/github/pullrequestmanager_mock.go
+++ b/pkg/github/pullrequestmanager_mock.go
@@ -37,3 +37,8 @@ func (mockGithubPullrequestManager *MockGithubPullrequestManager) DownloadLatest
 	mockGithubPullrequestManager.commands = append(mockGithubPullrequestManager.commands, "DownloadLatestPlans")
 	return "", nil
 }
+
+func (mockGithubPullrequestManager *MockGithubPullrequestManager) IsClosed(prNumber int) (bool, error) {
+	mockGithubPullrequestManager.commands = append(mockGithubPullrequestManager.commands, "IsClosed")
+	return false, nil
+}
diff --git a/pkg/terraform/tf.go b/pkg/terraform/tf.go
index 4b1d23e0..78762679 100644
--- a/pkg/terraform/tf.go
+++ b/pkg/terraform/tf.go
@@ -10,9 +10,9 @@ import (
 )
 
 type TerraformExecutor interface {
-	Init([]string) (string, string, error)
-	Apply([]string, *string) (string, string, error)
-	Plan([]string) (bool, string, string, error)
+	Init([]string, map[string]string) (string, string, error)
+	Apply([]string, *string, map[string]string) (string, string, error)
+	Plan([]string, map[string]string) (bool, string, string, error)
 }
 
 type Terragrunt struct {
@@ -24,27 +24,27 @@ type Terraform struct {
 	Workspace  string
 }
 
-func (terragrunt Terragrunt) Init(params []string) (string, string, error) {
-	return terragrunt.runTerragruntCommand("init", params...)
+func (terragrunt Terragrunt) Init(params []string, envs map[string]string) (string, string, error) {
+	return terragrunt.runTerragruntCommand("init", envs, params...)
 
 }
 
-func (terragrunt Terragrunt) Apply(params []string, plan *string) (string, string, error) {
+func (terragrunt Terragrunt) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
 	params = append(params, "--auto-approve")
 	params = append(params, "--terragrunt-non-interactive")
 	if plan != nil {
 		params = append(params, *plan)
 	}
-	stdout, stderr, err := terragrunt.runTerragruntCommand("apply", params...)
+	stdout, stderr, err := terragrunt.runTerragruntCommand("apply", envs, params...)
 	return stdout, stderr, err
 }
 
-func (terragrunt Terragrunt) Plan(params []string) (bool, string, string, error) {
-	stdout, stderr, err := terragrunt.runTerragruntCommand("plan", params...)
+func (terragrunt Terragrunt) Plan(params []string, envs map[string]string) (bool, string, string, error) {
+	stdout, stderr, err := terragrunt.runTerragruntCommand("plan", envs, params...)
 	return true, stdout, stderr, err
 }
 
-func (terragrunt Terragrunt) runTerragruntCommand(command string, arg ...string) (string, string, error) {
+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)
@@ -53,6 +53,11 @@ func (terragrunt Terragrunt) runTerragruntCommand(command string, arg ...string)
 	env := os.Environ()
 	env = append(env, "TF_CLI_ARGS=-no-color")
 	env = append(env, "TF_IN_AUTOMATION=true")
+
+	for k, v := range envs {
+		env = append(env, fmt.Sprintf("%s=%s", k, v))
+	}
+
 	cmd.Env = env
 
 	var stdout, stderr bytes.Buffer
@@ -69,23 +74,23 @@ func (terragrunt Terragrunt) runTerragruntCommand(command string, arg ...string)
 	return stdout.String(), stderr.String(), err
 }
 
-func (tf Terraform) Init(params []string) (string, string, error) {
+func (tf Terraform) Init(params []string, envs map[string]string) (string, string, error) {
 	params = append(params, "-upgrade=true")
 	params = append(params, "-input=false")
 	params = append(params, "-no-color")
-	stdout, stderr, _, err := tf.runTerraformCommand("init", params...)
+	stdout, stderr, _, err := tf.runTerraformCommand("init", envs, params...)
 	return stdout, stderr, err
 }
 
-func (tf Terraform) Apply(params []string, plan *string) (string, string, error) {
-	workspace, _, _, err := tf.runTerraformCommand("workspace", "show")
+func (tf Terraform) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
+	workspace, _, _, err := tf.runTerraformCommand("workspace", envs, "show")
 
 	if err != nil {
 		return "", "", err
 	}
 
 	if strings.TrimSpace(workspace) != tf.Workspace {
-		_, _, _, err = tf.runTerraformCommand("workspace", "new", tf.Workspace)
+		_, _, _, err = tf.runTerraformCommand("workspace", envs, "new", tf.Workspace)
 		if err != nil {
 			return "", "", err
 		}
@@ -94,18 +99,17 @@ func (tf Terraform) Apply(params []string, plan *string) (string, string, error)
 	if plan != nil {
 		params = append(params, *plan)
 	}
-	stdout, stderr, _, err := tf.runTerraformCommand("apply", params...)
+	stdout, stderr, _, err := tf.runTerraformCommand("apply", envs, params...)
 	if err != nil {
 		return "", "", err
 	}
 	return stdout, stderr, nil
 }
 
-func (tf Terraform) runTerraformCommand(command string, arg ...string) (string, string, int, error) {
+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 = append(args, arg...)
-	fmt.Printf("Running terraform %v", args)
 
 	var stdout, stderr bytes.Buffer
 	mwout := io.MultiWriter(os.Stdout, &stdout)
@@ -113,6 +117,11 @@ func (tf Terraform) runTerraformCommand(command string, arg ...string) (string,
 
 	cmd := exec.Command("terraform", args...)
 
+	env := os.Environ()
+	for k, v := range envs {
+		env = append(env, fmt.Sprintf("%s=%s", k, v))
+	}
+	cmd.Env = env
 	cmd.Stdout = mwout
 	cmd.Stderr = mwerr
 
@@ -145,20 +154,20 @@ func (sw *StdWriter) GetString() string {
 	return s
 }
 
-func (tf Terraform) Plan(params []string) (bool, string, string, error) {
-	workspace, _, _, err := tf.runTerraformCommand("workspace", "show")
+func (tf Terraform) Plan(params []string, envs map[string]string) (bool, string, string, error) {
+	workspace, _, _, err := tf.runTerraformCommand("workspace", envs, "show")
 
 	if err != nil {
 		return false, "", "", err
 	}
 	if strings.TrimSpace(workspace) != tf.Workspace {
-		_, _, _, err = tf.runTerraformCommand("workspace", "new", tf.Workspace)
+		_, _, _, err = tf.runTerraformCommand("workspace", envs, "new", tf.Workspace)
 		if err != nil {
 			return false, "", "", err
 		}
 	}
 	params = append(append(params, "-input=false"), "-no-color")
-	stdout, stderr, statusCode, err := tf.runTerraformCommand("plan", params...)
+	stdout, stderr, statusCode, err := tf.runTerraformCommand("plan", envs, params...)
 	if err != nil {
 		return false, "", "", err
 	}
diff --git a/pkg/utils/locking.go b/pkg/utils/locking.go
index 9e7233c5..3d7124af 100644
--- a/pkg/utils/locking.go
+++ b/pkg/utils/locking.go
@@ -8,12 +8,13 @@ import (
 	"digger/pkg/github"
 	"errors"
 	"fmt"
-	"github.com/aws/aws-sdk-go/service/sts"
 	"log"
 	"os"
 	"strconv"
 	"strings"
 
+	"github.com/aws/aws-sdk-go/service/sts"
+
 	"cloud.google.com/go/storage"
 	awssdk "github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/credentials"
@@ -45,23 +46,32 @@ type ProjectLock interface {
 func (projectLock *ProjectLockImpl) Lock(prNumber int) (bool, error) {
 	lockId := projectLock.LockId()
 	fmt.Printf("Lock %s\n", lockId)
-	transactionId, err := projectLock.InternalLock.GetLock(lockId)
-	var transactionIdStr string
+
+	noHangingLocks, err := projectLock.verifyNoHangingLocks(prNumber)
 
 	if err != nil {
 		return false, err
 	}
 
-	if transactionId != nil {
-		transactionIdStr := strconv.Itoa(*transactionId)
-		if *transactionId != prNumber {
-			comment := "Project " + projectLock.projectId() + " locked by another PR #" + transactionIdStr + "(failed to acquire lock " + projectLock.ProjectName + "). The locking plan must be applied or discarded before future plans can execute"
+	if !noHangingLocks {
+		return false, nil
+	}
+
+	existingLockTransactionId, err := projectLock.InternalLock.GetLock(lockId)
+	if err != nil {
+		fmt.Printf("failed to get lock: %v\n", err)
+		return false, err
+	}
+	if existingLockTransactionId != nil {
+		if *existingLockTransactionId == prNumber {
+			return true, nil
+		} else {
+			transactionIdStr := strconv.Itoa(*existingLockTransactionId)
+			comment := "Project " + projectLock.projectId() + " locked by another PR #" + transactionIdStr + " (failed to acquire lock " + projectLock.RepoName + "). The locking plan must be applied or discarded before future plans can execute"
 			projectLock.PrManager.PublishComment(prNumber, comment)
 			return false, nil
 		}
-		return true, nil
 	}
-
 	lockAcquired, err := projectLock.InternalLock.Lock(prNumber, lockId)
 	if err != nil {
 		return false, err
@@ -71,16 +81,40 @@ func (projectLock *ProjectLockImpl) Lock(prNumber int) (bool, error) {
 		comment := "Project " + projectLock.projectId() + " has been locked by PR #" + strconv.Itoa(prNumber)
 		projectLock.PrManager.PublishComment(prNumber, comment)
 		println("project " + projectLock.projectId() + " locked successfully. PR # " + strconv.Itoa(prNumber))
-		return true, nil
+
 	}
+	return lockAcquired, nil
+}
 
-	transactionId, _ = projectLock.InternalLock.GetLock(lockId)
-	transactionIdStr = strconv.Itoa(*transactionId)
+func (projectLock *ProjectLockImpl) verifyNoHangingLocks(prNumber int) (bool, error) {
+	lockId := projectLock.LockId()
+	transactionId, err := projectLock.InternalLock.GetLock(lockId)
 
-	comment := "Project " + projectLock.projectId() + " locked by another PR #" + transactionIdStr + " (failed to acquire lock " + projectLock.RepoName + "). The locking plan must be applied or discarded before future plans can execute"
-	projectLock.PrManager.PublishComment(prNumber, comment)
-	println(comment)
-	return false, nil
+	if err != nil {
+		return false, err
+	}
+
+	if transactionId != nil {
+		if *transactionId != prNumber {
+			isPrClosed, err := projectLock.PrManager.IsClosed(*transactionId)
+			if err != nil {
+				return false, fmt.Errorf("failed to check if PR holding a lock is closed: %w", err)
+			}
+			if isPrClosed {
+				_, err := projectLock.InternalLock.Unlock(lockId)
+				if err != nil {
+					return false, fmt.Errorf("failed to unlock a lock acquired by closed PR %v: %w", transactionId, err)
+				}
+				return true, nil
+			}
+			transactionIdStr := strconv.Itoa(*transactionId)
+			comment := "Project " + projectLock.projectId() + " locked by another PR #" + transactionIdStr + "(failed to acquire lock " + projectLock.ProjectName + "). The locking plan must be applied or discarded before future plans can execute"
+			projectLock.PrManager.PublishComment(prNumber, comment)
+			return false, nil
+		}
+		return true, nil
+	}
+	return true, nil
 }
 
 func (projectLock *ProjectLockImpl) Unlock(prNumber int) (bool, error) {
diff --git a/pkg/utils/mocks.go b/pkg/utils/mocks.go
index 026b2132..4f986c48 100644
--- a/pkg/utils/mocks.go
+++ b/pkg/utils/mocks.go
@@ -70,13 +70,21 @@ func (t MockPullRequestManager) DownloadLatestPlans(prNumber int) (string, error
 	return "", nil
 }
 
+func (t MockPullRequestManager) IsClosed(prNumber int) (bool, error) {
+	return false, nil
+}
+
 type MockPlanStorage struct {
 }
 
-func (t MockPlanStorage) StorePlan(planfile string) error {
+func (t MockPlanStorage) StorePlan(localPlanFilePath string, storedPlanFilePath string) error {
 	return nil
 }
 
-func (t MockPlanStorage) RetrievePlan(planfile string) (*string, error) {
+func (t MockPlanStorage) RetrievePlan(localPlanFilePath string, storedPlanFilePath string) (*string, error) {
 	return nil, nil
 }
+
+func (t MockPlanStorage) DeleteStoredPlan(storedPlanFilePath string) error {
+	return nil
+}
diff --git a/pkg/utils/plan_storage.go b/pkg/utils/plan_storage.go
index f51ed5b7..a71e500d 100644
--- a/pkg/utils/plan_storage.go
+++ b/pkg/utils/plan_storage.go
@@ -1,20 +1,22 @@
 package utils
 
 import (
-	"cloud.google.com/go/storage"
 	"context"
 	"fmt"
-	"github.com/google/go-github/v51/github"
 	"io"
 	"net/http"
 	"net/url"
 	"os"
 	"strconv"
+
+	"cloud.google.com/go/storage"
+	"github.com/google/go-github/v51/github"
 )
 
 type PlanStorage interface {
-	StorePlan(planFileName string) error
-	RetrievePlan(planFileName string) (*string, error)
+	StorePlan(localPlanFilePath string, storedPlanFilePath string) error
+	RetrievePlan(localPlanFilePath string, storedPlanFilePath string) (*string, error)
+	DeleteStoredPlan(storedPlanFilePath string) error
 }
 
 type PlanStorageGcp struct {
@@ -31,14 +33,14 @@ type GithubPlanStorage struct {
 	ZipManager        Zipper
 }
 
-func (psg *PlanStorageGcp) StorePlan(planFileName string) error {
-	file, err := os.Open(planFileName)
+func (psg *PlanStorageGcp) StorePlan(localPlanFilePath string, storedPlanFilePath string) error {
+	file, err := os.Open(localPlanFilePath)
 	if err != nil {
 		return fmt.Errorf("unable to open file: %v", err)
 	}
 	defer file.Close()
 
-	obj := psg.Bucket.Object(planFileName)
+	obj := psg.Bucket.Object(storedPlanFilePath)
 	wc := obj.NewWriter(psg.Context)
 
 	if _, err = io.Copy(wc, file); err != nil {
@@ -53,15 +55,15 @@ func (psg *PlanStorageGcp) StorePlan(planFileName string) error {
 	return nil
 }
 
-func (psg *PlanStorageGcp) RetrievePlan(planFileName string) (*string, error) {
-	obj := psg.Bucket.Object(planFileName)
+func (psg *PlanStorageGcp) RetrievePlan(localPlanFilePath string, storedPlanFilePath string) (*string, error) {
+	obj := psg.Bucket.Object(storedPlanFilePath)
 	rc, err := obj.NewReader(psg.Context)
 	if err != nil {
 		return nil, fmt.Errorf("unable to read data from bucket: %v", err)
 	}
 	defer rc.Close()
 
-	file, err := os.Create(planFileName)
+	file, err := os.Create(localPlanFilePath)
 	if err != nil {
 		return nil, fmt.Errorf("unable to create file: %v", err)
 	}
@@ -71,15 +73,25 @@ func (psg *PlanStorageGcp) RetrievePlan(planFileName string) (*string, error) {
 		return nil, fmt.Errorf("unable to write data to file: %v", err)
 	}
 
-	return &planFileName, nil
+	return &localPlanFilePath, nil
+}
+
+func (psg *PlanStorageGcp) DeleteStoredPlan(storedPlanFilePath string) error {
+	obj := psg.Bucket.Object(storedPlanFilePath)
+	err := obj.Delete(psg.Context)
+
+	if err != nil {
+		return fmt.Errorf("unable to delete file '%v' from bucket: %v", storedPlanFilePath, err)
+	}
+	return nil
 }
 
-func (gps *GithubPlanStorage) StorePlan(planFileName string) error {
-	_ = fmt.Sprintf("Skipping storing plan %s. It should be achieved using actions/upload-artifact@v3", planFileName)
+func (gps *GithubPlanStorage) StorePlan(localPlanFilePath string, storedPlanFilePath string) error {
+	_ = fmt.Sprintf("Skipping storing plan %s. It should be achieved using actions/upload-artifact@v3", localPlanFilePath)
 	return nil
 }
 
-func (gps *GithubPlanStorage) RetrievePlan(planFileName string) (*string, error) {
+func (gps *GithubPlanStorage) RetrievePlan(localPlanFilePath string, storedPlanFilePath string) (*string, error) {
 	plansFilename, err := gps.DownloadLatestPlans()
 
 	if err != nil {
@@ -90,7 +102,7 @@ func (gps *GithubPlanStorage) RetrievePlan(planFileName string) (*string, error)
 		return nil, fmt.Errorf("no plans found for this PR")
 	}
 
-	plansFilename, err = gps.ZipManager.GetFileFromZip(plansFilename, planFileName)
+	plansFilename, err = gps.ZipManager.GetFileFromZip(plansFilename, storedPlanFilePath)
 
 	if err != nil {
 		return nil, fmt.Errorf("error extracting plan: %v", err)
@@ -98,6 +110,10 @@ func (gps *GithubPlanStorage) RetrievePlan(planFileName string) (*string, error)
 	return &plansFilename, nil
 }
 
+func (gps *GithubPlanStorage) DeleteStoredPlan(storedPlanFilePath string) error {
+	return nil
+}
+
 func (gps *GithubPlanStorage) DownloadLatestPlans() (string, error) {
 	artifacts, _, err := gps.Client.Actions.ListArtifacts(context.Background(), gps.Owner, gps.RepoName, &github.ListOptions{
 		PerPage: 100,
diff --git a/renovate.json b/renovate.json
deleted file mode 100644
index 39a2b6e9..00000000
--- a/renovate.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
-  "extends": [
-    "config:base"
-  ]
-}


Test patch:
diff --git a/pkg/configuration/digger_config_test.go b/pkg/configuration/digger_config_test.go
index 55930eb4..b5b2a3bf 100644
--- a/pkg/configuration/digger_config_test.go
+++ b/pkg/configuration/digger_config_test.go
@@ -116,6 +116,56 @@ workflows:
 	assert.Equal(t, Step{Action: "run", Value: "echo \"hello\""}, dg.Workflows["myworkflow"].Plan.Steps[0], "parsed struct does not match expected struct")
 }
 
+func TestEnvVarsConfiguration(t *testing.T) {
+	tempDir, teardown := setUp()
+	defer teardown()
+
+	diggerCfg := `
+projects:
+- name: dev
+  branch: /main/
+  dir: .
+  workspace: default
+  terragrunt: false
+  workflow: myworkflow
+workflows:
+  myworkflow:
+    plan:
+      steps:
+      - init:
+          extra_args: ["-lock=false"]
+      - plan:
+          extra_args: ["-lock=false"]
+      - run: echo "hello"
+    apply:
+      steps:
+      - apply:
+          extra_args: ["-lock=false"]
+    workflow_configuration:
+      on_pull_request_pushed: [digger plan]
+      on_pull_request_closed: [digger unlock]
+      on_commit_to_default: [digger apply]
+    env_vars:
+      state:
+      - name: TF_VAR_state
+        value: s3://mybucket/terraform.tfstate
+      commands:
+      - name: TF_VAR_command
+        value: plan
+`
+	deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg)
+	defer deleteFile()
+
+	dg, err := NewDiggerConfig(tempDir, &FileSystemDirWalker{})
+	assert.NoError(t, err, "expected error to be nil")
+	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, []EnvVarConfig{
+		{Name: "TF_VAR_command", Value: "plan"},
+	}, dg.Workflows["myworkflow"].EnvVars.Commands, "parsed struct does not match expected struct")
+}
+
 func TestDefaultValuesForWorkflowConfiguration(t *testing.T) {
 	tempDir, teardown := setUp()
 	defer teardown()
diff --git a/pkg/digger/digger_test.go b/pkg/digger/digger_test.go
index d5d026fa..56451603 100644
--- a/pkg/digger/digger_test.go
+++ b/pkg/digger/digger_test.go
@@ -30,12 +30,12 @@ type MockTerraformExecutor struct {
 	Commands []RunInfo
 }
 
-func (m *MockTerraformExecutor) Init(params []string) (string, string, error) {
+func (m *MockTerraformExecutor) Init(params []string, envs map[string]string) (string, string, error) {
 	m.Commands = append(m.Commands, RunInfo{"Init", strings.Join(params, " "), time.Now()})
 	return "", "", nil
 }
 
-func (m *MockTerraformExecutor) Apply(params []string, plan *string) (string, string, error) {
+func (m *MockTerraformExecutor) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
 	if plan != nil {
 		params = append(params, *plan)
 	}
@@ -43,7 +43,7 @@ func (m *MockTerraformExecutor) Apply(params []string, plan *string) (string, st
 	return "", "", nil
 }
 
-func (m *MockTerraformExecutor) Plan(params []string) (bool, string, string, error) {
+func (m *MockTerraformExecutor) Plan(params []string, envs map[string]string) (bool, string, string, error) {
 	m.Commands = append(m.Commands, RunInfo{"Plan", strings.Join(params, " "), time.Now()})
 	return true, "", "", nil
 }
@@ -86,6 +86,11 @@ func (m *MockPRManager) DownloadLatestPlans(prNumber int) (string, error) {
 	return "plan", nil
 }
 
+func (m *MockPRManager) IsClosed(prNumber int) (bool, error) {
+	m.Commands = append(m.Commands, RunInfo{"IsClosed", strconv.Itoa(prNumber), time.Now()})
+	return false, nil
+}
+
 type MockProjectLock struct {
 	Commands []RunInfo
 }
@@ -123,16 +128,21 @@ type MockPlanStorage struct {
 	Commands []RunInfo
 }
 
-func (m *MockPlanStorage) StorePlan(planFileName string) error {
-	m.Commands = append(m.Commands, RunInfo{"StorePlan", planFileName, time.Now()})
+func (m *MockPlanStorage) StorePlan(localPlanFilePath string, storedPlanFilePath string) error {
+	m.Commands = append(m.Commands, RunInfo{"StorePlan", localPlanFilePath, time.Now()})
 	return nil
 }
 
-func (m *MockPlanStorage) RetrievePlan(planFileName string) (*string, error) {
-	m.Commands = append(m.Commands, RunInfo{"RetrievePlan", planFileName, time.Now()})
+func (m *MockPlanStorage) RetrievePlan(localPlanFilePath string, storedPlanFilePath string) (*string, error) {
+	m.Commands = append(m.Commands, RunInfo{"RetrievePlan", localPlanFilePath, time.Now()})
 	return nil, nil
 }
 
+func (m *MockPlanStorage) DeleteStoredPlan(storedPlanFilePath string) error {
+	m.Commands = append(m.Commands, RunInfo{"DeleteStoredPlan", storedPlanFilePath, time.Now()})
+	return nil
+}
+
 func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 
 	commandRunner := &MockCommandRunner{}
@@ -172,11 +182,10 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 
 	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage)
 
-	assert.Equal(t, []string{"RetrievePlan .tfplan", "IsMergeable 1", "Lock 1", "Init ", "Apply ", "LockId ", "PublishComment 1 <details>\n  <summary>Apply for ****</summary>\n\n  ```terraform\n\n  ```\n</details>", "Unlock 1", "Run echo", "LockId "}, commandStrings)
+	assert.Equal(t, []string{"RetrievePlan #.tfplan", "IsMergeable 1", "Lock 1", "Init ", "Apply ", "LockId ", "PublishComment 1 <details>\n  <summary>Apply for ****</summary>\n\n  ```terraform\n\n  ```\n</details>", "Run echo", "LockId "}, commandStrings)
 }
 
 func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
-
 	commandRunner := &MockCommandRunner{}
 	terraformExecutor := &MockTerraformExecutor{}
 	prManager := &MockPRManager{}
@@ -215,7 +224,7 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
 
 	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage)
 
-	assert.Equal(t, []string{"Lock 1", "Init ", "Plan -out .tfplan", "StorePlan .tfplan", "LockId ", "PublishComment 1 <details>\n  <summary>Plan for ****</summary>\n\n  ```terraform\n\n  ```\n</details>", "Run echo", "LockId "}, commandStrings)
+	assert.Equal(t, []string{"Lock 1", "Init ", "Plan -out #.tfplan", "StorePlan #.tfplan", "LockId ", "PublishComment 1 <details>\n  <summary>Plan for ****</summary>\n\n  ```terraform\n\n  ```\n</details>", "Run echo", "LockId "}, commandStrings)
 }
 
 func allCommandsInOrderWithParams(terraformExecutor *MockTerraformExecutor, commandRunner *MockCommandRunner, prManager *MockPRManager, lock *MockProjectLock, planStorage *MockPlanStorage) []string {
diff --git a/pkg/integration/integration_test.go b/pkg/integration/integration_test.go
index 4da2419a..a5a854ff 100644
--- a/pkg/integration/integration_test.go
+++ b/pkg/integration/integration_test.go
@@ -388,7 +388,7 @@ func TestHappyPath(t *testing.T) {
 	//  new pr should lock the project
 	impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	zipManager := utils.Zipper{}
 	planStorage := &utils.GithubPlanStorage{
@@ -423,7 +423,7 @@ func TestHappyPath(t *testing.T) {
 	// 'digger plan' comment should trigger terraform execution
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
 	assert.NoError(t, err)
@@ -437,7 +437,7 @@ func TestHappyPath(t *testing.T) {
 	// 'digger apply' comment should trigger terraform execution and unlock the project
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
 	assert.NoError(t, err)
@@ -461,7 +461,7 @@ func TestHappyPath(t *testing.T) {
 
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
 	assert.NoError(t, err)
@@ -539,7 +539,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	// no files changed, no locks
 	impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 
 	zipManager := utils.Zipper{}
@@ -574,7 +574,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	// 'digger plan' comment should trigger terraform execution
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
 	assert.NoError(t, err)
@@ -588,7 +588,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	// 'digger apply' comment should trigger terraform execution and unlock the project
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
 	assert.NoError(t, err)
@@ -612,7 +612,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
 
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
 	assert.NoError(t, err)
@@ -754,7 +754,7 @@ workflows:
 	//  new pr should lock the project
 	impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 
 	zipManager := utils.Zipper{}
@@ -790,7 +790,7 @@ workflows:
 	// 'digger plan' comment should trigger terraform execution
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
 	assert.NoError(t, err)
@@ -804,7 +804,7 @@ workflows:
 	// 'digger apply' comment should trigger terraform execution and unlock the project
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
 	assert.NoError(t, err)
@@ -827,7 +827,7 @@ workflows:
 
 	impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
 	assert.NoError(t, err)
-	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
+	commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
 	assert.NoError(t, err)
 	_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
 	assert.NoError(t, err)
diff --git a/pkg/terraform/tf_test.go b/pkg/terraform/tf_test.go
index 66bfe22e..4a8f5969 100644
--- a/pkg/terraform/tf_test.go
+++ b/pkg/terraform/tf_test.go
@@ -19,8 +19,8 @@ func TestExecuteTerraformPlan(t *testing.T) {
 	CreateValidTerraformTestFile(dir)
 
 	tf := Terraform{WorkingDir: dir, Workspace: "dev"}
-	tf.Init([]string{})
-	_, _, _, err := tf.Plan([]string{})
+	tf.Init([]string{}, map[string]string{})
+	_, _, _, err := tf.Plan([]string{}, map[string]string{})
 	assert.NoError(t, err)
 }
 
@@ -36,8 +36,8 @@ func TestExecuteTerraformApply(t *testing.T) {
 	CreateValidTerraformTestFile(dir)
 
 	tf := Terraform{WorkingDir: dir, Workspace: "dev"}
-	tf.Init([]string{})
-	_, _, _, err := tf.Plan([]string{})
+	tf.Init([]string{}, map[string]string{})
+	_, _, _, err := tf.Plan([]string{}, map[string]string{})
 	assert.NoError(t, err)
 }
 
@@ -53,11 +53,11 @@ func TestExecuteTerraformApplyDefaultWorkspace(t *testing.T) {
 	CreateValidTerraformTestFile(dir)
 
 	tf := Terraform{WorkingDir: dir, Workspace: "default"}
-	tf.Init([]string{})
+	tf.Init([]string{}, map[string]string{})
 	var planArgs []string
 	planArgs = append(planArgs, "-out", "plan.tfplan")
-	tf.Plan(planArgs)
+	tf.Plan(planArgs, map[string]string{})
 	plan := "plan.tfplan"
-	_, _, err := tf.Apply([]string{}, &plan)
+	_, _, err := tf.Apply([]string{}, &plan, map[string]string{})
 	assert.NoError(t, err)
 }