From 549942caaac4e51de3aa9a3c180ac9d0f8c46f81 Mon Sep 17 00:00:00 2001 From: Yusuke Kuoka Date: Wed, 15 Nov 2023 07:51:49 +0000 Subject: [PATCH] GitOps plugin system and a "kanvas" plugin placeholder This is the first step to add the support for kanvas as our alternative deployment tool usable by gocat. We implement the interactor and the model for kanvas almost reusing our existing InteractorKustomize and ModelKustomize. The differences between the kustomize and the kanvas supports are the "Prepare" function, which is used to edit the gitops config file in a Git repository and submit a pull request. The kustomize support does so by using gocat-builtin kustomize support. The new kanvas support does so by calling kanvas. In this change, we focus on refactoring and adding the placeholder for the kanvas support. Once this is merged, we will keep adding the actual implementation, gradually replacing the placeholder to a full-fledged feature. --- gitops_plugin.go | 7 +++++ gitops_plugin_kanvas.go | 56 ++++++++++++++++++++++++++++++++++++++ gitops_plugin_kustomize.go | 15 ++++++++++ interactor.go | 16 +++++++++-- interactor_kanvas.go | 10 +++++++ interactor_kustomize.go | 25 +++++++++-------- model.go | 2 ++ model_kanvas.go | 9 ++++++ model_kustomize.go | 37 ++++++++++++++----------- 9 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 gitops_plugin.go create mode 100644 gitops_plugin_kanvas.go create mode 100644 gitops_plugin_kustomize.go create mode 100644 interactor_kanvas.go create mode 100644 model_kanvas.go diff --git a/gitops_plugin.go b/gitops_plugin.go new file mode 100644 index 00000000..87eb0cb2 --- /dev/null +++ b/gitops_plugin.go @@ -0,0 +1,7 @@ +package main + +// GitOpsPlugin is the extension point for InteractorGitOps +// It is used to support various GitOps tools. +type GitOpsPlugin interface { + Prepare(pj DeployProject, phase string, branch string, user User, message string) (o GitOpsPrepareOutput, err error) +} diff --git a/gitops_plugin_kanvas.go b/gitops_plugin_kanvas.go new file mode 100644 index 00000000..86a18665 --- /dev/null +++ b/gitops_plugin_kanvas.go @@ -0,0 +1,56 @@ +package main + +// GitOpsPluginKanvas is a gocat gitops plugin to prepare +// deployments using kanvas. +// This is used when you want to use gocat as a workflow engine +// with a chatops interface, while using kanvas as a deployment tool. +// +// Unlike GitOpsPluginKustomize which uses gocat's builtin Git and GitHub support, +// GitOpsPluginKanvas uses kanvas's Git and GitHub support. +type GitOpsPluginKanvas struct { + github *GitHub + git *GitOperator +} + +func NewGitOpsPluginKanvas(github *GitHub, git *GitOperator) GitOpsPlugin { + return &GitOpsPluginKanvas{github: github, git: git} +} + +func (k GitOpsPluginKanvas) Prepare(pj DeployProject, phase string, branch string, assigner User, tag string) (o GitOpsPrepareOutput, err error) { + o.status = DeployStatusFail + if tag == "" { + ecr, err := CreateECRInstance() + if err != nil { + return o, err + } + tag, err = ecr.FindImageTagByRegexp(pj.ECRRegistryId(), pj.ECRRepository(), pj.ImageTagRegexp(), pj.TargetRegexp(), ImageTagVars{Branch: branch, Phase: phase}) + if err != nil { + return o, err + } + } + + // TODO Enhance kanvas to create a git commit and branch from the image tag + _ = tag + + // TODO Do kanvas deployment using specific PR title and description + + // TODO if the result code is "No difference", we should not create a pull request. + // if tag == currentTag { + // o.status = DeployStatusAlready + // return + // } + + // TODO Enhance kanvas to support setting assigner for the pull request + // TODO Enhance kanvas to returning: + // - pull request id + // - pull request number + // - pull request head branch + + o = GitOpsPrepareOutput{ + // PullRequestID: prID, + // PullRequestNumber: prNum, + // Branch: prBranch, + // status: DeployStatusSuccess, + } + return +} diff --git a/gitops_plugin_kustomize.go b/gitops_plugin_kustomize.go new file mode 100644 index 00000000..26601df0 --- /dev/null +++ b/gitops_plugin_kustomize.go @@ -0,0 +1,15 @@ +package main + +// GitOpsPluginKustomize is a gocat gitops plugin to prepare +// deployments using kustomize and gocat's builtin Git and GitHub support. +// This is used when you want to use gocat as a workflow engine +// with a chatops interface, while using kustomize along with +// the gocat native features as a deployment tool. +type GitOpsPluginKustomize struct { + github *GitHub + git *GitOperator +} + +func NewGitOpsPluginKustomize(github *GitHub, git *GitOperator) GitOpsPlugin { + return &GitOpsPluginKustomize{github: github, git: git} +} diff --git a/interactor.go b/interactor.go index d1e163e5..c378eff5 100644 --- a/interactor.go +++ b/interactor.go @@ -30,7 +30,8 @@ type DeployUsecase interface { } type InteractorFactory struct { - kustomize InteractorKustomize + kanvas InteractorGitOps + kustomize InteractorGitOps jenkins InteractorJenkins job InteractorJob lambda InteractorLambda @@ -38,7 +39,14 @@ type InteractorFactory struct { } func NewInteractorFactory(c InteractorContext) InteractorFactory { - return InteractorFactory{kustomize: NewInteractorKustomize(c), jenkins: NewInteractorJenkins(c), job: NewInteractorJob(c), lambda: NewInteractorLambda(c), combine: NewInteractorCombine(c)} + return InteractorFactory{ + kanvas: NewInteractorKanavs(c), + kustomize: NewInteractorKustomize(c), + jenkins: NewInteractorJenkins(c), + job: NewInteractorJob(c), + lambda: NewInteractorLambda(c), + combine: NewInteractorCombine(c), + } } func (i InteractorFactory) Get(pj DeployProject, phase string) DeployUsecase { @@ -50,6 +58,8 @@ func (i InteractorFactory) Get(pj DeployProject, phase string) DeployUsecase { func (i InteractorFactory) get(kind string) DeployUsecase { switch kind { + case "kanvas": + return i.kanvas case "kustomize": return i.kustomize case "job": @@ -65,6 +75,8 @@ func (i InteractorFactory) get(kind string) DeployUsecase { func (i InteractorFactory) GetByParams(params string) DeployUsecase { switch { + case strings.Contains(params, "kanvas"): + return i.kanvas case strings.Contains(params, "kustomize"): return i.kustomize case strings.Contains(params, "job"): diff --git a/interactor_kanvas.go b/interactor_kanvas.go new file mode 100644 index 00000000..d47e349b --- /dev/null +++ b/interactor_kanvas.go @@ -0,0 +1,10 @@ +package main + +func NewInteractorKanavs(i InteractorContext) (o InteractorGitOps) { + o = InteractorGitOps{ + InteractorContext: i, + model: NewGitOpsPluginKanvas(&o.github, &o.git), + } + o.kind = "kanvas" + return +} diff --git a/interactor_kustomize.go b/interactor_kustomize.go index 24f2cb68..1d89b286 100644 --- a/interactor_kustomize.go +++ b/interactor_kustomize.go @@ -8,18 +8,21 @@ import ( "github.com/nlopes/slack" ) -type InteractorKustomize struct { +type InteractorGitOps struct { InteractorContext - model ModelKustomize + model GitOpsPlugin } -func NewInteractorKustomize(i InteractorContext) (o InteractorKustomize) { - o = InteractorKustomize{InteractorContext: i, model: NewModelKustomize(&o.github, &o.git)} +func NewInteractorKustomize(i InteractorContext) (o InteractorGitOps) { + o = InteractorGitOps{ + InteractorContext: i, + model: NewGitOpsPluginKustomize(&o.github, &o.git), + } o.kind = "kustomize" return } -func (i InteractorKustomize) Request(pj DeployProject, phase string, branch string, assigner string, channel string) (blocks []slack.Block, err error) { +func (i InteractorGitOps) Request(pj DeployProject, phase string, branch string, assigner string, channel string) (blocks []slack.Block, err error) { user := i.userList.FindBySlackUserID(assigner) go func() { @@ -50,23 +53,23 @@ func (i InteractorKustomize) Request(pj DeployProject, phase string, branch stri return i.plainBlocks("Now creating pull request..."), nil } -func (i InteractorKustomize) BranchList(pj DeployProject, phase string) ([]slack.Block, error) { +func (i InteractorGitOps) BranchList(pj DeployProject, phase string) ([]slack.Block, error) { return i.branchList(pj, phase) } -func (i InteractorKustomize) BranchListFromRaw(params string) ([]slack.Block, error) { +func (i InteractorGitOps) BranchListFromRaw(params string) ([]slack.Block, error) { p := strings.Split(params, "_") pj := i.projectList.Find(p[0]) return i.branchList(pj, p[1]) } -func (i InteractorKustomize) SelectBranch(params string, branch string, userID string, channel string) ([]slack.Block, error) { +func (i InteractorGitOps) SelectBranch(params string, branch string, userID string, channel string) ([]slack.Block, error) { p := strings.Split(params, "_") pj := i.projectList.Find(p[0]) return i.Request(pj, p[1], branch, userID, channel) } -func (i InteractorKustomize) Approve(params string, userID string, channel string) (blocks []slack.Block, err error) { +func (i InteractorGitOps) Approve(params string, userID string, channel string) (blocks []slack.Block, err error) { prID := "" prNumber := "" p := strings.Split(params, "_") @@ -113,12 +116,12 @@ func (i InteractorKustomize) Approve(params string, userID string, channel strin return } -func (i InteractorKustomize) Reject(params string, userID string) ([]slack.Block, error) { +func (i InteractorGitOps) Reject(params string, userID string) ([]slack.Block, error) { p := strings.Split(params, "_") return i.reject(p[0], p[1], p[2], userID) } -func (i InteractorKustomize) reject(prID string, prNum string, branch string, userID string) (blocks []slack.Block, err error) { +func (i InteractorGitOps) reject(prID string, prNum string, branch string, userID string) (blocks []slack.Block, err error) { if err = i.github.ClosePullRequest(prID); err != nil { return } diff --git a/model.go b/model.go index cb099072..611b32bb 100644 --- a/model.go +++ b/model.go @@ -52,6 +52,7 @@ func NewDeployModelList(github *GitHub, git *GitOperator, projectList *ProjectLi return &DeployModelList{ "lambda": NewModelLambda(), "kustomize": NewModelKustomize(github, git), + "kanvas": NewModelKanvas(github, git), "combine": NewModelCombine(github, git, projectList), "job": NewModelJob(github), } @@ -61,6 +62,7 @@ func NewDeployModelListWithoutCombine(github *GitHub, git *GitOperator) *DeployM return &DeployModelList{ "lambda": NewModelLambda(), "kustomize": NewModelKustomize(github, git), + "kanvas": NewModelKanvas(github, git), "job": NewModelJob(github), } } diff --git a/model_kanvas.go b/model_kanvas.go new file mode 100644 index 00000000..6a9f9411 --- /dev/null +++ b/model_kanvas.go @@ -0,0 +1,9 @@ +package main + +func NewModelKanvas(github *GitHub, git *GitOperator) ModelGitOps { + return ModelGitOps{ + github: github, + git: git, + plugin: NewGitOpsPluginKanvas(github, git), + } +} diff --git a/model_kustomize.go b/model_kustomize.go index eb0927d6..807a8ca9 100644 --- a/model_kustomize.go +++ b/model_kustomize.go @@ -5,31 +5,36 @@ import ( "strings" ) -type ModelKustomize struct { +type ModelGitOps struct { github *GitHub git *GitOperator + plugin GitOpsPlugin } -func NewModelKustomize(github *GitHub, git *GitOperator) ModelKustomize { - return ModelKustomize{github: github, git: git} +func NewModelKustomize(github *GitHub, git *GitOperator) ModelGitOps { + return ModelGitOps{ + github: github, + git: git, + plugin: NewGitOpsPluginKustomize(github, git), + } } -type ModelKustomizePrepareOutput struct { +type GitOpsPrepareOutput struct { PullRequestID string PullRequestNumber int Branch string status DeployStatus } -func (self ModelKustomizePrepareOutput) Status() DeployStatus { +func (self GitOpsPrepareOutput) Status() DeployStatus { return self.status } -func (self ModelKustomizePrepareOutput) Message() string { +func (self GitOpsPrepareOutput) Message() string { return "Success to deploy" } -func (self ModelKustomize) Prepare(pj DeployProject, phase string, branch string, assigner User, tag string) (o ModelKustomizePrepareOutput, err error) { +func (k GitOpsPluginKustomize) Prepare(pj DeployProject, phase string, branch string, assigner User, tag string) (o GitOpsPrepareOutput, err error) { o.status = DeployStatusFail if tag == "" { ecr, err := CreateECRInstance() @@ -43,7 +48,7 @@ func (self ModelKustomize) Prepare(pj DeployProject, phase string, branch string } ph := pj.FindPhase(phase) - currentTag, err := ph.Destination.GetCurrentRevision(GetCurrentRevisionInput{github: self.github}) + currentTag, err := ph.Destination.GetCurrentRevision(GetCurrentRevisionInput{github: k.github}) if err != nil { return } @@ -53,7 +58,7 @@ func (self ModelKustomize) Prepare(pj DeployProject, phase string, branch string return } - commits, err := self.github.CommitsBetween(GitHubCommitsBetweenInput{ + commits, err := k.github.CommitsBetween(GitHubCommitsBetweenInput{ Repository: pj.GitHubRepository(), Branch: branch, FirstCommitID: currentTag, @@ -66,24 +71,24 @@ func (self ModelKustomize) Prepare(pj DeployProject, phase string, branch string commitlog = commitlog + "- " + m + "\n" } - prBranch, err := self.git.PushDockerImageTag(pj.ID, ph, tag, pj.DockerRepository()) + prBranch, err := k.git.PushDockerImageTag(pj.ID, ph, tag, pj.DockerRepository()) if err != nil { return } - prID, prNum, err := self.github.CreatePullRequest(prBranch, fmt.Sprintf("Deploy %s %s", pj.ID, branch), commitlog) + prID, prNum, err := k.github.CreatePullRequest(prBranch, fmt.Sprintf("Deploy %s %s", pj.ID, branch), commitlog) if err != nil { return } if assigner.GitHubNodeID != "" { - err = self.github.UpdatePullRequest(prID, assigner.GitHubNodeID) + err = k.github.UpdatePullRequest(prID, assigner.GitHubNodeID) if err != nil { return } } - o = ModelKustomizePrepareOutput{ + o = GitOpsPrepareOutput{ PullRequestID: prID, PullRequestNumber: prNum, Branch: prBranch, @@ -92,13 +97,13 @@ func (self ModelKustomize) Prepare(pj DeployProject, phase string, branch string return } -func (self ModelKustomize) Commit(pullRequestID string) error { +func (self ModelGitOps) Commit(pullRequestID string) error { return self.github.MergePullRequest(pullRequestID) } -func (self ModelKustomize) Deploy(pj DeployProject, phase string, option DeployOption) (do DeployOutput, err error) { - o, err := self.Prepare(pj, phase, option.Branch, option.Assigner, option.Tag) +func (self ModelGitOps) Deploy(pj DeployProject, phase string, option DeployOption) (do DeployOutput, err error) { + o, err := self.plugin.Prepare(pj, phase, option.Branch, option.Assigner, option.Tag) if err != nil { return }