Repository: diggerhq/digger
Pull request #449 (2023-07-17 15:17:14)

Introduce manual execution mode


Patch:
diff --git a/action.yml b/action.yml
index 9f1f908f..596d4efa 100644
--- a/action.yml
+++ b/action.yml
@@ -93,6 +93,19 @@ inputs:
     description: 'comments_per_run or latest_run_comment, anything else will default to original behavior of multiple comments'
     required: false
     default: 'comments_per_run'
+  mode:
+    description: 'manual or otherwise'
+    required: false
+    default: ''
+  command:
+    description: 'digger plan or digger apply in case of manual mode'
+    required: false
+    default: ''
+  project:
+    description: 'project name for digger to run in case of manual mode'
+    required: false
+    default: ''
+
 outputs:
   output:
     value: ${{ steps.digger.outputs.output }}
@@ -195,6 +208,9 @@ runs:
         DIGGER_HOSTNAME: ${{ inputs.digger-hostname }}
         ACCUMULATE_PLANS: ${{ inputs.post-plans-as-one-comment == 'true' }}
         REPORTING_STRATEGY: ${{ inputs.reporting-strategy }}
+        INPUT_DIGGER_PROJECT: ${{ inputs.project }}
+        INPUT_DIGGER_MODE: ${{ inputs.mode }}
+        INPUT_DIGGER_COMMAND: ${{ inputs.command }}
       run: |
           cd ${{ github.action_path }}
           go build -o digger ./cmd/digger
@@ -213,6 +229,9 @@ runs:
         DIGGER_HOSTNAME: ${{ inputs.digger-hostname }}
         ACCUMULATE_PLANS: ${{ inputs.post-plans-as-one-comment == 'true' }}
         REPORTING_STRATEGY: ${{ inputs.reporting-strategy }}
+        INPUT_DIGGER_PROJECT: ${{ inputs.project }}
+        INPUT_DIGGER_MODE: ${{ inputs.mode }}
+        INPUT_DIGGER_COMMAND: ${{ inputs.command }}
       id: digger
       shell: bash
       run: |
diff --git a/cmd/digger/main.go b/cmd/digger/main.go
index eee455c5..d5ddd059 100644
--- a/cmd/digger/main.go
+++ b/cmd/digger/main.go
@@ -53,13 +53,6 @@ func gitHubCI(lock core_locking.Lock, policyChecker core_policy.Checker, reporti
 	if ghContext == "" {
 		reportErrorAndExit(githubActor, "GITHUB_CONTEXT is not defined", 2)
 	}
-
-	parsedGhContext, err := github_models.GetGitHubContext(ghContext)
-	if err != nil {
-		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to parse GitHub context. %s", err), 3)
-	}
-	println("GitHub context parsed successfully")
-
 	diggerConfig, dependencyGraph, err := configuration.LoadDiggerConfig("./")
 	if err != nil {
 		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to read Digger config. %s", err), 4)
@@ -72,60 +65,114 @@ func gitHubCI(lock core_locking.Lock, policyChecker core_policy.Checker, reporti
 	}
 	println("Lock provider has been created successfully")
 
-	ghEvent := parsedGhContext.Event
-	eventName := parsedGhContext.EventName
-	splitRepositoryName := strings.Split(parsedGhContext.Repository, "/")
+	ghRepository := os.Getenv("GITHUB_REPOSITORY")
+
+	if ghRepository == "" {
+		reportErrorAndExit(githubActor, "GITHUB_REPOSITORY is not defined", 3)
+	}
+
+	splitRepositoryName := strings.Split(ghRepository, "/")
 	repoOwner, repositoryName := splitRepositoryName[0], splitRepositoryName[1]
 	githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner)
 
-	impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	currentDir, err := os.Getwd()
 	if err != nil {
-		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to process GitHub event. %s", err), 6)
+		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to get current dir. %s", err), 4)
 	}
-	logImpactedProjects(impactedProjects, prNumber)
-	println("GitHub event processed successfully")
 
-	if dg_github.CheckIfHelpComment(ghEvent) {
-		reply := utils.GetCommands()
-		err := githubPrService.PublishComment(prNumber, reply)
+	runningMode := os.Getenv("INPUT_DIGGER_MODE")
+
+	if runningMode == "manual" {
+		command := os.Getenv("INPUT_DIGGER_COMMAND")
+		if command == "" {
+			reportErrorAndExit(githubActor, "provide 'command' to run in 'manual' mode", 1)
+		}
+		project := os.Getenv("INPUT_DIGGER_PROJECT")
+		if project == "" {
+			reportErrorAndExit(githubActor, "provide 'project' to run in 'manual' mode", 2)
+		}
+
+		var projectConfig configuration.Project
+		for _, projectConfig = range diggerConfig.Projects {
+			if projectConfig.Name == project {
+				break
+			}
+		}
+		workflow := diggerConfig.Workflows[projectConfig.Workflow]
+
+		stateEnvVars, commandEnvVars := configuration.CollectTerraformEnvConfig(workflow.EnvVars)
+
+		planStorage := newPlanStorage(ghToken, repoOwner, repositoryName, githubActor, nil)
+
+		projectCommand := models.ProjectCommand{
+			ProjectName:      project,
+			ProjectDir:       projectConfig.Dir,
+			ProjectWorkspace: projectConfig.Workspace,
+			Terragrunt:       projectConfig.Terragrunt,
+			Commands:         []string{command},
+			ApplyStage:       workflow.Apply,
+			PlanStage:        workflow.Plan,
+			CommandEnvVars:   commandEnvVars,
+			StateEnvVars:     stateEnvVars,
+		}
+		err := digger.RunCommandForProject(projectCommand, ghRepository, githubActor, "manual_invocation", &githubPrService, policyChecker, planStorage, currentDir)
 		if err != nil {
-			reportErrorAndExit(githubActor, "Failed to publish help command output", 1)
+			reportErrorAndExit(githubActor, fmt.Sprintf("Failed to run commands. %s", err), 8)
 		}
-	}
+	} else {
+		parsedGhContext, err := github_models.GetGitHubContext(ghContext)
+		if err != nil {
+			reportErrorAndExit(githubActor, fmt.Sprintf("Failed to parse GitHub context. %s", err), 3)
+		}
+		println("GitHub context parsed successfully")
 
-	if len(impactedProjects) == 0 {
-		reportErrorAndExit(githubActor, "No projects impacted", 0)
-	}
+		ghEvent := parsedGhContext.Event
+		eventName := parsedGhContext.EventName
+		impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
+		if err != nil {
+			reportErrorAndExit(githubActor, fmt.Sprintf("Failed to process GitHub event. %s", err), 6)
+		}
+		logImpactedProjects(impactedProjects, prNumber)
+		println("GitHub event processed successfully")
+
+		if dg_github.CheckIfHelpComment(ghEvent) {
+			reply := utils.GetCommands()
+			err := githubPrService.PublishComment(prNumber, reply)
+			if err != nil {
+				reportErrorAndExit(githubActor, "Failed to publish help command output", 1)
+			}
+		}
 
-	commandsToRunPerProject, coversAllImpactedProjects, err := dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
-	if err != nil {
-		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to convert GitHub event to commands. %s", err), 7)
-	}
-	println("GitHub event converted to commands successfully")
-	logCommands(commandsToRunPerProject)
+		if len(impactedProjects) == 0 {
+			reportErrorAndExit(githubActor, "No projects impacted", 0)
+		}
 
-	planStorage := newPlanStorage(ghToken, repoOwner, repositoryName, githubActor, prNumber)
+		commandsToRunPerProject, coversAllImpactedProjects, err := dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
+		if err != nil {
+			reportErrorAndExit(githubActor, fmt.Sprintf("Failed to convert GitHub event to commands. %s", err), 7)
+		}
+		println("GitHub event converted to commands successfully")
+		logCommands(commandsToRunPerProject)
 
-	reporter := &reporting.CiReporter{
-		CiService:      githubPrService,
-		PrNumber:       prNumber,
-		ReportStrategy: reportingStrategy,
-	}
-	currentDir, err := os.Getwd()
-	if err != nil {
-		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to get current dir. %s", err), 4)
-	}
-	allAppliesSuccessful, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, &dependencyGraph, parsedGhContext.Repository, githubActor, eventName, prNumber, githubPrService, lock, reporter, planStorage, policyChecker, currentDir)
-	if err != nil {
-		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to run commands. %s", err), 8)
-	}
+		planStorage := newPlanStorage(ghToken, repoOwner, repositoryName, githubActor, &prNumber)
 
-	if diggerConfig.AutoMerge && allAppliesSuccessful && atLeastOneApply && coversAllImpactedProjects {
-		digger.MergePullRequest(githubPrService, prNumber)
-		println("PR merged successfully")
-	}
+		reporter := &reporting.CiReporter{
+			CiService:      &githubPrService,
+			PrNumber:       prNumber,
+			ReportStrategy: reportingStrategy,
+		}
+		allAppliesSuccessful, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, &dependencyGraph, parsedGhContext.Repository, githubActor, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, policyChecker, currentDir)
+		if err != nil {
+			reportErrorAndExit(githubActor, fmt.Sprintf("Failed to run commands. %s", err), 8)
+		}
 
-	println("Commands executed successfully")
+		if diggerConfig.AutoMerge && allAppliesSuccessful && atLeastOneApply && coversAllImpactedProjects {
+			digger.MergePullRequest(&githubPrService, prNumber)
+			println("PR merged successfully")
+		}
+
+		println("Commands executed successfully")
+	}
 
 	reportErrorAndExit(githubActor, "Digger finished successfully", 0)
 
@@ -198,13 +245,13 @@ func gitLabCI(lock core_locking.Lock, policyChecker core_policy.Checker, reporti
 	}
 
 	diggerProjectNamespace := gitLabContext.ProjectNamespace + "/" + gitLabContext.ProjectName
-	planStorage := newPlanStorage("", "", "", gitLabContext.GitlabUserName, *gitLabContext.MergeRequestIId)
+	planStorage := newPlanStorage("", "", "", gitLabContext.GitlabUserName, gitLabContext.MergeRequestIId)
 	reporter := &reporting.CiReporter{
 		CiService:      gitlabService,
 		PrNumber:       *gitLabContext.MergeRequestIId,
 		ReportStrategy: reportingStrategy,
 	}
-	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, &dependencyGraph, diggerProjectNamespace, gitLabContext.GitlabUserName, gitLabContext.EventType.String(), *gitLabContext.MergeRequestIId, gitlabService, lock, reporter, planStorage, policyChecker, currentDir)
+	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, &dependencyGraph, diggerProjectNamespace, gitLabContext.GitlabUserName, gitLabContext.EventType.String(), *gitLabContext.MergeRequestIId, gitlabService, gitlabService, lock, reporter, planStorage, policyChecker, currentDir)
 
 	if err != nil {
 		fmt.Printf("failed to execute command, %v", err)
@@ -282,7 +329,7 @@ func azureCI(lock core_locking.Lock, policyChecker core_policy.Checker, reportin
 		PrNumber:       prNumber,
 		ReportStrategy: reportingStrategy,
 	}
-	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, &dependencyGraph, diggerProjectNamespace, parsedAzureContext.BaseUrl, parsedAzureContext.EventType, prNumber, azureService, lock, reporter, planStorage, policyChecker, currentDir)
+	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, &dependencyGraph, diggerProjectNamespace, parsedAzureContext.BaseUrl, parsedAzureContext.EventType, prNumber, azureService, azureService, lock, reporter, planStorage, policyChecker, currentDir)
 	if err != nil {
 		reportErrorAndExit(parsedAzureContext.BaseUrl, fmt.Sprintf("Failed to run commands. %s", err), 8)
 	}
@@ -380,7 +427,7 @@ func main() {
 	}
 }
 
-func newPlanStorage(ghToken string, ghRepoOwner string, ghRepositoryName string, requestedBy string, prNumber int) core_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"))
@@ -390,7 +437,7 @@ func newPlanStorage(ghToken string, ghRepoOwner string, ghRepositoryName string,
 			Client:            github.NewTokenClient(context.Background(), ghToken),
 			Owner:             ghRepoOwner,
 			RepoName:          ghRepositoryName,
-			PullRequestNumber: prNumber,
+			PullRequestNumber: *prNumber,
 			ZipManager:        zipManager,
 		}
 	} else if uploadDestination == "gcp" {
diff --git a/pkg/aws/envprovider/envprovider.go b/pkg/aws/envprovider/envprovider.go
index 82ddda7f..e99c2225 100644
--- a/pkg/aws/envprovider/envprovider.go
+++ b/pkg/aws/envprovider/envprovider.go
@@ -1,7 +1,6 @@
 package envprovider
 
 import (
-	"errors"
 	"os"
 
 	"github.com/aws/aws-sdk-go/aws/awserr"
@@ -37,17 +36,31 @@ type EnvProvider struct {
 func (e *EnvProvider) Retrieve() (credentials.Value, error) {
 	e.retrieved = false
 
-	//assign id from env vars
-	idEnvVars := []string{"DIGGER_AWS_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID", "AWS_ACCESS_KEY"}
-	id, err := assignEnv(idEnvVars)
-	if err != nil {
+	id := os.Getenv("DIGGER_AWS_ACCESS_KEY_ID")
+
+	if id == "" {
+		id = os.Getenv("AWS_ACCESS_KEY_ID")
+	}
+
+	if id == "" {
+		id = os.Getenv("AWS_ACCESS_KEY")
+	}
+
+	secret := os.Getenv("DIGGER_AWS_SECRET_ACCESS_KEY")
+
+	if secret == "" {
+		secret = os.Getenv("AWS_SECRET_ACCESS_KEY")
+	}
+
+	if secret == "" {
+		secret = os.Getenv("AWS_SECRET_KEY")
+	}
+
+	if id == "" {
 		return credentials.Value{ProviderName: EnvProviderName}, ErrAccessKeyIDNotFound
 	}
 
-	//assign secret from env vars
-	secretEnvVars := []string{"DIGGER_AWS_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY", "AWS_SECRET_KEY"}
-	secret, err := assignEnv(secretEnvVars)
-	if err != nil {
+	if secret == "" {
 		return credentials.Value{ProviderName: EnvProviderName}, ErrSecretAccessKeyNotFound
 	}
 
@@ -60,18 +73,6 @@ func (e *EnvProvider) Retrieve() (credentials.Value, error) {
 	}, nil
 }
 
-// Assign first non-nil env var
-func assignEnv(envVars []string) (string, error) {
-	var v string
-	for _, envVar := range envVars {
-		if value, ok := os.LookupEnv(envVar); ok {
-			v = value
-			return v, nil
-		}
-	}
-	return "", errors.New("not found")
-}
-
 // IsExpired returns if the credentials have been retrieved.
 func (e *EnvProvider) IsExpired() bool {
 	return !e.retrieved
diff --git a/pkg/azure/azure.go b/pkg/azure/azure.go
index faa393e6..a943e42c 100644
--- a/pkg/azure/azure.go
+++ b/pkg/azure/azure.go
@@ -351,7 +351,7 @@ func (a *AzureReposService) GetComments(prNumber int) ([]ci.Comment, error) {
 
 }
 
-func ProcessAzureReposEvent(azureEvent interface{}, diggerConfig *configuration.DiggerConfig, ciService ci.CIService) ([]configuration.Project, *configuration.Project, int, error) {
+func ProcessAzureReposEvent(azureEvent interface{}, diggerConfig *configuration.DiggerConfig, ciService ci.PullRequestService) ([]configuration.Project, *configuration.Project, int, error) {
 	var impactedProjects []configuration.Project
 	var prNumber int
 
diff --git a/pkg/ci/ci.go b/pkg/ci/ci.go
index 3caaf512..01975b70 100644
--- a/pkg/ci/ci.go
+++ b/pkg/ci/ci.go
@@ -1,6 +1,6 @@
 package ci
 
-type CIService interface {
+type PullRequestService interface {
 	GetChangedFiles(prNumber int) ([]string, error)
 	PublishComment(prNumber int, comment string) error
 	EditComment(id interface{}, comment string) error
@@ -15,6 +15,9 @@ type CIService interface {
 	IsMerged(prNumber int) (bool, error)
 	// IsClosed closed without merging
 	IsClosed(prNumber int) (bool, error)
+}
+
+type OrgService interface {
 	GetUserTeams(organisation string, user string) ([]string, error)
 }
 
diff --git a/pkg/core/execution/execution.go b/pkg/core/execution/execution.go
index 2f0c6c06..f1dc16e8 100644
--- a/pkg/core/execution/execution.go
+++ b/pkg/core/execution/execution.go
@@ -16,6 +16,59 @@ import (
 	"strings"
 )
 
+type Executor interface {
+	Plan() (bool, string, error)
+	Apply() (bool, error)
+}
+
+type LockingExecutorWrapper struct {
+	ProjectLock locking.ProjectLock
+	Executor    Executor
+}
+
+func (l LockingExecutorWrapper) Plan() (bool, string, error) {
+	plan := ""
+	locked, err := l.ProjectLock.Lock()
+	if err != nil {
+		return false, "", fmt.Errorf("error locking project: %v", err)
+	}
+	log.Printf("Lock result: %t\n", locked)
+	if locked {
+		return l.Executor.Plan()
+	} else {
+		return false, plan, nil
+	}
+}
+
+func (l LockingExecutorWrapper) Apply() (bool, error) {
+	locked, err := l.ProjectLock.Lock()
+	if err != nil {
+		return false, fmt.Errorf("error locking project: %v", err)
+	}
+	log.Printf("Lock result: %t\n", locked)
+	if locked {
+		return l.Executor.Apply()
+	} else {
+		return false, nil
+	}
+}
+
+func (l LockingExecutorWrapper) Unlock() error {
+	err := l.ProjectLock.ForceUnlock()
+	if err != nil {
+		return fmt.Errorf("failed to aquire lock: %s, %v", l.ProjectLock.LockId(), err)
+	}
+	return nil
+}
+
+func (l LockingExecutorWrapper) Lock() error {
+	_, err := l.ProjectLock.Lock()
+	if err != nil {
+		return fmt.Errorf("failed to aquire lock: %s, %v", l.ProjectLock.LockId(), err)
+	}
+	return nil
+}
+
 type DiggerExecutor struct {
 	ProjectNamespace  string
 	ProjectName       string
@@ -27,197 +80,166 @@ type DiggerExecutor struct {
 	CommandRunner     runners.CommandRun
 	TerraformExecutor terraform.TerraformExecutor
 	Reporter          reporting.Reporter
-	ProjectLock       locking.ProjectLock
 	PlanStorage       storage.PlanStorage
+	PlanPathProvider  PlanPathProvider
+}
+
+type PlanPathProvider interface {
+	LocalPlanFilePath() string
+	StoredPlanFilePath() string
+	PlanFileName() string
 }
 
-func (d DiggerExecutor) planFileName() string {
+type ProjectPathProvider struct {
+	ProjectPath      string
+	ProjectNamespace string
+	ProjectName      string
+}
+
+func (d ProjectPathProvider) PlanFileName() string {
 	return strings.ReplaceAll(d.ProjectNamespace, "/", ":") + "#" + d.ProjectName + ".tfplan"
 }
 
-func (d DiggerExecutor) localPlanFilePath() string {
-	return path.Join(d.ProjectPath, d.planFileName())
+func (d ProjectPathProvider) LocalPlanFilePath() string {
+	return path.Join(d.ProjectPath, d.PlanFileName())
 }
 
-func (d DiggerExecutor) storedPlanFilePath() string {
-	return path.Join(d.ProjectNamespace, d.planFileName())
+func (d ProjectPathProvider) StoredPlanFilePath() string {
+	return path.Join(d.ProjectNamespace, d.PlanFileName())
 }
 
 func (d DiggerExecutor) Plan() (bool, string, error) {
 	plan := ""
-	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
+	var planSteps []models.Step
 
-		if d.PlanStage != nil {
-			planSteps = d.PlanStage.Steps
-		} else {
-			planSteps = []models.Step{
-				{
-					Action: "init",
-				},
-				{
-					Action: "plan",
-				},
+	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)
 			}
 		}
-		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.PlanPathProvider.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 step.Action == "plan" {
-				planArgs := []string{"-out", d.planFileName()}
-				planArgs = append(planArgs, step.ExtraArgs...)
-				isNonEmptyPlan, stdout, stderr, err := d.TerraformExecutor.Plan(planArgs, d.CommandEnvVars)
+			if d.PlanStorage != nil {
+				planExists, err := d.PlanStorage.PlanExists(d.PlanPathProvider.StoredPlanFilePath())
 				if err != nil {
-					return false, "", fmt.Errorf("error executing plan: %v", err)
+					return false, "", fmt.Errorf("error checking if plan exists: %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 planExists {
+					err = d.PlanStorage.DeleteStoredPlan(d.PlanPathProvider.StoredPlanFilePath())
 					if err != nil {
-						return false, "", fmt.Errorf("error storing plan: %v", err)
+						return false, "", fmt.Errorf("error deleting plan: %v", err)
 					}
 				}
-				plan = cleanupTerraformPlan(isNonEmptyPlan, err, stdout, stderr)
+
+				err = d.PlanStorage.StorePlan(d.PlanPathProvider.LocalPlanFilePath(), d.PlanPathProvider.StoredPlanFilePath())
 				if err != nil {
-					fmt.Printf("error publishing comment: %v", err)
+					return false, "", fmt.Errorf("error storing plan: %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)
-				}
+			plan = cleanupTerraformPlan(isNonEmptyPlan, err, stdout, stderr)
+			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.ProjectNamespace+"#"+d.ProjectName)
+			_, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands)
+			if err != nil {
+				return false, "", fmt.Errorf("error running command: %v", err)
 			}
 		}
-		return true, plan, nil
 	}
-	return false, plan, nil
+	return true, plan, 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())
+		plansFilename, err = d.PlanStorage.RetrievePlan(d.PlanPathProvider.LocalPlanFilePath(), d.PlanPathProvider.StoredPlanFilePath())
 		if err != nil {
 			return false, fmt.Errorf("error retrieving plan: %v", err)
 		}
 	}
 
-	locked, err := d.ProjectLock.Lock()
+	var applySteps []models.Step
 
-	if err != nil {
-		return false, fmt.Errorf("error locking project: %v", err)
+	if d.ApplyStage != nil {
+		applySteps = d.ApplyStage.Steps
+	} else {
+		applySteps = []models.Step{
+			{
+				Action: "init",
+			},
+			{
+				Action: "apply",
+			},
+		}
 	}
 
-	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)
+			formatter := utils.GetTerraformOutputAsCollapsibleComment("Apply for <b>" + d.ProjectNamespace + "#" + d.ProjectName + "</b>")
 
-		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)
-				}
+			commentErr := d.Reporter.Report(applyOutput, formatter)
+			if commentErr != nil {
+				fmt.Printf("error publishing comment: %v", err)
 			}
-			if step.Action == "apply" {
-				stdout, stderr, err := d.TerraformExecutor.Apply(step.ExtraArgs, plansFilename, d.CommandEnvVars)
-				applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
-				formatter := utils.GetTerraformOutputAsCollapsibleComment("Apply for <b>" + d.ProjectLock.LockId() + "</b>")
-
-				commentErr := d.Reporter.Report(applyOutput, formatter)
+			if err != nil {
+				commentErr = d.Reporter.Report(err.Error(), utils.AsCollapsibleComment("Error during applying."))
 				if commentErr != nil {
 					fmt.Printf("error publishing comment: %v", err)
 				}
-				if err != nil {
-					commentErr = d.Reporter.Report(err.Error(), utils.AsCollapsibleComment("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 false, fmt.Errorf("error executing apply: %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 step.Action == "run" {
+			var commands []string
+			if os.Getenv("ACTIVATE_VENV") == "true" {
+				commands = append(commands, fmt.Sprintf("source %v/.venv/bin/activate", d.ProjectPath))
+			}
+			commands = append(commands, step.Value)
+			log.Printf("Running %v for **%v**\n", step.Value, d.ProjectNamespace+"#"+d.ProjectName)
+			_, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands)
+			if err != nil {
+				return false, fmt.Errorf("error running command: %v", 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
+	return true, nil
 }
 
 func cleanupTerraformOutput(nonEmptyOutput bool, planError error, stdout string, stderr string, regexStr *string) string {
diff --git a/pkg/core/policy/policy.go b/pkg/core/policy/policy.go
index 296eac52..3aa6b26f 100644
--- a/pkg/core/policy/policy.go
+++ b/pkg/core/policy/policy.go
@@ -7,5 +7,5 @@ type Provider interface {
 }
 
 type Checker interface {
-	Check(ciService ci.CIService, SCMOrganisation string, SCMrepository string, projectname string, command string, requestedBy string) (bool, error)
+	Check(ciService ci.OrgService, SCMOrganisation string, SCMrepository string, projectname string, command string, requestedBy string) (bool, error)
 }
diff --git a/pkg/digger/digger.go b/pkg/digger/digger.go
index e8370d9b..a3b035e6 100644
--- a/pkg/digger/digger.go
+++ b/pkg/digger/digger.go
@@ -6,12 +6,13 @@ import (
 	core_locking "digger/pkg/core/locking"
 	"digger/pkg/core/models"
 	"digger/pkg/core/policy"
-	"digger/pkg/core/reporting"
+	core_reporting "digger/pkg/core/reporting"
 	"digger/pkg/core/runners"
 	"digger/pkg/core/storage"
 	"digger/pkg/core/terraform"
 	"digger/pkg/core/utils"
 	"digger/pkg/locking"
+	"digger/pkg/reporting"
 	"digger/pkg/usage"
 	"errors"
 	"fmt"
@@ -67,9 +68,10 @@ func RunCommandsPerProject(
 	requestedBy string,
 	eventName string,
 	prNumber int,
-	ciService ci.CIService,
+	prService ci.PullRequestService,
+	orgService ci.OrgService,
 	lock core_locking.Lock,
-	reporter reporting.Reporter,
+	reporter core_reporting.Reporter,
 	planStorage storage.PlanStorage,
 	policyChecker policy.Checker,
 	workingDir string,
@@ -86,7 +88,7 @@ func RunCommandsPerProject(
 		for _, command := range projectCommands.Commands {
 			fmt.Printf("Running '%s' for project '%s'\n", command, projectCommands.ProjectName)
 
-			allowedToPerformCommand, err := policyChecker.Check(ciService, SCMOrganisation, SCMrepository, projectCommands.ProjectName, command, requestedBy)
+			allowedToPerformCommand, err := policyChecker.Check(orgService, SCMOrganisation, SCMrepository, projectCommands.ProjectName, command, requestedBy)
 
 			if err != nil {
 				return false, false, fmt.Errorf("error checking policy: %v", err)
@@ -105,7 +107,7 @@ func RunCommandsPerProject(
 			projectLock := &locking.PullRequestLock{
 				InternalLock:     lock,
 				Reporter:         reporter,
-				CIService:        ciService,
+				CIService:        prService,
 				ProjectName:      projectCommands.ProjectName,
 				ProjectNamespace: projectNamespace,
 				PrNumber:         prNumber,
@@ -120,19 +122,27 @@ func RunCommandsPerProject(
 			}
 
 			commandRunner := runners.CommandRunner{}
-			diggerExecutor := execution.DiggerExecutor{
-				projectNamespace,
-				projectCommands.ProjectName,
-				projectPath,
-				projectCommands.StateEnvVars,
-				projectCommands.CommandEnvVars,
-				projectCommands.ApplyStage,
-				projectCommands.PlanStage,
-				commandRunner,
-				terraformExecutor,
-				reporter,
-				projectLock,
-				planStorage,
+			planPathProvider := execution.ProjectPathProvider{
+				ProjectPath:      projectPath,
+				ProjectNamespace: projectNamespace,
+				ProjectName:      projectCommands.ProjectName,
+			}
+			diggerExecutor := execution.LockingExecutorWrapper{
+				ProjectLock: projectLock,
+				Executor: execution.DiggerExecutor{
+					ProjectNamespace:  projectNamespace,
+					ProjectName:       projectCommands.ProjectName,
+					ProjectPath:       projectPath,
+					StateEnvVars:      projectCommands.StateEnvVars,
+					CommandEnvVars:    projectCommands.CommandEnvVars,
+					ApplyStage:        projectCommands.ApplyStage,
+					PlanStage:         projectCommands.PlanStage,
+					CommandRunner:     commandRunner,
+					TerraformExecutor: terraformExecutor,
+					Reporter:          reporter,
+					PlanStorage:       planStorage,
+					PlanPathProvider:  planPathProvider,
+				},
 			}
 
 			switch command {
@@ -141,14 +151,14 @@ func RunCommandsPerProject(
 				if err != nil {
 					return false, false, fmt.Errorf("failed to send usage report. %v", err)
 				}
-				err = ciService.SetStatus(prNumber, "pending", projectCommands.ProjectName+"/plan")
+				err = prService.SetStatus(prNumber, "pending", projectCommands.ProjectName+"/plan")
 				if err != nil {
 					return false, false, fmt.Errorf("failed to set PR status. %v", err)
 				}
 				planPerformed, plan, err := diggerExecutor.Plan()
 				if err != nil {
 					log.Printf("Failed to run digger plan command. %v", err)
-					err := ciService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/plan")
+					err := prService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/plan")
 					if err != nil {
 						return false, false, fmt.Errorf("failed to set PR status. %v", err)
 					}
@@ -161,7 +171,7 @@ func RunCommandsPerProject(
 							log.Printf("Failed to report plan. %v", err)
 						}
 					}
-					err := ciService.SetStatus(prNumber, "success", projectCommands.ProjectName+"/plan")
+					err := prService.SetStatus(prNumber, "success", projectCommands.ProjectName+"/plan")
 					if err != nil {
 						return false, false, fmt.Errorf("failed to set PR status. %v", err)
 					}
@@ -172,18 +182,18 @@ func RunCommandsPerProject(
 				if err != nil {
 					return false, false, fmt.Errorf("failed to send usage report. %v", err)
 				}
-				err = ciService.SetStatus(prNumber, "pending", projectCommands.ProjectName+"/apply")
+				err = prService.SetStatus(prNumber, "pending", projectCommands.ProjectName+"/apply")
 				if err != nil {
 					return false, false, fmt.Errorf("failed to set PR status. %v", err)
 				}
 
-				isMerged, err := ciService.IsMerged(prNumber)
+				isMerged, err := prService.IsMerged(prNumber)
 				if err != nil {
 					return false, false, fmt.Errorf("error checking if PR is merged: %v", err)
 				}
 
 				// this might go into some sort of "appliability" plugin later
-				isMergeable, err := ciService.IsMergeable(prNumber)
+				isMergeable, err := prService.IsMergeable(prNumber)
 				if err != nil {
 					return false, false, fmt.Errorf("error validating is PR is mergeable: %v", err)
 				}
@@ -200,13 +210,13 @@ func RunCommandsPerProject(
 					applyPerformed, err := diggerExecutor.Apply()
 					if err != nil {
 						log.Printf("Failed to run digger apply command. %v", err)
-						err := ciService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/apply")
+						err := prService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/apply")
 						if err != nil {
 							return false, false, fmt.Errorf("failed to set PR status. %v", err)
 						}
 						return false, false, fmt.Errorf("failed to run digger apply command. %v", err)
 					} else if applyPerformed {
-						err := ciService.SetStatus(prNumber, "success", projectCommands.ProjectName+"/apply")
+						err := prService.SetStatus(prNumber, "success", projectCommands.ProjectName+"/apply")
 						if err != nil {
 							return false, false, fmt.Errorf("failed to set PR status. %v", err)
 						}
@@ -222,6 +232,13 @@ func RunCommandsPerProject(
 				if err != nil {
 					return false, false, fmt.Errorf("failed to unlock project. %v", err)
 				}
+
+				if planStorage != nil {
+					err = planStorage.DeleteStoredPlan(planPathProvider.StoredPlanFilePath())
+					if err != nil {
+						log.Printf("failed to delete stored plan file '%v':  %v", planPathProvider.StoredPlanFilePath(), err)
+					}
+				}
 			case "digger lock":
 				err := usage.SendUsageRecord(requestedBy, eventName, "lock")
 				if err != nil {
@@ -247,6 +264,94 @@ func RunCommandsPerProject(
 	return allAppliesSuccess, atLeastOneApply, nil
 }
 
+func RunCommandForProject(
+	commands models.ProjectCommand,
+	projectNamespace string,
+	requestedBy string,
+	eventName string,
+	orgService ci.OrgService,
+	policyChecker policy.Checker,
+	planStorage storage.PlanStorage,
+	workingDir string,
+) error {
+	splits := strings.Split(projectNamespace, "/")
+	SCMOrganisation := splits[0]
+	SCMrepository := splits[1]
+	fmt.Printf("Running '%s' for project '%s'\n", commands.Commands, commands.ProjectName)
+
+	for _, command := range commands.Commands {
+
+		allowedToPerformCommand, err := policyChecker.Check(orgService, SCMOrganisation, SCMrepository, commands.ProjectName, command, requestedBy)
+
+		if err != nil {
+			return fmt.Errorf("error checking policy: %v", err)
+		}
+
+		if !allowedToPerformCommand {
+			msg := fmt.Sprintf("User %s is not allowed to perform action: %s. Check your policies", requestedBy, command)
+			if err != nil {
+				log.Printf("Error publishing comment: %v", err)
+			}
+			log.Println(msg)
+			return errors.New(msg)
+		}
+		var terraformExecutor terraform.TerraformExecutor
+		projectPath := path.Join(workingDir, commands.ProjectDir)
+		if commands.Terragrunt {
+			terraformExecutor = terraform.Terragrunt{WorkingDir: projectPath}
+		} else {
+			terraformExecutor = terraform.Terraform{WorkingDir: projectPath, Workspace: commands.ProjectWorkspace}
+		}
+
+		commandRunner := runners.CommandRunner{}
+
+		planPathProvider := execution.ProjectPathProvider{
+			ProjectPath:      projectPath,
+			ProjectNamespace: projectNamespace,
+			ProjectName:      commands.ProjectName,
+		}
+
+		diggerExecutor := execution.DiggerExecutor{
+			ProjectNamespace:  projectNamespace,
+			ProjectName:       commands.ProjectName,
+			ProjectPath:       projectPath,
+			StateEnvVars:      commands.StateEnvVars,
+			CommandEnvVars:    commands.CommandEnvVars,
+			ApplyStage:        commands.ApplyStage,
+			PlanStage:         commands.PlanStage,
+			CommandRunner:     commandRunner,
+			Reporter:          &reporting.StdOutReporter{},
+			TerraformExecutor: terraformExecutor,
+			PlanStorage:       planStorage,
+			PlanPathProvider:  planPathProvider,
+		}
+
+		switch command {
+		case "digger plan":
+			err := usage.SendUsageRecord(requestedBy, eventName, "plan")
+			if err != nil {
+				log.Printf("Failed to send usage report. %v", err)
+			}
+			_, _, err = diggerExecutor.Plan()
+			if err != nil {
+				log.Printf("Failed to run digger plan command. %v", err)
+				return fmt.Errorf("failed to run digger plan command. %v", err)
+			}
+		case "digger apply":
+			err := usage.SendUsageRecord(requestedBy, eventName, "apply")
+			if err != nil {
+				log.Printf("Failed to send usage report. %v", err)
+			}
+			_, err = diggerExecutor.Apply()
+			if err != nil {
+				log.Printf("Failed to run digger apply command. %v", err)
+				return fmt.Errorf("failed to run digger apply command. %v", err)
+			}
+		}
+	}
+	return nil
+}
+
 func SortedCommandsByDependency(project []models.ProjectCommand, dependencyGraph *graph.Graph[string, string]) []models.ProjectCommand {
 	var sortedCommands []models.ProjectCommand
 	sortedGraph, err := graph.StableTopologicalSort(*dependencyGraph, func(s string, s2 string) bool {
@@ -265,7 +370,7 @@ func SortedCommandsByDependency(project []models.ProjectCommand, dependencyGraph
 	return sortedCommands
 }
 
-func MergePullRequest(ciService ci.CIService, prNumber int) {
+func MergePullRequest(ciService ci.PullRequestService, prNumber int) {
 	time.Sleep(5 * time.Second)
 
 	// Check if it was manually merged
@@ -300,6 +405,6 @@ func MergePullRequest(ciService ci.CIService, prNumber int) {
 			log.Fatalf("failed to merge PR, %v", err)
 		}
 	} else {
-	   log.Printf("PR is already merged, skipping merge step")
+		log.Printf("PR is already merged, skipping merge step")
 	}
 }
diff --git a/pkg/github/github.go b/pkg/github/github.go
index 3f94874a..499fbb42 100644
--- a/pkg/github/github.go
+++ b/pkg/github/github.go
@@ -15,9 +15,9 @@ import (
 	"github.com/google/go-github/v53/github"
 )
 
-func NewGitHubService(ghToken string, repoName string, owner string) ci.CIService {
+func NewGitHubService(ghToken string, repoName string, owner string) GithubService {
 	client := github.NewTokenClient(context.Background(), ghToken)
-	return &GithubService{
+	return GithubService{
 		Client:   client,
 		RepoName: repoName,
 		Owner:    owner,
@@ -291,7 +291,7 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
 	}
 }
 
-func ProcessGitHubEvent(ghEvent models.Event, diggerConfig *configuration.DiggerConfig, ciService ci.CIService) ([]configuration.Project, *configuration.Project, int, error) {
+func ProcessGitHubEvent(ghEvent models.Event, diggerConfig *configuration.DiggerConfig, ciService ci.PullRequestService) ([]configuration.Project, *configuration.Project, int, error) {
 	var impactedProjects []configuration.Project
 	var prNumber int
 
diff --git a/pkg/github/models/models.go b/pkg/github/models/models.go
index 36b2dab0..370583d5 100644
--- a/pkg/github/models/models.go
+++ b/pkg/github/models/models.go
@@ -60,6 +60,12 @@ func (g *Github) UnmarshalJSON(data []byte) error {
 			return err
 		}
 		g.Event = event
+	case "push":
+		var event PushEvent
+		if err := json.Unmarshal(rawEvent, &event); err != nil {
+			return err
+		}
+		g.Event = event
 	default:
 		return errors.New("unknown GitHub event: " + g.EventName)
 	}
@@ -80,6 +86,9 @@ type PullRequest struct {
 	Base   Base `json:"base"`
 }
 
+type PushEvent struct {
+}
+
 type IssueCommentEvent struct {
 	Action  string  `json:"action"`
 	Comment Comment `json:"comment"`
diff --git a/pkg/locking/locking.go b/pkg/locking/locking.go
index db66b30a..471e0752 100644
--- a/pkg/locking/locking.go
+++ b/pkg/locking/locking.go
@@ -27,7 +27,7 @@ import (
 
 type PullRequestLock struct {
 	InternalLock     locking.Lock
-	CIService        ci.CIService
+	CIService        ci.PullRequestService
 	Reporter         reporting.Reporter
 	ProjectName      string
 	ProjectNamespace string
diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go
index 1be703e7..879cecf5 100644
--- a/pkg/policy/policy.go
+++ b/pkg/policy/policy.go
@@ -25,7 +25,7 @@ type DiggerHttpPolicyProvider struct {
 type NoOpPolicyChecker struct {
 }
 
-func (p NoOpPolicyChecker) Check(_ ci.CIService, _ string, _ string, _ string, _ string, _ string) (bool, error) {
+func (p NoOpPolicyChecker) Check(_ ci.OrgService, _ string, _ string, _ string, _ string, _ string) (bool, error) {
 	return true, nil
 }
 
@@ -108,7 +108,7 @@ type DiggerPolicyChecker struct {
 	PolicyProvider PolicyProvider
 }
 
-func (p DiggerPolicyChecker) Check(ciService ci.CIService, SCMOrganisation string, SCMrepository string, projectName string, command string, requestedBy string) (bool, error) {
+func (p DiggerPolicyChecker) Check(ciService ci.OrgService, SCMOrganisation string, SCMrepository string, projectName string, command string, requestedBy string) (bool, error) {
 	organisation := p.PolicyProvider.GetOrganisation()
 	policy, err := p.PolicyProvider.GetPolicy(organisation, SCMrepository, projectName)
 
diff --git a/pkg/reporting/reporting.go b/pkg/reporting/reporting.go
index 3e11fa2c..0903fb6e 100644
--- a/pkg/reporting/reporting.go
+++ b/pkg/reporting/reporting.go
@@ -9,20 +9,31 @@ import (
 )
 
 type CiReporter struct {
-	CiService      ci.CIService
+	CiService      ci.PullRequestService
 	PrNumber       int
 	ReportStrategy ReportStrategy
 }
 
+func (ciReporter *CiReporter) Report(report string, reportFormatter func(report string) string) error {
+	return ciReporter.ReportStrategy.Report(ciReporter.CiService, ciReporter.PrNumber, report, reportFormatter)
+}
+
+type StdOutReporter struct{}
+
+func (reporter *StdOutReporter) Report(report string, reportFormatter func(report string) string) error {
+	fmt.Println(reportFormatter(report))
+	return nil
+}
+
 type ReportStrategy interface {
-	Report(ciService ci.CIService, PrNumber int, report string, reportFormatter func(report string) string) error
+	Report(ciService ci.PullRequestService, PrNumber int, report string, reportFormatter func(report string) string) error
 }
 
 type CommentPerRunStrategy struct {
 	TimeOfRun time.Time
 }
 
-func (strategy *CommentPerRunStrategy) Report(ciService ci.CIService, PrNumber int, report string, reportFormatter func(report string) string) error {
+func (strategy *CommentPerRunStrategy) Report(ciService ci.PullRequestService, PrNumber int, report string, reportFormatter func(report string) string) error {
 	comments, err := ciService.GetComments(PrNumber)
 	if err != nil {
 		return fmt.Errorf("error getting comments: %v", err)
@@ -32,7 +43,7 @@ func (strategy *CommentPerRunStrategy) Report(ciService ci.CIService, PrNumber i
 	return upsertComment(ciService, PrNumber, report, reportFormatter, comments, reportTitle, err)
 }
 
-func upsertComment(ciService ci.CIService, PrNumber int, report string, reportFormatter func(report string) string, comments []ci.Comment, reportTitle string, err error) error {
+func upsertComment(ciService ci.PullRequestService, PrNumber int, report string, reportFormatter func(report string) string, comments []ci.Comment, reportTitle string, err error) error {
 	report = reportFormatter(report)
 	var commentIdForThisRun interface{}
 	var commentBody string
@@ -74,7 +85,7 @@ type LatestRunCommentStrategy struct {
 	TimeOfRun time.Time
 }
 
-func (strategy *LatestRunCommentStrategy) Report(ciService ci.CIService, prNumber int, comment string, commentFormatting func(comment string) string) error {
+func (strategy *LatestRunCommentStrategy) Report(ciService ci.PullRequestService, prNumber int, comment string, commentFormatting func(comment string) string) error {
 	comments, err := ciService.GetComments(prNumber)
 	if err != nil {
 		return fmt.Errorf("error getting comments: %v", err)
@@ -86,10 +97,6 @@ func (strategy *LatestRunCommentStrategy) Report(ciService ci.CIService, prNumbe
 
 type MultipleCommentsStrategy struct{}
 
-func (strategy *MultipleCommentsStrategy) Report(ciService ci.CIService, PrNumber int, report string, formatter func(string) string) error {
+func (strategy *MultipleCommentsStrategy) Report(ciService ci.PullRequestService, PrNumber int, report string, formatter func(string) string) error {
 	return ciService.PublishComment(PrNumber, formatter(report))
 }
-
-func (ciReporter *CiReporter) Report(report string, reportFormatter func(report string) string) error {
-	return ciReporter.ReportStrategy.Report(ciReporter.CiService, ciReporter.PrNumber, report, reportFormatter)
-}
diff --git a/pkg/utils/mocks.go b/pkg/utils/mocks.go
index b299a917..0aa1a603 100644
--- a/pkg/utils/mocks.go
+++ b/pkg/utils/mocks.go
@@ -44,7 +44,7 @@ func (lock *MockLock) GetLock(resource string) (*int, error) {
 type MockPolicyChecker struct {
 }
 
-func (t MockPolicyChecker) Check(ciService ci.CIService, SCMOrganisation string, SCMrepository string, projectName string, command string, requestedBy string) (bool, error) {
+func (t MockPolicyChecker) Check(ciService ci.OrgService, SCMOrganisation string, SCMrepository string, projectName string, command string, requestedBy string) (bool, error) {
 	return false, nil
 }
 


Test patch:
diff --git a/cmd/digger/main_test.go b/cmd/digger/main_test.go
index a878f9d7..1e85a66a 100644
--- a/cmd/digger/main_test.go
+++ b/cmd/digger/main_test.go
@@ -889,7 +889,7 @@ func TestGitHubNewPullRequestContext(t *testing.T) {
 		PrNumber:  prNumber,
 	}
 	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.Workflow{})
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, context.Repository, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, policyChecker, "")
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, context.Repository, context.Repository, eventName, prNumber, prManager, prManager, lock, reporter, planStorage, policyChecker, "")
 
 	assert.NoError(t, err)
 	if err != nil {
@@ -919,7 +919,7 @@ func TestGitHubNewCommentContext(t *testing.T) {
 	policyChecker := &utils.MockPolicyChecker{}
 
 	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.Workflow{})
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, context.Repository, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, policyChecker, "")
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, context.Repository, context.Repository, eventName, prNumber, prManager, prManager, lock, reporter, planStorage, policyChecker, "")
 	assert.NoError(t, err)
 	if err != nil {
 		fmt.Println(err)
diff --git a/pkg/aws/envprovider/envprovider_test.go b/pkg/aws/envprovider/envprovider_test.go
deleted file mode 100644
index 691d75ac..00000000
--- a/pkg/aws/envprovider/envprovider_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package envprovider
-
-import (
-	"os"
-	"testing"
-
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestRetrieve(t *testing.T) {
-	t.Parallel()
-	tests := map[string]struct {
-		key    string
-		secret string
-	}{
-		"digger prefix": {key: "DIGGER_AWS_ACCESS_KEY_ID", secret: "DIGGER_AWS_SECRET_ACCESS_KEY"},
-		"no prefix":     {key: "AWS_ACCESS_KEY_ID", secret: "AWS_SECRET_ACCESS_KEY"},
-		"other":         {key: "AWS_ACCESS_KEY", secret: "AWS_SECRET_KEY"},
-	}
-
-	for name, tc := range tests {
-		t.Run(name, func(t *testing.T) {
-			os.Setenv(tc.key, "key")
-			os.Setenv(tc.secret, "secret")
-			e := EnvProvider{}
-			act, _ := e.Retrieve()
-			exp := credentials.Value(credentials.Value{AccessKeyID: "key", SecretAccessKey: "secret", SessionToken: "", ProviderName: "DiggerEnvProvider"})
-			assert.Equal(t, exp, act)
-		})
-	}
-}
diff --git a/pkg/digger/digger_test.go b/pkg/digger/digger_test.go
index e3465833..1fe30dec 100644
--- a/pkg/digger/digger_test.go
+++ b/pkg/digger/digger_test.go
@@ -173,6 +173,25 @@ func (m *MockPlanStorage) PlanExists(storedPlanFilePath string) (bool, error) {
 	return false, nil
 }
 
+type MockPlanPathProvider struct {
+	Commands []RunInfo
+}
+
+func (m MockPlanPathProvider) PlanFileName() string {
+	m.Commands = append(m.Commands, RunInfo{"PlanFileName", "", time.Now()})
+	return "plan"
+}
+
+func (m MockPlanPathProvider) LocalPlanFilePath() string {
+	m.Commands = append(m.Commands, RunInfo{"LocalPlanFilePath", "", time.Now()})
+	return "plan"
+}
+
+func (m MockPlanPathProvider) StoredPlanFilePath() string {
+	m.Commands = append(m.Commands, RunInfo{"StoredPlanFilePath", "", time.Now()})
+	return "plan"
+}
+
 func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 
 	commandRunner := &MockCommandRunner{}
@@ -185,6 +204,7 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 		PrNumber:       1,
 		ReportStrategy: &reporting.MultipleCommentsStrategy{},
 	}
+	planPathProvider := &MockPlanPathProvider{}
 	executor := execution.DiggerExecutor{
 		ApplyStage: &models.Stage{
 			Steps: []models.Step{
@@ -209,15 +229,15 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 		CommandRunner:     commandRunner,
 		TerraformExecutor: terraformExecutor,
 		Reporter:          reporter,
-		ProjectLock:       lock,
 		PlanStorage:       planStorage,
+		PlanPathProvider:  planPathProvider,
 	}
 
 	executor.Apply()
 
-	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage)
+	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage, planPathProvider)
 
-	assert.Equal(t, []string{"RetrievePlan #.tfplan", "Lock ", "Init ", "Apply ", "LockId ", "PublishComment 1 <details><summary>Apply for <b></b></summary>\n  \n```terraform\n\n  ```\n</details>", "LockId ", "Run   echo"}, commandStrings)
+	assert.Equal(t, []string{"RetrievePlan plan", "Init ", "Apply ", "PublishComment 1 <details><summary>Apply for <b>#</b></summary>\n  \n```terraform\n\n  ```\n</details>", "Run   echo"}, commandStrings)
 }
 
 func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
@@ -230,6 +250,7 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
 		CiService: prManager,
 		PrNumber:  1,
 	}
+	planPathProvider := &MockPlanPathProvider{}
 
 	executor := execution.DiggerExecutor{
 		ApplyStage: &models.Stage{},
@@ -255,18 +276,18 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
 		CommandRunner:     commandRunner,
 		TerraformExecutor: terraformExecutor,
 		Reporter:          reporter,
-		ProjectLock:       lock,
 		PlanStorage:       planStorage,
+		PlanPathProvider:  planPathProvider,
 	}
 
 	executor.Plan()
 
-	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage)
+	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage, planPathProvider)
 
-	assert.Equal(t, []string{"Lock ", "Init ", "Plan -out #.tfplan", "PlanExists #.tfplan", "StorePlan #.tfplan", "LockId ", "Run   echo"}, commandStrings)
+	assert.Equal(t, []string{"Init ", "Plan -out plan", "PlanExists plan", "StorePlan plan", "Run   echo"}, commandStrings)
 }
 
-func allCommandsInOrderWithParams(terraformExecutor *MockTerraformExecutor, commandRunner *MockCommandRunner, prManager *MockPRManager, lock *MockProjectLock, planStorage *MockPlanStorage) []string {
+func allCommandsInOrderWithParams(terraformExecutor *MockTerraformExecutor, commandRunner *MockCommandRunner, prManager *MockPRManager, lock *MockProjectLock, planStorage *MockPlanStorage, planPathProvider *MockPlanPathProvider) []string {
 	var commands []RunInfo
 	for _, command := range terraformExecutor.Commands {
 		commands = append(commands, command)
@@ -283,6 +304,9 @@ func allCommandsInOrderWithParams(terraformExecutor *MockTerraformExecutor, comm
 	for _, command := range planStorage.Commands {
 		commands = append(commands, command)
 	}
+	for _, command := range planPathProvider.Commands {
+		commands = append(commands, command)
+	}
 
 	sort.Slice(commands, func(i, j int) bool {
 		return commands[i].Timestamp.Before(commands[j].Timestamp)
diff --git a/pkg/integration/integration_test.go b/pkg/integration/integration_test.go
index 53472fc0..5b9f025a 100644
--- a/pkg/integration/integration_test.go
+++ b/pkg/integration/integration_test.go
@@ -45,7 +45,7 @@ func getProjectLockForTests() (error, *locking.PullRequestLock) {
 	ghToken := "token"
 	githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner)
 	reporter := reporting.CiReporter{
-		CiService:      githubPrService,
+		CiService:      &githubPrService,
 		PrNumber:       1,
 		ReportStrategy: &reporting.MultipleCommentsStrategy{},
 	}
@@ -53,7 +53,7 @@ func getProjectLockForTests() (error, *locking.PullRequestLock) {
 	projectLock := &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		Reporter:         &reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "test_dynamodb_lock",
 		ProjectNamespace: repoOwner + "/" + repositoryName,
 	}
@@ -394,7 +394,7 @@ func TestHappyPath(t *testing.T) {
 	assert.Equal(t, "pull_request", parsedNewPullRequestContext.EventName)
 
 	//  new pr should lock the project
-	impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err := dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
@@ -408,19 +408,19 @@ func TestHappyPath(t *testing.T) {
 	}
 
 	reporter := &reporting.CiReporter{
-		CiService: githubPrService,
+		CiService: &githubPrService,
 		PrNumber:  prNumber,
 	}
 
 	diggerProjectNamespace := repoOwner + "/" + repositoryName
 
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock := &locking.PullRequestLock{
 		InternalLock:     lock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "dev",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -437,11 +437,11 @@ func TestHappyPath(t *testing.T) {
 	repositoryName = parsedDiggerPlanCommentContext.Repository
 
 	// 'digger plan' comment should trigger terraform execution
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	println("--- digger apply comment ---")
@@ -451,17 +451,17 @@ func TestHappyPath(t *testing.T) {
 	repositoryName = parsedDiggerApplyCommentContext.Repository
 
 	// 'digger apply' comment should trigger terraform execution and unlock the project
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "dev",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -475,17 +475,17 @@ func TestHappyPath(t *testing.T) {
 	repoOwner = parsedDiggerUnlockCommentContext.RepositoryOwner
 	repositoryName = parsedDiggerUnlockCommentContext.Repository
 
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "dev",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -554,7 +554,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	assert.Equal(t, "pull_request", parsedNewPullRequestContext.EventName)
 
 	// no files changed, no locks
-	impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err := dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
@@ -569,16 +569,16 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	}
 
 	reporter := &reporting.CiReporter{
-		CiService: githubPrService,
+		CiService: &githubPrService,
 		PrNumber:  prNumber,
 	}
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock := &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "digger_demo",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -594,11 +594,11 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	repositoryName = parsedDiggerPlanCommentContext.Repository
 
 	// 'digger plan' comment should trigger terraform execution
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	println("--- digger apply comment ---")
@@ -608,17 +608,17 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	repositoryName = parsedDiggerApplyCommentContext.Repository
 
 	// 'digger apply' comment should trigger terraform execution and unlock the project
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock = &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "digger_demo",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -632,17 +632,17 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	repoOwner = parsedDiggerUnlockCommentContext.RepositoryOwner
 	repositoryName = parsedDiggerUnlockCommentContext.Repository
 
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, &dynamoDbLock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock = &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "digger_demo",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -775,7 +775,7 @@ workflows:
 	assert.Equal(t, "pull_request", parsedNewPullRequestContext.EventName)
 
 	//  new pr should lock the project
-	impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err := dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err := dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
@@ -790,16 +790,16 @@ workflows:
 	}
 
 	reporter := &reporting.CiReporter{
-		CiService: githubPrService,
+		CiService: &githubPrService,
 		PrNumber:  prNumber,
 	}
 
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock := &locking.PullRequestLock{
 		InternalLock:     lock,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "dev",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -816,11 +816,11 @@ workflows:
 	repositoryName = parsedDiggerPlanCommentContext.Repository
 
 	// 'digger plan' comment should trigger terraform execution
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	println("--- digger apply comment ---")
@@ -830,17 +830,17 @@ workflows:
 	repositoryName = parsedDiggerApplyCommentContext.Repository
 
 	// 'digger apply' comment should trigger terraform execution and unlock the project
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "dev",
 		ProjectNamespace: diggerProjectNamespace,
 	}
@@ -853,17 +853,17 @@ workflows:
 	repoOwner = parsedDiggerUnlockCommentContext.RepositoryOwner
 	repositoryName = parsedDiggerUnlockCommentContext.Repository
 
-	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
+	impactedProjects, requestedProject, prNumber, err = dg_github.ProcessGitHubEvent(ghEvent, diggerConfig, &githubPrService)
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, nil, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, &depGraph, diggerProjectNamespace, repoOwner, eventName, prNumber, &githubPrService, &githubPrService, lock, reporter, planStorage, nil, dir)
 	assert.NoError(t, err)
 
 	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		Reporter:         reporter,
-		CIService:        githubPrService,
+		CIService:        &githubPrService,
 		ProjectName:      "dev",
 		ProjectNamespace: diggerProjectNamespace,
 	}