diff --git a/cmd/digger/main.go b/cmd/digger/main.go index e5f506100..0ad34f5df 100644 --- a/cmd/digger/main.go +++ b/cmd/digger/main.go @@ -60,7 +60,7 @@ func gitHubCI(lock utils.Lock) { eventName := parsedGhContext.EventName splitRepositoryName := strings.Split(parsedGhContext.Repository, "/") repoOwner, repositoryName := splitRepositoryName[0], splitRepositoryName[1] - githubPrService := dg_github.NewGithubPullRequestService(ghToken, repositoryName, repoOwner) + githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner) impactedProjects, requestedProject, prNumber, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService) if err != nil { @@ -106,7 +106,9 @@ func gitHubCI(lock utils.Lock) { func gitLabCI(lock utils.Lock) { println("Using GitLab.") + projectNamespace := os.Getenv("CI_PROJECT_NAMESPACE") + projectName := os.Getenv("CI_PROJECT_NAME") gitlabToken := os.Getenv("GITLAB_TOKEN") if gitlabToken == "" { fmt.Println("GITLAB_TOKEN is empty") @@ -131,6 +133,12 @@ func gitLabCI(lock utils.Lock) { os.Exit(4) } + // it's ok to not have merge request info if it has been merged + if (gitLabContext.MergeRequestIId == nil || len(gitLabContext.OpenMergeRequests) == 0) && gitLabContext.EventType != "merge_request_merge" { + fmt.Println("No merge request found.") + os.Exit(0) + } + gitlabService, err := gitlab.NewGitLabService(gitlabToken, gitLabContext) if err != nil { fmt.Printf("failed to initialise GitLab service, %v", err) @@ -153,21 +161,33 @@ func gitLabCI(lock utils.Lock) { } println("GitLab event converted to commands successfully") + println("Digger commands to be executed:") for _, v := range commandsToRunPerProject { fmt.Printf("command: %s, project: %s\n", strings.Join(v.Commands, ", "), v.ProjectName) } - //planStorage := newPlanStorage(ghToken, repoOwner, repositoryName, prNumber) - var planStorage utils.PlanStorage + planStorage := newPlanStorage(gitlabToken, projectNamespace, projectName, *gitLabContext.MergeRequestIId) - result, err := gitlab.RunCommandsPerProject(commandsToRunPerProject, *gitLabContext, diggerConfig, gitlabService, lock, planStorage, currentDir) + allAppliesSuccess, err := gitlab.RunCommandsPerProject(commandsToRunPerProject, *gitLabContext, diggerConfig, gitlabService, lock, planStorage, currentDir) if err != nil { fmt.Printf("failed to execute command, %v", err) os.Exit(8) } - print(result) + + if diggerConfig.AutoMerge && allAppliesSuccess { + digger.MergePullRequest(gitlabService, *gitLabContext.MergeRequestIId) + println("Merge request changes has been applied successfully") + } println("Commands executed successfully") + + reportErrorAndExit(projectName, "Digger finished successfully", 0) + + defer func() { + if r := recover(); r != nil { + reportErrorAndExit(projectName, fmt.Sprintf("Panic occurred. %s", r), 1) + } + }() } /* @@ -218,7 +238,8 @@ func main() { func newPlanStorage(ghToken string, repoOwner string, repositoryName string, prNumber int) utils.PlanStorage { var planStorage utils.PlanStorage - if os.Getenv("PLAN_UPLOAD_DESTINATION") == "github" { + uploadDestination := strings.ToLower(os.Getenv("PLAN_UPLOAD_DESTINATION")) + if uploadDestination == "github" { zipManager := utils.Zipper{} planStorage = &utils.GithubPlanStorage{ Client: github.NewTokenClient(context.Background(), ghToken), @@ -227,9 +248,8 @@ func newPlanStorage(ghToken string, repoOwner string, repositoryName string, prN PullRequestNumber: prNumber, ZipManager: zipManager, } - } else if os.Getenv("PLAN_UPLOAD_DESTINATION") == "gcp" { + } else if uploadDestination == "gcp" { ctx, client := gcp.GetGoogleStorageClient() - bucketName := strings.ToLower(os.Getenv("GOOGLE_STORAGE_BUCKET")) if bucketName == "" { reportErrorAndExit(repoOwner, fmt.Sprintf("GOOGLE_STORAGE_BUCKET is not defined"), 9) @@ -240,7 +260,10 @@ func newPlanStorage(ghToken string, repoOwner string, repositoryName string, prN Bucket: bucket, Context: ctx, } + } else if uploadDestination == "gitlab" { + //TODO implement me } + return planStorage } diff --git a/pkg/ci/main.go b/pkg/ci/main.go index 76c2a8669..2adc3cfcd 100644 --- a/pkg/ci/main.go +++ b/pkg/ci/main.go @@ -3,6 +3,8 @@ package ci type CIService interface { GetChangedFiles(prNumber int) ([]string, error) PublishComment(prNumber int, comment string) + + // SetStatus set status of specified pull/merge request, status could be: "pending", "failure", "success" SetStatus(prNumber int, status string, statusContext string) error GetCombinedPullRequestStatus(prNumber int) (string, error) MergePullRequest(prNumber int) error diff --git a/pkg/configuration/digger_config.go b/pkg/configuration/digger_config.go index 7458a2582..9015843b7 100644 --- a/pkg/configuration/digger_config.go +++ b/pkg/configuration/digger_config.go @@ -195,11 +195,50 @@ func (s *Step) extract(stepMap map[string]interface{}, action string) { } } +// duplicate copied from digger.go +func defaultWorkflow() *Workflow { + return &Workflow{ + Configuration: &WorkflowConfiguration{ + OnCommitToDefault: []string{"digger unlock"}, + OnPullRequestPushed: []string{"digger plan"}, + OnPullRequestClosed: []string{"digger unlock"}, + }, + Plan: &Stage{ + Steps: []Step{ + { + Action: "init", ExtraArgs: []string{}, + }, + { + Action: "plan", ExtraArgs: []string{}, + }, + }, + }, + Apply: &Stage{ + Steps: []Step{ + { + Action: "init", ExtraArgs: []string{}, + }, + { + Action: "apply", ExtraArgs: []string{}, + }, + }, + }, + } +} + func ConvertDiggerYamlToConfig(diggerYaml *DiggerConfigYaml, workingDir string, walker DirWalker) (*DiggerConfig, error) { var diggerConfig DiggerConfig diggerConfig.AutoMerge = diggerYaml.AutoMerge - diggerConfig.Workflows = diggerYaml.Workflows + + if diggerYaml.Workflows != nil { + diggerConfig.Workflows = diggerYaml.Workflows + } else { + workflow := *defaultWorkflow() + diggerConfig.Workflows = make(map[string]Workflow) + diggerConfig.Workflows["default"] = workflow + } + diggerConfig.Projects = diggerYaml.Projects diggerConfig.CollectUsageData = diggerYaml.CollectUsageData diff --git a/pkg/github/github.go b/pkg/github/github.go index 4e4967cd4..08d806530 100644 --- a/pkg/github/github.go +++ b/pkg/github/github.go @@ -9,7 +9,7 @@ import ( "github.com/google/go-github/v51/github" ) -func NewGithubPullRequestService(ghToken string, repoName string, owner string) ci.CIService { +func NewGitHubService(ghToken string, repoName string, owner string) ci.CIService { client := github.NewTokenClient(context.Background(), ghToken) return &GithubService{ Client: client, diff --git a/pkg/gitlab/gitlab.go b/pkg/gitlab/gitlab.go index dea32a430..a4a27fbe0 100644 --- a/pkg/gitlab/gitlab.go +++ b/pkg/gitlab/gitlab.go @@ -30,9 +30,11 @@ type GitLabContext struct { ProjectNamespace string `env:"CI_PROJECT_NAMESPACE"` ProjectId *int `env:"CI_PROJECT_ID"` ProjectNamespaceId *int `env:"CI_PROJECT_NAMESPACE_ID"` + OpenMergeRequests []string `env:"CI_OPEN_MERGE_REQUESTS"` Token string `env:"GITLAB_TOKEN"` DiggerCommand string `env:"DIGGER_COMMAND"` DiscussionID string `env:"DISCUSSION_ID"` + IsMeargeable bool `env:"IS_MERGEABLE"` } type PipelineSourceType string @@ -88,8 +90,7 @@ func ProcessGitLabEvent(gitlabContext *GitLabContext, diggerConfig *configuratio var impactedProjects []configuration.Project if gitlabContext.MergeRequestIId == nil { - println("Merge Request ID is not found.") - return nil, nil + return nil, fmt.Errorf("value for 'Merge Request ID' parameter is not found") } mergeRequestId := gitlabContext.MergeRequestIId @@ -139,61 +140,58 @@ func (gitlabService GitLabService) PublishComment(mergeRequestID int, comment st mergeRequestIID := *gitlabService.Context.MergeRequestIId commentOpt := &go_gitlab.AddMergeRequestDiscussionNoteOptions{Body: &comment} - fmt.Printf("PublishComment mergeRequestID : %d, projectId: %d, mergeRequestIID: %d, \n", mergeRequestID, projectId, mergeRequestIID) + fmt.Printf("PublishComment mergeRequestID : %d, projectId: %d, mergeRequestIID: %d, discussionId: %s \n", mergeRequestID, projectId, mergeRequestIID, discussionId) - _, _, err := gitlabService.Client.Discussions.AddMergeRequestDiscussionNote(projectId, mergeRequestIID, discussionId, commentOpt) - if err != nil { - fmt.Printf("Failed to publish a comment. %v\n", err) - print(err.Error()) + if discussionId == "" { + commentOpt := &go_gitlab.CreateMergeRequestDiscussionOptions{Body: &comment} + discussion, _, err := gitlabService.Client.Discussions.CreateMergeRequestDiscussion(projectId, mergeRequestIID, commentOpt) + if err != nil { + fmt.Printf("Failed to publish a comment. %v\n", err) + print(err.Error()) + } + discussionId = discussion.ID + } else { + _, _, err := gitlabService.Client.Discussions.AddMergeRequestDiscussionNote(projectId, mergeRequestIID, discussionId, commentOpt) + if err != nil { + fmt.Printf("Failed to publish a comment. %v\n", err) + print(err.Error()) + } } } +// SetStatus GitLab implementation is using https://docs.gitlab.com/15.11/ee/api/status_checks.html (external status checks) +// https://docs.gitlab.com/ee/user/project/merge_requests/status_checks.html#add-a-status-check-service +// only supported by 'Ultimate' plan func (gitlabService GitLabService) SetStatus(mergeRequestID int, status string, statusContext string) error { //TODO implement me fmt.Printf("SetStatus: mergeRequest: %d, status: %s, statusContext: %s\n", mergeRequestID, status, statusContext) return nil - //panic("SetStatus: implement me") } func (gitlabService GitLabService) GetCombinedPullRequestStatus(mergeRequestID int) (string, error) { //TODO implement me - panic("GetCombinedPullRequestStatus: implement me") + return "success", nil } func (gitlabService GitLabService) MergePullRequest(mergeRequestID int) error { projectId := *gitlabService.Context.ProjectId mergeRequestIID := *gitlabService.Context.MergeRequestIId - opt := &go_gitlab.AcceptMergeRequestOptions{} + mergeWhenPipelineSucceeds := true + opt := &go_gitlab.AcceptMergeRequestOptions{MergeWhenPipelineSucceeds: &mergeWhenPipelineSucceeds} fmt.Printf("MergePullRequest mergeRequestID : %d, projectId: %d, mergeRequestIID: %d, \n", mergeRequestID, projectId, mergeRequestIID) _, _, err := gitlabService.Client.MergeRequests.AcceptMergeRequest(projectId, mergeRequestIID, opt) if err != nil { fmt.Printf("Failed to merge Merge Request. %v\n", err) - return err + return fmt.Errorf("Failed to merge Merge Request. %v\n", err) } return nil } func (gitlabService GitLabService) IsMergeable(mergeRequestID int) (bool, error) { - projectId := *gitlabService.Context.ProjectId - mergeRequestIID := *gitlabService.Context.MergeRequestIId - - fmt.Printf("IsMergeable mergeRequestIID : %d, projectId: %d \n", mergeRequestIID, projectId) - opt := &go_gitlab.GetMergeRequestsOptions{} - - mergeRequest, _, err := gitlabService.Client.MergeRequests.GetMergeRequest(projectId, mergeRequestIID, opt) - - if err != nil { - fmt.Printf("Failed to get a MergeRequest: %d, %v \n", mergeRequestIID, err) - print(err.Error()) - } - - if mergeRequest.DetailedMergeStatus == "mergeable" { - return true, nil - } - return false, nil + return gitlabService.Context.IsMeargeable, nil } func (gitlabService GitLabService) IsClosed(mergeRequestID int) (bool, error) { @@ -227,37 +225,28 @@ func (e GitLabEventType) String() string { } const ( - MergeRequestOpened = GitLabEventType("merge_request_opened") - MergeRequestUpdated = GitLabEventType("merge_request_updated") - MergeRequestClosed = GitLabEventType("merge_request_closed") + MergeRequestOpened = GitLabEventType("merge_request_opened") + MergeRequestClosed = GitLabEventType("merge_request_closed") + MergeRequestReopened = GitLabEventType("merge_request_reopened") + MergeRequestUpdated = GitLabEventType("merge_request_updated") + MergeRequestApproved = GitLabEventType("merge_request_approved") + MergeRequestUnapproved = GitLabEventType("merge_request_unapproved") + MergeRequestApproval = GitLabEventType("merge_request_approval") + MergeRequestUnapproval = GitLabEventType("merge_request_unapproval") + MergeRequestMerged = GitLabEventType("merge_request_merge") + MergeRequestComment = GitLabEventType("merge_request_commented") ) func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContext, impactedProjects []configuration.Project, workflows map[string]configuration.Workflow) ([]digger.ProjectCommand, error) { commandsPerProject := make([]digger.ProjectCommand, 0) + fmt.Printf("ConvertGitLabEventToCommands, event.EventType: %s\n", event.EventType) switch event.EventType { - case MergeRequestOpened: + case MergeRequestOpened, MergeRequestReopened, MergeRequestUpdated: for _, project := range impactedProjects { workflow, ok := workflows[project.Workflow] - if !ok { - workflow = workflows["default"] - } - commandsPerProject = append(commandsPerProject, digger.ProjectCommand{ - ProjectName: project.Name, - ProjectDir: project.Dir, - ProjectWorkspace: project.Workspace, - Terragrunt: project.Terragrunt, - Commands: workflow.Configuration.OnPullRequestPushed, - ApplyStage: workflow.Apply, - PlanStage: workflow.Plan, - }) - } - return commandsPerProject, nil - case MergeRequestUpdated: - for _, project := range impactedProjects { - workflow, ok := workflows[project.Workflow] if !ok { workflow = workflows["default"] } @@ -273,7 +262,8 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex }) } return commandsPerProject, nil - case MergeRequestClosed: + case MergeRequestClosed, MergeRequestMerged: + fmt.Println("Merge request closed or merged.") for _, project := range impactedProjects { workflow, ok := workflows[project.Workflow] if !ok { @@ -297,13 +287,6 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex if strings.Contains(gitLabContext.DiggerCommand, command) { for _, project := range impactedProjects { workspace := project.Workspace - //workspaceOverride, err := parseWorkspace(gitLabContext.DiggerCommand) - //if err != nil { - // return []digger.ProjectCommand{}, err - //} - //if workspaceOverride != "" { - // workspace = workspaceOverride - //} commandsPerProject = append(commandsPerProject, digger.ProjectCommand{ ProjectName: project.Name, ProjectDir: project.Dir, @@ -319,6 +302,7 @@ func ConvertGitLabEventToCommands(event GitLabEvent, gitLabContext *GitLabContex default: return []digger.ProjectCommand{}, fmt.Errorf("unsupported GitLab event type: %v", event) } + return nil, nil } func RunCommandsPerProject(commandsPerProject []digger.ProjectCommand, gitLabContext GitLabContext, diggerConfig *configuration.DiggerConfig, service ci.CIService, lock utils.Lock, planStorage utils.PlanStorage, workingDir string) (bool, error) { @@ -338,7 +322,6 @@ func RunCommandsPerProject(commandsPerProject []digger.ProjectCommand, gitLabCon var terraformExecutor terraform.TerraformExecutor projectPath := path.Join(workingDir, projectCommands.ProjectDir) - fmt.Printf("pprojectPath: %s\n\n", projectPath) if projectCommands.Terragrunt { terraformExecutor = terraform.Terragrunt{WorkingDir: path.Join(workingDir, projectCommands.ProjectDir)} } else { @@ -417,52 +400,4 @@ func RunCommandsPerProject(commandsPerProject []digger.ProjectCommand, gitLabCon } } return allAppliesSuccess, nil - /* - - lockAcquisitionSuccess := true - for _, projectCommands := range commandsPerProject { - for _, command := range projectCommands.Commands { - projectLock := &utils.ProjectLockImpl{ - InternalLock: lock, - CIService: service, - ProjectName: projectCommands.ProjectName, - RepoName: gitLabContext.ProjectName, - RepoOwner: gitLabContext.ProjectNamespace, - } - diggerExecutor := digger.DiggerExecutor{ - workingDir, - projectCommands.ProjectWorkspace, - gitLabContext.ProjectNamespace, - projectCommands.ProjectName, - projectCommands.ProjectDir, - gitLabContext.ProjectName, - projectCommands.Terragrunt, - service, - projectLock, - diggerConfig, - } - switch command { - case "digger plan": - utils.SendUsageRecord(gitLabContext.ProjectNamespace, gitLabContext.EventType.String(), "plan") - diggerExecutor.Plan(*gitLabContext.MergeRequestIId) - case "digger apply": - utils.SendUsageRecord(gitLabContext.ProjectName, gitLabContext.EventType.String(), "apply") - diggerExecutor.Apply(*gitLabContext.MergeRequestIId) - case "digger unlock": - utils.SendUsageRecord(gitLabContext.ProjectNamespace, gitLabContext.EventType.String(), "unlock") - diggerExecutor.Unlock(*gitLabContext.MergeRequestIId) - case "digger lock": - utils.SendUsageRecord(gitLabContext.ProjectNamespace, gitLabContext.EventType.String(), "lock") - lockAcquisitionSuccess = diggerExecutor.Lock(*gitLabContext.MergeRequestIId) - } - } - } - - if !lockAcquisitionSuccess { - os.Exit(1) - } - return nil - - */ - } diff --git a/pkg/integration/integration_test.go b/pkg/integration/integration_test.go index 21c61d982..7d0ad1d7a 100644 --- a/pkg/integration/integration_test.go +++ b/pkg/integration/integration_test.go @@ -40,7 +40,7 @@ func getProjectLockForTests() (error, *utils.ProjectLockImpl) { repoOwner := "diggerhq" repositoryName := "test_dynamodb_lock" ghToken := "token" - githubPrService := dg_github.NewGithubPullRequestService(ghToken, repositoryName, repoOwner) + githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner) projectLock := &utils.ProjectLockImpl{ InternalLock: &dynamoDbLock, @@ -381,7 +381,7 @@ func TestHappyPath(t *testing.T) { eventName := parsedNewPullRequestContext.EventName repoOwner := parsedNewPullRequestContext.RepositoryOwner repositoryName := parsedNewPullRequestContext.Repository - githubPrService := dg_github.NewGithubPullRequestService(ghToken, repositoryName, repoOwner) + githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner) assert.Equal(t, "pull_request", parsedNewPullRequestContext.EventName) @@ -532,7 +532,7 @@ func TestMultiEnvHappyPath(t *testing.T) { eventName := parsedNewPullRequestContext.EventName repoOwner := parsedNewPullRequestContext.RepositoryOwner repositoryName := parsedNewPullRequestContext.Repository - githubPrService := dg_github.NewGithubPullRequestService(ghToken, repositoryName, repoOwner) + githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner) assert.Equal(t, "pull_request", parsedNewPullRequestContext.EventName) @@ -747,7 +747,7 @@ workflows: eventName := parsedNewPullRequestContext.EventName repoOwner := parsedNewPullRequestContext.RepositoryOwner repositoryName := parsedNewPullRequestContext.Repository - githubPrService := dg_github.NewGithubPullRequestService(ghToken, repositoryName, repoOwner) + githubPrService := dg_github.NewGitHubService(ghToken, repositoryName, repoOwner) assert.Equal(t, "pull_request", parsedNewPullRequestContext.EventName)