Repository: diggerhq/digger
Pull request #321 (2023-06-06 11:25:56)

refactor digger executor
to be considered core digger executor shouldn't know about existence of CIs to be agnostic of them


Patch:
diff --git a/cmd/digger/main.go b/cmd/digger/main.go
index a5f29529..54753bf0 100644
--- a/cmd/digger/main.go
+++ b/cmd/digger/main.go
@@ -11,6 +11,7 @@ import (
 	"digger/pkg/gitlab"
 	"digger/pkg/locking"
 	"digger/pkg/models"
+	"digger/pkg/reporting"
 	"digger/pkg/storage"
 	"digger/pkg/usage"
 	"digger/pkg/utils"
@@ -94,7 +95,12 @@ func gitHubCI(lock locking.Lock) {
 
 	planStorage := newPlanStorage(ghToken, repoOwner, repositoryName, githubActor, prNumber)
 
-	allAppliesSuccessful, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, parsedGhContext.Repository, githubActor, eventName, prNumber, githubPrService, lock, planStorage, "")
+	reporter := &reporting.CiReporter{
+		CiService: githubPrService,
+		PrNumber:  prNumber,
+	}
+
+	allAppliesSuccessful, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, parsedGhContext.Repository, githubActor, eventName, prNumber, githubPrService, lock, reporter, planStorage, "")
 	if err != nil {
 		reportErrorAndExit(githubActor, fmt.Sprintf("Failed to run commands. %s", err), 8)
 	}
@@ -179,8 +185,11 @@ func gitLabCI(lock locking.Lock) {
 
 	diggerProjectNamespace := gitLabContext.ProjectNamespace + "/" + gitLabContext.ProjectName
 	planStorage := newPlanStorage("", "", "", gitLabContext.GitlabUserName, *gitLabContext.MergeRequestIId)
-
-	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, gitLabContext.GitlabUserName, gitLabContext.EventType.String(), *gitLabContext.MergeRequestIId, gitlabService, lock, planStorage, currentDir)
+	reporter := &reporting.CiReporter{
+		CiService: gitlabService,
+		PrNumber:  *gitLabContext.MergeRequestIId,
+	}
+	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, gitLabContext.GitlabUserName, gitLabContext.EventType.String(), *gitLabContext.MergeRequestIId, gitlabService, lock, reporter, planStorage, currentDir)
 
 	if err != nil {
 		fmt.Printf("failed to execute command, %v", err)
@@ -254,7 +263,11 @@ func azureCI(lock locking.Lock) {
 	var planStorage storage.PlanStorage
 	diggerProjectNamespace := parsedAzureContext.BaseUrl + "/" + parsedAzureContext.ProjectName
 
-	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, parsedAzureContext.BaseUrl, parsedAzureContext.EventType, prNumber, azureService, lock, planStorage, currentDir)
+	reporter := &reporting.CiReporter{
+		CiService: azureService,
+		PrNumber:  prNumber,
+	}
+	allAppliesSuccess, atLeastOneApply, err := digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, parsedAzureContext.BaseUrl, parsedAzureContext.EventType, prNumber, azureService, lock, reporter, planStorage, currentDir)
 	if err != nil {
 		reportErrorAndExit(parsedAzureContext.BaseUrl, fmt.Sprintf("Failed to run commands. %s", err), 8)
 	}
diff --git a/pkg/digger/digger.go b/pkg/digger/digger.go
index d3986f67..75b59366 100644
--- a/pkg/digger/digger.go
+++ b/pkg/digger/digger.go
@@ -6,6 +6,7 @@ import (
 	"digger/pkg/configuration"
 	"digger/pkg/locking"
 	"digger/pkg/models"
+	"digger/pkg/reporting"
 	"digger/pkg/storage"
 	"digger/pkg/terraform"
 	"digger/pkg/usage"
@@ -22,15 +23,16 @@ import (
 	"time"
 )
 
-func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNamespace string, requestedBy string, eventName string, prNumber int, ciService ci.CIService, lock locking.Lock, planStorage storage.PlanStorage, workingDir string) (bool, bool, error) {
+func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNamespace string, requestedBy string, eventName string, prNumber int, ciService ci.CIService, lock locking.Lock, reporter reporting.Reporter, planStorage storage.PlanStorage, workingDir string) (bool, bool, error) {
 	appliesPerProject := make(map[string]bool)
 	for _, projectCommands := range commandsPerProject {
 		for _, command := range projectCommands.Commands {
-			projectLock := &locking.ProjectLockImpl{
+			projectLock := &locking.PullRequestLock{
 				InternalLock:     lock,
 				CIService:        ciService,
 				ProjectName:      projectCommands.ProjectName,
 				ProjectNamespace: projectNamespace,
+				PrNumber:         prNumber,
 			}
 
 			var terraformExecutor terraform.TerraformExecutor
@@ -52,7 +54,7 @@ func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNa
 				projectCommands.PlanStage,
 				commandRunner,
 				terraformExecutor,
-				ciService,
+				reporter,
 				projectLock,
 				planStorage,
 			}
@@ -60,7 +62,7 @@ func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNa
 			case "digger plan":
 				usage.SendUsageRecord(requestedBy, eventName, "plan")
 				ciService.SetStatus(prNumber, "pending", projectCommands.ProjectName+"/plan")
-				planPerformed, err := diggerExecutor.Plan(prNumber)
+				planPerformed, err := diggerExecutor.Plan()
 				if err != nil {
 					log.Printf("Failed to run digger plan command. %v", err)
 					ciService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/plan")
@@ -72,24 +74,39 @@ func RunCommandsPerProject(commandsPerProject []models.ProjectCommand, projectNa
 				appliesPerProject[projectCommands.ProjectName] = false
 				usage.SendUsageRecord(requestedBy, eventName, "apply")
 				ciService.SetStatus(prNumber, "pending", projectCommands.ProjectName+"/apply")
-				applyPerformed, err := diggerExecutor.Apply(prNumber)
+
+				// this might go into some sort of "appliability" plugin later
+				isMergeable, err := ciService.IsMergeable(prNumber)
 				if err != nil {
-					log.Printf("Failed to run digger apply command. %v", err)
-					ciService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/apply")
-					return false, false, fmt.Errorf("failed to run digger apply command. %v", err)
-				} else if applyPerformed {
-					ciService.SetStatus(prNumber, "success", projectCommands.ProjectName+"/apply")
-					appliesPerProject[projectCommands.ProjectName] = true
+					return false, false, fmt.Errorf("error validating is PR is mergeable: %v", err)
+				}
+				if !isMergeable {
+					comment := "Cannot perform Apply since the PR is not currently mergeable."
+					err = ciService.PublishComment(prNumber, comment)
+					if err != nil {
+						fmt.Printf("error publishing comment: %v", err)
+					}
+					return false, false, nil
+				} else {
+					applyPerformed, err := diggerExecutor.Apply()
+					if err != nil {
+						log.Printf("Failed to run digger apply command. %v", err)
+						ciService.SetStatus(prNumber, "failure", projectCommands.ProjectName+"/apply")
+						return false, false, fmt.Errorf("failed to run digger apply command. %v", err)
+					} else if applyPerformed {
+						ciService.SetStatus(prNumber, "success", projectCommands.ProjectName+"/apply")
+						appliesPerProject[projectCommands.ProjectName] = true
+					}
 				}
 			case "digger unlock":
 				usage.SendUsageRecord(requestedBy, eventName, "unlock")
-				err := diggerExecutor.Unlock(prNumber)
+				err := diggerExecutor.Unlock()
 				if err != nil {
 					return false, false, fmt.Errorf("failed to unlock project. %v", err)
 				}
 			case "digger lock":
 				usage.SendUsageRecord(requestedBy, eventName, "lock")
-				err := diggerExecutor.Lock(prNumber)
+				err := diggerExecutor.Lock()
 				if err != nil {
 					return false, false, fmt.Errorf("failed to lock project. %v", err)
 				}
@@ -147,7 +164,7 @@ type DiggerExecutor struct {
 	PlanStage         *configuration.Stage
 	CommandRunner     CommandRun
 	TerraformExecutor terraform.TerraformExecutor
-	CIService         ci.CIService
+	Reporter          reporting.Reporter
 	ProjectLock       locking.ProjectLock
 	PlanStorage       storage.PlanStorage
 }
@@ -209,8 +226,8 @@ func (d DiggerExecutor) storedPlanFilePath() string {
 	return path.Join(d.ProjectNamespace, d.planFileName())
 }
 
-func (d DiggerExecutor) Plan(prNumber int) (bool, error) {
-	locked, err := d.ProjectLock.Lock(prNumber)
+func (d DiggerExecutor) Plan() (bool, error) {
+	locked, err := d.ProjectLock.Lock()
 	if err != nil {
 		return false, fmt.Errorf("error locking project: %v", err)
 	}
@@ -264,7 +281,7 @@ func (d DiggerExecutor) Plan(prNumber int) (bool, error) {
 				}
 				plan := cleanupTerraformPlan(isNonEmptyPlan, err, stdout, stderr)
 				comment := utils.GetTerraformOutputAsCollapsibleComment("Plan for **"+d.ProjectLock.LockId()+"**", plan)
-				err = d.CIService.PublishComment(prNumber, comment)
+				err = d.Reporter.Report(comment)
 				if err != nil {
 					fmt.Printf("error publishing comment: %v", err)
 				}
@@ -287,7 +304,7 @@ func (d DiggerExecutor) Plan(prNumber int) (bool, error) {
 	return false, nil
 }
 
-func (d DiggerExecutor) Apply(prNumber int) (bool, error) {
+func (d DiggerExecutor) Apply() (bool, error) {
 	var plansFilename *string
 	if d.PlanStorage != nil {
 		var err error
@@ -297,85 +314,72 @@ func (d DiggerExecutor) Apply(prNumber int) (bool, error) {
 		}
 	}
 
-	isMergeable, err := d.CIService.IsMergeable(prNumber)
+	locked, err := d.ProjectLock.Lock()
+
 	if err != nil {
-		return false, fmt.Errorf("error validating is PR is mergeable: %v", err)
+		return false, fmt.Errorf("error locking project: %v", err)
 	}
-	if !isMergeable {
-		comment := "Cannot perform Apply since the PR is not currently mergeable."
-		err = d.CIService.PublishComment(prNumber, comment)
-		if err != nil {
-			fmt.Printf("error publishing comment: %v", err)
-		}
-		return false, nil
-	} else {
-		locked, err := d.ProjectLock.Lock(prNumber)
 
-		if err != nil {
-			return false, fmt.Errorf("error locking project: %v", err)
-		}
+	if locked {
+		var applySteps []configuration.Step
 
-		if locked {
-			var applySteps []configuration.Step
+		if d.ApplyStage != nil {
+			applySteps = d.ApplyStage.Steps
+		} else {
+			applySteps = []configuration.Step{
+				{
+					Action: "init",
+				},
+				{
+					Action: "apply",
+				},
+			}
+		}
 
-			if d.ApplyStage != nil {
-				applySteps = d.ApplyStage.Steps
-			} else {
-				applySteps = []configuration.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)
 				}
 			}
-
-			for _, step := range applySteps {
-				if step.Action == "init" {
-					_, _, err := d.TerraformExecutor.Init(step.ExtraArgs, d.StateEnvVars)
-					if err != nil {
-						return false, fmt.Errorf("error running init: %v", err)
-					}
+			if step.Action == "apply" {
+				stdout, stderr, err := d.TerraformExecutor.Apply(step.ExtraArgs, plansFilename, d.CommandEnvVars)
+				applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
+				comment := utils.GetTerraformOutputAsCollapsibleComment("Apply for **"+d.ProjectLock.LockId()+"**", applyOutput)
+				commentErr := d.Reporter.Report(comment)
+				if commentErr != nil {
+					fmt.Printf("error publishing comment: %v", err)
 				}
-				if step.Action == "apply" {
-					stdout, stderr, err := d.TerraformExecutor.Apply(step.ExtraArgs, plansFilename, d.CommandEnvVars)
-					applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
-					comment := utils.GetTerraformOutputAsCollapsibleComment("Apply for **"+d.ProjectLock.LockId()+"**", applyOutput)
-					commentErr := d.CIService.PublishComment(prNumber, comment)
+				if err != nil {
+					commentErr = d.Reporter.Report("Error during applying.")
 					if commentErr != nil {
 						fmt.Printf("error publishing comment: %v", err)
 					}
-					if err != nil {
-						commentErr = d.CIService.PublishComment(prNumber, "Error during applying.")
-						if commentErr != nil {
-							fmt.Printf("error publishing comment: %v", err)
-						}
-						return false, fmt.Errorf("error executing apply: %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)
-					}
+			}
+			if step.Action == "run" {
+				var commands []string
+				if os.Getenv("ACTIVATE_VENV") == "true" {
+					commands = append(commands, fmt.Sprintf("source %v/.venv/bin/activate", os.Getenv("GITHUB_WORKSPACE")))
+				}
+				commands = append(commands, step.Value)
+				log.Printf("Running %v for **%v**\n", step.Value, d.ProjectLock.LockId())
+				_, _, err := d.CommandRunner.Run(d.ProjectPath, step.Shell, commands)
+				if err != nil {
+					return false, fmt.Errorf("error running command: %v", err)
 				}
 			}
-			return true, nil
-		} else {
-			return false, nil
 		}
+		return true, nil
+	} else {
+		return false, nil
 	}
 }
 
-func (d DiggerExecutor) Unlock(prNumber int) error {
-	err := d.ProjectLock.ForceUnlock(prNumber)
+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)
 	}
@@ -391,8 +395,8 @@ func (d DiggerExecutor) Unlock(prNumber int) error {
 	return nil
 }
 
-func (d DiggerExecutor) Lock(prNumber int) error {
-	_, err := d.ProjectLock.Lock(prNumber)
+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)
 	}
diff --git a/pkg/locking/locking.go b/pkg/locking/locking.go
index bd969e74..6007b4ee 100644
--- a/pkg/locking/locking.go
+++ b/pkg/locking/locking.go
@@ -22,11 +22,12 @@ import (
 	"github.com/aws/aws-sdk-go/service/dynamodb"
 )
 
-type ProjectLockImpl struct {
+type PullRequestLock struct {
 	InternalLock     Lock
 	CIService        ci.CIService
 	ProjectName      string
 	ProjectNamespace string
+	PrNumber         int
 }
 
 type Lock interface {
@@ -51,17 +52,17 @@ func (noOpLock *NoOpLock) GetLock(resource string) (*int, error) {
 }
 
 type ProjectLock interface {
-	Lock(prNumber int) (bool, error)
-	Unlock(prNumber int) (bool, error)
-	ForceUnlock(prNumber int) error
+	Lock() (bool, error)
+	Unlock() (bool, error)
+	ForceUnlock() error
 	LockId() string
 }
 
-func (projectLock *ProjectLockImpl) Lock(prNumber int) (bool, error) {
+func (projectLock *PullRequestLock) Lock() (bool, error) {
 	lockId := projectLock.LockId()
 	fmt.Printf("Lock %s\n", lockId)
 
-	noHangingLocks, err := projectLock.verifyNoHangingLocks(prNumber)
+	noHangingLocks, err := projectLock.verifyNoHangingLocks()
 
 	if err != nil {
 		return false, err
@@ -77,16 +78,16 @@ func (projectLock *ProjectLockImpl) Lock(prNumber int) (bool, error) {
 		return false, err
 	}
 	if existingLockTransactionId != nil {
-		if *existingLockTransactionId == prNumber {
+		if *existingLockTransactionId == projectLock.PrNumber {
 			return true, nil
 		} else {
 			transactionIdStr := strconv.Itoa(*existingLockTransactionId)
 			comment := "Project " + projectLock.projectId() + " locked by another PR #" + transactionIdStr + " (failed to acquire lock " + projectLock.ProjectNamespace + "). The locking plan must be applied or discarded before future plans can execute"
-			projectLock.CIService.PublishComment(prNumber, comment)
+			projectLock.CIService.PublishComment(projectLock.PrNumber, comment)
 			return false, nil
 		}
 	}
-	lockAcquired, err := projectLock.InternalLock.Lock(prNumber, lockId)
+	lockAcquired, err := projectLock.InternalLock.Lock(projectLock.PrNumber, lockId)
 	if err != nil {
 		return false, err
 	}
@@ -94,15 +95,15 @@ func (projectLock *ProjectLockImpl) Lock(prNumber int) (bool, error) {
 	_, isNoOpLock := projectLock.InternalLock.(*NoOpLock)
 
 	if lockAcquired && !isNoOpLock {
-		comment := "Project " + projectLock.projectId() + " has been locked by PR #" + strconv.Itoa(prNumber)
-		projectLock.CIService.PublishComment(prNumber, comment)
-		println("project " + projectLock.projectId() + " locked successfully. PR # " + strconv.Itoa(prNumber))
+		comment := "Project " + projectLock.projectId() + " has been locked by PR #" + strconv.Itoa(projectLock.PrNumber)
+		projectLock.CIService.PublishComment(projectLock.PrNumber, comment)
+		println("project " + projectLock.projectId() + " locked successfully. PR # " + strconv.Itoa(projectLock.PrNumber))
 
 	}
 	return lockAcquired, nil
 }
 
-func (projectLock *ProjectLockImpl) verifyNoHangingLocks(prNumber int) (bool, error) {
+func (projectLock *PullRequestLock) verifyNoHangingLocks() (bool, error) {
 	lockId := projectLock.LockId()
 	transactionId, err := projectLock.InternalLock.GetLock(lockId)
 
@@ -111,7 +112,7 @@ func (projectLock *ProjectLockImpl) verifyNoHangingLocks(prNumber int) (bool, er
 	}
 
 	if transactionId != nil {
-		if *transactionId != prNumber {
+		if *transactionId != projectLock.PrNumber {
 			isPrClosed, err := projectLock.CIService.IsClosed(*transactionId)
 			if err != nil {
 				return false, fmt.Errorf("failed to check if PR holding a lock is closed: %w", err)
@@ -125,7 +126,7 @@ func (projectLock *ProjectLockImpl) verifyNoHangingLocks(prNumber int) (bool, er
 			}
 			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.CIService.PublishComment(prNumber, comment)
+			projectLock.CIService.PublishComment(projectLock.PrNumber, comment)
 			return false, nil
 		}
 		return true, nil
@@ -133,7 +134,7 @@ func (projectLock *ProjectLockImpl) verifyNoHangingLocks(prNumber int) (bool, er
 	return true, nil
 }
 
-func (projectLock *ProjectLockImpl) Unlock(prNumber int) (bool, error) {
+func (projectLock *PullRequestLock) Unlock() (bool, error) {
 	lockId := projectLock.LockId()
 	fmt.Printf("Unlock %s\n", lockId)
 	lock, err := projectLock.InternalLock.GetLock(lockId)
@@ -143,14 +144,14 @@ func (projectLock *ProjectLockImpl) Unlock(prNumber int) (bool, error) {
 
 	if lock != nil {
 		transactionId := *lock
-		if prNumber == transactionId {
+		if projectLock.PrNumber == transactionId {
 			lockReleased, err := projectLock.InternalLock.Unlock(lockId)
 			if err != nil {
 				return false, err
 			}
 			if lockReleased {
 				comment := "Project unlocked (" + projectLock.projectId() + ")."
-				projectLock.CIService.PublishComment(prNumber, comment)
+				projectLock.CIService.PublishComment(projectLock.PrNumber, comment)
 				println("Project unlocked")
 				return true, nil
 			}
@@ -159,7 +160,7 @@ func (projectLock *ProjectLockImpl) Unlock(prNumber int) (bool, error) {
 	return false, nil
 }
 
-func (projectLock *ProjectLockImpl) ForceUnlock(prNumber int) error {
+func (projectLock *PullRequestLock) ForceUnlock() error {
 	lockId := projectLock.LockId()
 	fmt.Printf("ForceUnlock %s\n", lockId)
 	lock, err := projectLock.InternalLock.GetLock(lockId)
@@ -174,7 +175,7 @@ func (projectLock *ProjectLockImpl) ForceUnlock(prNumber int) error {
 
 		if lockReleased {
 			comment := "Project unlocked (" + projectLock.projectId() + ")."
-			projectLock.CIService.PublishComment(prNumber, comment)
+			projectLock.CIService.PublishComment(projectLock.PrNumber, comment)
 			println("Project unlocked")
 		}
 		return nil
@@ -182,11 +183,11 @@ func (projectLock *ProjectLockImpl) ForceUnlock(prNumber int) error {
 	return nil
 }
 
-func (projectLock *ProjectLockImpl) projectId() string {
+func (projectLock *PullRequestLock) projectId() string {
 	return projectLock.ProjectNamespace + "#" + projectLock.ProjectName
 }
 
-func (projectLock *ProjectLockImpl) LockId() string {
+func (projectLock *PullRequestLock) LockId() string {
 	return projectLock.ProjectNamespace + "#" + projectLock.ProjectName
 }
 
diff --git a/pkg/reporting/reporting.go b/pkg/reporting/reporting.go
new file mode 100644
index 00000000..3544af76
--- /dev/null
+++ b/pkg/reporting/reporting.go
@@ -0,0 +1,16 @@
+package reporting
+
+import "digger/pkg/ci"
+
+type Reporter interface {
+	Report(report string) error
+}
+
+type CiReporter struct {
+	CiService ci.CIService
+	PrNumber  int
+}
+
+func (ciReporter *CiReporter) Report(report string) error {
+	return ciReporter.CiService.PublishComment(ciReporter.PrNumber, report)
+}


Test patch:
diff --git a/cmd/digger/main_test.go b/cmd/digger/main_test.go
index 500ca663..56276130 100644
--- a/cmd/digger/main_test.go
+++ b/cmd/digger/main_test.go
@@ -5,6 +5,7 @@ import (
 	"digger/pkg/digger"
 	"digger/pkg/github"
 	"digger/pkg/github/models"
+	"digger/pkg/reporting"
 	"digger/pkg/utils"
 	"fmt"
 	"testing"
@@ -879,8 +880,12 @@ func TestGitHubNewPullRequestContext(t *testing.T) {
 
 	impactedProjects, requestedProject, prNumber, err := github.ProcessGitHubEvent(ghEvent, &diggerConfig, prManager)
 
+	reporter := &reporting.CiReporter{
+		CiService: prManager,
+		PrNumber:  prNumber,
+	}
 	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.Workflow{})
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, planStorage, "")
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, "")
 
 	assert.NoError(t, err)
 	if err != nil {
@@ -901,11 +906,14 @@ func TestGitHubNewCommentContext(t *testing.T) {
 	prManager := &utils.MockPullRequestManager{ChangedFiles: []string{"dev/test.tf"}}
 	planStorage := &utils.MockPlanStorage{}
 	impactedProjects, requestedProject, prNumber, err := github.ProcessGitHubEvent(ghEvent, &diggerConfig, prManager)
-
+	reporter := &reporting.CiReporter{
+		CiService: prManager,
+		PrNumber:  prNumber,
+	}
 	commandsToRunPerProject, _, err := github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, map[string]configuration.Workflow{})
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, planStorage, "")
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, "")
 
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, planStorage, "")
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, context.RepositoryOwner, context.Repository, eventName, prNumber, prManager, lock, reporter, planStorage, "")
 
 	assert.NoError(t, err)
 	if err != nil {
diff --git a/pkg/digger/digger_test.go b/pkg/digger/digger_test.go
index 23ff3afa..f7b86e24 100644
--- a/pkg/digger/digger_test.go
+++ b/pkg/digger/digger_test.go
@@ -2,6 +2,7 @@ package digger
 
 import (
 	"digger/pkg/configuration"
+	"digger/pkg/reporting"
 	"digger/pkg/utils"
 	"sort"
 	"strconv"
@@ -97,18 +98,18 @@ type MockProjectLock struct {
 	Commands []RunInfo
 }
 
-func (m *MockProjectLock) Lock(prNumber int) (bool, error) {
-	m.Commands = append(m.Commands, RunInfo{"Lock", strconv.Itoa(prNumber), time.Now()})
+func (m *MockProjectLock) Lock() (bool, error) {
+	m.Commands = append(m.Commands, RunInfo{"Lock", "", time.Now()})
 	return true, nil
 }
 
-func (m *MockProjectLock) Unlock(prNumber int) (bool, error) {
-	m.Commands = append(m.Commands, RunInfo{"Unlock", strconv.Itoa(prNumber), time.Now()})
+func (m *MockProjectLock) Unlock() (bool, error) {
+	m.Commands = append(m.Commands, RunInfo{"Unlock", "", time.Now()})
 	return true, nil
 }
 
-func (m *MockProjectLock) ForceUnlock(prNumber int) error {
-	m.Commands = append(m.Commands, RunInfo{"ForceUnlock", strconv.Itoa(prNumber), time.Now()})
+func (m *MockProjectLock) ForceUnlock() error {
+	m.Commands = append(m.Commands, RunInfo{"ForceUnlock", "", time.Now()})
 	return nil
 }
 
@@ -157,6 +158,10 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 	prManager := &MockPRManager{}
 	lock := &MockProjectLock{}
 	planStorage := &MockPlanStorage{}
+	reporter := &reporting.CiReporter{
+		CiService: prManager,
+		PrNumber:  1,
+	}
 	executor := DiggerExecutor{
 		ApplyStage: &configuration.Stage{
 			Steps: []configuration.Step{
@@ -180,16 +185,16 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 		PlanStage:         &configuration.Stage{},
 		CommandRunner:     commandRunner,
 		TerraformExecutor: terraformExecutor,
-		CIService:         prManager,
+		Reporter:          reporter,
 		ProjectLock:       lock,
 		PlanStorage:       planStorage,
 	}
 
-	executor.Apply(1)
+	executor.Apply()
 
 	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>", "LockId ", "Run   echo"}, commandStrings)
+	assert.Equal(t, []string{"RetrievePlan #.tfplan", "Lock ", "Init ", "Apply ", "LockId ", "PublishComment 1 <details>\n  <summary>Apply for ****</summary>\n\n  ```terraform\n\n  ```\n</details>", "LockId ", "Run   echo"}, commandStrings)
 }
 
 func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
@@ -198,7 +203,10 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
 	prManager := &MockPRManager{}
 	lock := &MockProjectLock{}
 	planStorage := &MockPlanStorage{}
-
+	reporter := &reporting.CiReporter{
+		CiService: prManager,
+		PrNumber:  1,
+	}
 	executor := DiggerExecutor{
 		ApplyStage: &configuration.Stage{},
 		PlanStage: &configuration.Stage{
@@ -222,16 +230,16 @@ func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
 		},
 		CommandRunner:     commandRunner,
 		TerraformExecutor: terraformExecutor,
-		CIService:         prManager,
+		Reporter:          reporter,
 		ProjectLock:       lock,
 		PlanStorage:       planStorage,
 	}
 
-	executor.Plan(1)
+	executor.Plan()
 
 	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock, planStorage)
 
-	assert.Equal(t, []string{"Lock 1", "Init ", "Plan -out #.tfplan", "PlanExists #.tfplan", "StorePlan #.tfplan", "LockId ", "PublishComment 1 <details>\n  <summary>Plan for ****</summary>\n\n  ```terraform\n\n  ```\n</details>", "LockId ", "Run   echo"}, commandStrings)
+	assert.Equal(t, []string{"Lock ", "Init ", "Plan -out #.tfplan", "PlanExists #.tfplan", "StorePlan #.tfplan", "LockId ", "PublishComment 1 <details>\n  <summary>Plan for ****</summary>\n\n  ```terraform\n\n  ```\n</details>", "LockId ", "Run   echo"}, 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 68b65625..cedc96a4 100644
--- a/pkg/integration/integration_test.go
+++ b/pkg/integration/integration_test.go
@@ -7,6 +7,7 @@ import (
 	"digger/pkg/digger"
 	dg_github "digger/pkg/github"
 	"digger/pkg/locking"
+	"digger/pkg/reporting"
 	"digger/pkg/storage"
 	"digger/pkg/terraform"
 	"digger/pkg/utils"
@@ -29,7 +30,7 @@ func SkipCI(t *testing.T) {
 	}
 }
 
-func getProjectLockForTests() (error, *locking.ProjectLockImpl) {
+func getProjectLockForTests() (error, *locking.PullRequestLock) {
 	sess, err := session.NewSessionWithOptions(session.Options{
 		Profile: "digger-test",
 		Config: awssdk.Config{
@@ -44,7 +45,7 @@ func getProjectLockForTests() (error, *locking.ProjectLockImpl) {
 	ghToken := "token"
 	githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner)
 
-	projectLock := &locking.ProjectLockImpl{
+	projectLock := &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		CIService:        githubPrService,
 		ProjectName:      "test_dynamodb_lock",
@@ -400,12 +401,17 @@ func TestHappyPath(t *testing.T) {
 		ZipManager:        zipManager,
 	}
 
+	reporter := &reporting.CiReporter{
+		CiService: githubPrService,
+		PrNumber:  prNumber,
+	}
+
 	diggerProjectNamespace := repoOwner + "/" + repositoryName
 
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock := &locking.ProjectLockImpl{
+	projectLock := &locking.PullRequestLock{
 		InternalLock:     lock,
 		CIService:        githubPrService,
 		ProjectName:      "dev",
@@ -428,7 +434,7 @@ func TestHappyPath(t *testing.T) {
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
 	println("--- digger apply comment ---")
@@ -442,10 +448,10 @@ func TestHappyPath(t *testing.T) {
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock = &locking.ProjectLockImpl{
+	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		CIService:        githubPrService,
 		ProjectName:      "dev",
@@ -465,10 +471,10 @@ func TestHappyPath(t *testing.T) {
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock = &locking.ProjectLockImpl{
+	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		CIService:        githubPrService,
 		ProjectName:      "dev",
@@ -552,10 +558,15 @@ func TestMultiEnvHappyPath(t *testing.T) {
 		PullRequestNumber: prNumber,
 		ZipManager:        zipManager,
 	}
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
+
+	reporter := &reporting.CiReporter{
+		CiService: githubPrService,
+		PrNumber:  prNumber,
+	}
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock := &locking.ProjectLockImpl{
+	projectLock := &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		CIService:        githubPrService,
 		ProjectName:      "digger_demo",
@@ -577,7 +588,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
 	println("--- digger apply comment ---")
@@ -591,10 +602,10 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock = &locking.ProjectLockImpl{
+	projectLock = &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		CIService:        githubPrService,
 		ProjectName:      "digger_demo",
@@ -614,10 +625,10 @@ func TestMultiEnvHappyPath(t *testing.T) {
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, &dynamoDbLock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock = &locking.ProjectLockImpl{
+	projectLock = &locking.PullRequestLock{
 		InternalLock:     &dynamoDbLock,
 		CIService:        githubPrService,
 		ProjectName:      "digger_demo",
@@ -765,10 +776,16 @@ workflows:
 		PullRequestNumber: prNumber,
 		ZipManager:        zipManager,
 	}
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+
+	reporter := &reporting.CiReporter{
+		CiService: githubPrService,
+		PrNumber:  prNumber,
+	}
+
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock := &locking.ProjectLockImpl{
+	projectLock := &locking.PullRequestLock{
 		InternalLock:     lock,
 		CIService:        githubPrService,
 		ProjectName:      "dev",
@@ -791,7 +808,7 @@ workflows:
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
 	println("--- digger apply comment ---")
@@ -805,10 +822,10 @@ workflows:
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock = &locking.ProjectLockImpl{
+	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		CIService:        githubPrService,
 		ProjectName:      "dev",
@@ -827,10 +844,10 @@ workflows:
 	assert.NoError(t, err)
 	commandsToRunPerProject, _, err = dg_github.ConvertGithubEventToCommands(ghEvent, impactedProjects, requestedProject, diggerConfig.Workflows)
 	assert.NoError(t, err)
-	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, planStorage, dir)
+	_, _, err = digger.RunCommandsPerProject(commandsToRunPerProject, diggerProjectNamespace, repoOwner, eventName, prNumber, githubPrService, lock, reporter, planStorage, dir)
 	assert.NoError(t, err)
 
-	projectLock = &locking.ProjectLockImpl{
+	projectLock = &locking.PullRequestLock{
 		InternalLock:     lock,
 		CIService:        githubPrService,
 		ProjectName:      "dev",
diff --git a/pkg/locking/locking_test.go b/pkg/locking/locking_test.go
index 255077e1..11c040dd 100644
--- a/pkg/locking/locking_test.go
+++ b/pkg/locking/locking_test.go
@@ -9,16 +9,25 @@ import (
 func TestLockingTwiceThrowsError(t *testing.T) {
 	mockDynamoDB := utils.MockLock{make(map[string]int)}
 	mockPrManager := utils.MockGithubPullrequestManager{}
-	pl := ProjectLockImpl{
+	pl := PullRequestLock{
 		InternalLock:     &mockDynamoDB,
 		CIService:        &mockPrManager,
 		ProjectName:      "a",
 		ProjectNamespace: "",
+		PrNumber:         1,
 	}
-	state1, err1 := pl.Lock(1)
+	state1, err1 := pl.Lock()
 	assert.True(t, state1)
 	assert.NoError(t, err1)
-	state2, err2 := pl.Lock(2)
+
+	pl2 := PullRequestLock{
+		InternalLock:     &mockDynamoDB,
+		CIService:        &mockPrManager,
+		ProjectName:      "a",
+		ProjectNamespace: "",
+		PrNumber:         2,
+	}
+	state2, err2 := pl2.Lock()
 	assert.False(t, state2)
 	// No error because the lock was not aquired
 	assert.NoError(t, err2)