-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add option to rerun kubecheck plan (GitHub Only) #250
Conversation
Hi @KyriosGN0, thanks for the PR. |
hi @Greyeye, is there some example on how to do it, im not sure if i should/can mock the entire HTTP request that a webhook sends, would it be possible to have some help? |
@KyriosGN0 |
@Greyeye merged your PR, thx a lot |
@Greyeye can you rerun the CI ? i don't see anything weird in the ci logs |
CI is trying to push the docker image to the org repo, and it's failing. @KyriosGN0 can you change .github/workflows/on_pull_request.yaml and add github.repository == 'zapier/kubechecks' under
|
@Greyeye added |
@Greyeye looks like it failed still |
@Greyeye any update ? |
pkg/vcs/github_client/client.go
Outdated
case *github.IssueCommentEvent: | ||
switch p.GetAction() { | ||
case "created": | ||
if strings.ToLower(p.Comment.GetBody()) == "kubechecks again" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the right phrasing? A lot of similar tools would do something like /kubechecks retry
or something like that. Alternately, we could make it configurable. Still need a good default, but that removes some pressure off of getting it perfectly right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly im coming from atlantis where do atlantis apply/run apply
but i agree that making it configureable is a good suggestion, i'll update the PR this week and tag you
Looks great! I think the wording could use some work though; check out my comment? |
@djeebus added it |
@KyriosGN0 doc lint and ci are failing, would you be able to check these please. |
@Greyeye should be fixed |
@Greyeye it seems like same issue, can this PR be merged or is this blocking ? (because its not even something in the code itself) |
@Greyeye any updates? |
Don't worry about the failing push, it should be (might be?) sorted out if you pull |
Signed-off-by: AvivGuiser <[email protected]>
Signed-off-by: AvivGuiser <[email protected]>
Signed-off-by: AvivGuiser <[email protected]>
Signed-off-by: AvivGuiser <[email protected]>
…d of direct access to pointers.
Signed-off-by: AvivGuiser <[email protected]>
Signed-off-by: AvivGuiser <[email protected]>
Co-authored-by: Joseph Lombrozo <[email protected]> Signed-off-by: AvivGuiser <[email protected]>
@djeebus fixed conflicts and commited your suggestion |
Signed-off-by: AvivGuiser <[email protected]>
This reverts commit ad876a3. Signed-off-by: AvivGuiser <[email protected]>
This reverts commit bd0e151. Signed-off-by: AvivGuiser <[email protected]>
@djeebus fixed test failures, the change which you proposed caused test to fails as those methods don't error but rather return 0 |
Thanks! It looks like those failed tests uncovered a broken test, and a legit bug. I've fixed them here, if you want to grab them: KyriosGN0/kubechecks@replan...djeebus:kubechecks:fix-replan-code-tests |
Co-authored-by: Joseph Lombrozo <[email protected]> Signed-off-by: AvivGuiser <[email protected]>
Signed-off-by: AvivGuiser <[email protected]>
@djeebus took your suggestion and also add case for repo/owner is empty string |
@djeebus any updates here ? |
Signed-off-by: Joe Lombrozo <[email protected]>
# Conflicts: # pkg/vcs/github_client/client.go
Mergecat's ReviewClick to read mergecats review!😼 Mergecat review of pkg/server/hook_handler.go@@ -47,11 +47,11 @@ func (h *VCSHookHandler) groupHandler(c echo.Context) error {
return c.String(http.StatusUnauthorized, "Unauthorized")
}
- pr, err := h.ctr.VcsClient.ParseHook(c.Request(), payload)
+ pr, err := h.ctr.VcsClient.ParseHook(ctx, c.Request(), payload)
if err != nil {
switch err {
case vcs.ErrInvalidType:
- log.Debug().Msg("Ignoring event, not a merge request")
+ log.Debug().Msg("Ignoring event, not a supported request")
return c.String(http.StatusOK, "Skipped")
default:
// TODO: do something ELSE with the error Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/gitlab_client/status.go+
+type CommitsServices interface {
+ SetCommitStatus(pid interface{}, sha string, opt *gitlab.SetCommitStatusOptions, options ...gitlab.RequestOptionFunc) (*gitlab.CommitStatus, *gitlab.Response, error)
+}
+
+type CommitsService struct {
+ CommitsServices
+} Feedback & Suggestions:
😼 Mergecat review of cmd/root.go@@ -115,6 +115,9 @@ func init() {
stringFlag(flags, "worst-hooks-state", "The worst state that can be returned from the hooks renderer.",
newStringOpts().
withDefault("panic"))
+ stringFlag(flags, "replan-comment-msg", "comment message which re-triggers kubechecks on PR.",
+ newStringOpts().
+ withDefault("kubechecks again"))
panicIfError(viper.BindPFlags(flags))
setupLogOutput() Feedback & Suggestions:
😼 Mergecat review of pkg/config/config.go@@ -78,6 +78,7 @@ type ServerConfig struct {
TidyOutdatedCommentsMode string `mapstructure:"tidy-outdated-comments-mode"`
MaxQueueSize int64 `mapstructure:"max-queue-size"`
MaxConcurrenctChecks int `mapstructure:"max-concurrenct-checks"`
+ ReplanCommentMessage string `mapstructure:"replan-comment-msg"`
}
func New() (ServerConfig, error) { Feedback & Suggestions:
😼 Mergecat review of go.mod@@ -41,7 +41,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
- github.com/xanzy/go-gitlab v0.105.0
+ github.com/xanzy/go-gitlab v0.107.0
github.com/yannh/kubeconform v0.6.4
github.com/ziflex/lecho/v3 v3.5.0
go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 Feedback & Suggestions:
😼 Mergecat review of .github/workflows/on_pull-request_closed.yaml@@ -12,6 +12,7 @@ env:
jobs:
remove-temp-image:
runs-on: ubuntu-22.04
+ continue-on-error: true
# should match env.FS_TAG, in both pr-open.yaml and pr-close.yaml
concurrency: pr-${{ github.event.pull_request.number }} Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/gitlab_client/pipeline.go@@ -57,3 +57,11 @@ func (c *Client) GetLastPipelinesForCommit(projectName string, commitSHA string)
return nil
}
+
+type PipelinesServices interface {
+ ListProjectPipelines(pid interface{}, opt *gitlab.ListProjectPipelinesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.PipelineInfo, *gitlab.Response, error)
+}
+
+type PipelinesService struct {
+ PipelinesServices
+} Feedback & Suggestions:
😼 Mergecat review of docs/usage.md@@ -64,6 +64,7 @@ The full list of supported environment variables is described below:
|`KUBECHECKS_OTEL_ENABLED`|Enable OpenTelemetry.|`false`|
|`KUBECHECKS_PERSIST_LOG_LEVEL`|Persists the set log level down to other module loggers.|`false`|
|`KUBECHECKS_POLICIES_LOCATION`|Sets rego policy locations to be used for every check request. Can be common path inside the repos being checked or git urls in either git or http(s) format.|`[./policies]`|
+|`KUBECHECKS_REPLAN_COMMENT_MSG`|comment message which re-triggers kubechecks on PR.|`kubechecks again`|
|`KUBECHECKS_REPO_REFRESH_INTERVAL`|Interval between static repo refreshes (for schemas and policies).|`5m`|
|`KUBECHECKS_SCHEMAS_LOCATION`|Sets schema locations to be used for every check request. Can be common paths inside the repos being checked or git urls in either git or http(s) format.|`[]`|
|`KUBECHECKS_SHOW_DEBUG_INFO`|Set to true to print debug info to the footer of MR comments.|`false`| Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/gitlab_client/merge.go+
+type MergeRequestsServices interface {
+ GetMergeRequestDiffVersions(pid interface{}, mergeRequest int, opt *gitlab.GetMergeRequestDiffVersionsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequestDiffVersion, *gitlab.Response, error)
+ GetMergeRequestChanges(pid interface{}, mergeRequest int, opt *gitlab.GetMergeRequestChangesOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
+ ListMergeRequestDiffs(pid interface{}, mergeRequest int, opt *gitlab.ListMergeRequestDiffsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.MergeRequestDiff, *gitlab.Response, error)
+ UpdateMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.UpdateMergeRequestOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
+ GetMergeRequest(pid interface{}, mergeRequest int, opt *gitlab.GetMergeRequestsOptions, options ...gitlab.RequestOptionFunc) (*gitlab.MergeRequest, *gitlab.Response, error)
+}
+
+type MergeRequestsService struct {
+ MergeRequestsServices
+} Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/gitlab_client/message.go+type NotesServices interface {
+ CreateMergeRequestNote(pid interface{}, mergeRequest int, opt *gitlab.CreateMergeRequestNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error)
+ UpdateMergeRequestNote(pid interface{}, mergeRequest, note int, opt *gitlab.UpdateMergeRequestNoteOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Note, *gitlab.Response, error)
+ DeleteMergeRequestNote(pid interface{}, mergeRequest, note int, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error)
+ ListMergeRequestNotes(pid interface{}, mergeRequest int, opt *gitlab.ListMergeRequestNotesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Note, *gitlab.Response, error)
+}
+
+type NotesService struct {
+ NotesServices
+} Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/types.go- // ParseHook parses webook payload for valid events
- ParseHook(*http.Request, []byte) (PullRequest, error)
+ // ParseHook parses webook payload for valid events, with context for request-scoped values
+ ParseHook(context.Context, *http.Request, []byte) (PullRequest, error) Feedback & Suggestions:
😼 Mergecat review of .mockery.yaml@@ -5,11 +5,7 @@ packages:
# place your package-specific config here
config:
all: true
- github.com/zapier/kubechecks/pkg/generator:
- # place your package-specific config here
- config:
- all: true
- github.com/zapier/kubechecks/pkg/affected_apps:
+ github.com/zapier/kubechecks/pkg/vcs/gitlab_client:
# place your package-specific config here
config:
all: true Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/gitlab_client/project.go+
+type ProjectsServices interface {
+ AddProjectHook(pid interface{}, opt *gitlab.AddProjectHookOptions, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectHook, *gitlab.Response, error)
+ ListProjectHooks(pid interface{}, opt *gitlab.ListProjectHooksOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectHook, *gitlab.Response, error)
+ EditProjectHook(pid interface{}, hook int, opt *gitlab.EditProjectHookOptions, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectHook, *gitlab.Response, error)
+ GetProject(pid interface{}, opt *gitlab.GetProjectOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Project, *gitlab.Response, error)
+}
+
+type ProjectsService struct {
+ ProjectsServices
+}
+
+type RepositoryFilesServices interface {
+ GetRawFile(pid interface{}, fileName string, opt *gitlab.GetRawFileOptions, options ...gitlab.RequestOptionFunc) ([]byte, *gitlab.Response, error)
+}
+
+type RepositoryFilesService struct {
+ RepositoryFilesServices
+} Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/github_client/client.go@@ -150,7 +150,7 @@ func (c *Client) VerifyHook(r *http.Request, secret string) ([]byte, error) {
var nilPr vcs.PullRequest
-func (c *Client) ParseHook(r *http.Request, request []byte) (vcs.PullRequest, error) {
+func (c *Client) ParseHook(ctx context.Context, r *http.Request, request []byte) (vcs.PullRequest, error) {
payload, err := github.ParseWebHook(github.WebHookType(r), request)
if err != nil {
return nilPr, err
@@ -166,28 +166,44 @@ func (c *Client) ParseHook(r *http.Request, request []byte) (vcs.PullRequest, er
log.Info().Str("action", p.GetAction()).Msg("ignoring Github pull request event due to non commit based action")
return nilPr, vcs.ErrInvalidType
}
+ case *github.IssueCommentEvent:
+ switch p.GetAction() {
+ case "created":
+ if strings.ToLower(p.Comment.GetBody()) == c.cfg.ReplanCommentMessage {
+ log.Info().Msgf("Got %s comment, Running again", c.cfg.ReplanCommentMessage)
+ return c.buildRepoFromComment(ctx, p)
+ } else {
+ log.Info().Str("action", p.GetAction()).Msg("ignoring Github issue comment event due to non matching string")
+ return nilPr, vcs.ErrInvalidType
+ }
+ default:
+ log.Info().Str("action", p.GetAction()).Msg("ignoring Github issue comment due to invalid action")
+ return nilPr, vcs.ErrInvalidType
+ }
default:
log.Error().Msg("invalid event provided to Github client")
return nilPr, vcs.ErrInvalidType
}
}
-func (c *Client) buildRepoFromEvent(event *github.PullRequestEvent) vcs.PullRequest {
+func (c *Client) buildRepo(pullRequest *github.PullRequest) vcs.PullRequest {
+ repo := pullRequest.Head.Repo
+
var labels []string
- for _, label := range event.PullRequest.Labels {
+ for _, label := range pullRequest.Labels {
labels = append(labels, label.GetName())
}
return vcs.PullRequest{
- BaseRef: *event.PullRequest.Base.Ref,
- HeadRef: *event.PullRequest.Head.Ref,
- DefaultBranch: *event.Repo.DefaultBranch,
- CloneURL: *event.Repo.CloneURL,
- FullName: event.Repo.GetFullName(),
- Owner: *event.Repo.Owner.Login,
- Name: event.Repo.GetName(),
- CheckID: *event.PullRequest.Number,
- SHA: *event.PullRequest.Head.SHA,
+ BaseRef: pullRequest.Base.GetRef(),
+ HeadRef: pullRequest.Head.GetRef(),
+ DefaultBranch: repo.GetDefaultBranch(),
+ CloneURL: repo.GetCloneURL(),
+ FullName: repo.GetFullName(),
+ Owner: repo.Owner.GetLogin(),
+ Name: repo.GetName(),
+ CheckID: pullRequest.GetNumber(),
+ SHA: pullRequest.Head.GetSHA(),
Username: c.username,
Email: c.email,
Labels: labels,
@@ -196,6 +212,25 @@ func (c *Client) buildRepoFromEvent(event *github.PullRequestEvent) vcs.PullRequ
}
}
+func (c *Client) buildRepoFromEvent(event *github.PullRequestEvent) vcs.PullRequest {
+ return c.buildRepo(event.PullRequest)
+}
+
+// buildRepoFromComment builds a vcs.PullRequest from a github.IssueCommentEvent
+func (c *Client) buildRepoFromComment(context context.Context, comment *github.IssueCommentEvent) (vcs.PullRequest, error) {
+ owner := comment.GetRepo().GetOwner().GetLogin()
+ repoName := comment.GetRepo().GetName()
+ prNumber := comment.GetIssue().GetNumber()
+
+ log.Info().Str("owner", owner).Str("repo", repoName).Int("number", prNumber).Msg("getting pr")
+ pr, _, err := c.googleClient.PullRequests.Get(context, owner, repoName, prNumber)
+ if err != nil {
+ return nilPr, errors.Wrap(err, "failed to get pull request")
+ }
+
+ return c.buildRepo(pr), nil
+}
+
func toGithubCommitStatus(state pkg.CommitState) *string {
switch state {
case pkg.StateError, pkg.StatePanic:
@@ -283,7 +318,7 @@ func (c *Client) CreateHook(ctx context.Context, ownerAndRepoName, webhookUrl, w
Secret: pkg.Pointer(webhookSecret),
},
Events: []string{
- "pull_request",
+ "pull_request", "issue_comment",
},
Name: pkg.Pointer("web"),
}) Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/github_client/client_test.go@@ -10,6 +10,7 @@ import (
"github.com/shurcooL/githubv4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
githubMocks "github.com/zapier/kubechecks/mocks/github_client/mocks"
"github.com/zapier/kubechecks/pkg/config"
"github.com/zapier/kubechecks/pkg/vcs"
@@ -25,6 +26,16 @@ func MockGitHubMethod(methodName string, returns []interface{}) *GClient {
}
}
+// MockGitHubPullRequestMethod is a generic function to mock GitHub client methods
+func MockGitHubPullRequestMethod(methodName string, returns []interface{}) *GClient {
+ mockClient := new(githubMocks.MockPullRequestsServices)
+ mockClient.On(methodName, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(returns...)
+
+ return &GClient{
+ PullRequests: mockClient,
+ }
+}
+
func TestParseRepo(t *testing.T) {
testcases := []struct {
name, input string
@@ -324,3 +335,130 @@ func TestClient_GetHookByUrl(t *testing.T) {
})
}
}
+
+func TestClient_buildRepoFromComment_HappyPath(t *testing.T) {
+ type fields struct {
+ shurcoolClient *githubv4.Client
+ googleClient *GClient
+ cfg config.ServerConfig
+ username string
+ email string
+ }
+ type args struct {
+ context context.Context
+ comment *github.IssueCommentEvent
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want vcs.PullRequest
+ }{
+ {
+ name: "normal ok",
+ fields: fields{
+ shurcoolClient: nil,
+ googleClient: MockGitHubPullRequestMethod("Get",
+ []interface{}{
+ &github.PullRequest{
+ ID: nil,
+ Number: github.Int(123),
+ Labels: []*github.Label{
+ {
+ Name: github.String("test label1"),
+ },
+ {
+ Name: github.String("test label2"),
+ },
+ },
+ Head: &github.PullRequestBranch{
+ Ref: github.String("new-feature"),
+ SHA: github.String("dummySHAHead"),
+ Repo: &github.Repository{
+ CloneURL: github.String("https://github.com/zapier/kubechecks/"),
+ DefaultBranch: github.String("main"),
+ FullName: github.String("zapier/kubechecks"),
+ Owner: &github.User{Login: github.String("fork")},
+ Name: github.String("kubechecks"),
+ },
+ },
+ Base: &github.PullRequestBranch{
+ Ref: github.String("main"),
+ SHA: github.String("dummySHABase"),
+ Repo: &github.Repository{
+ CloneURL: github.String("https://github.com/zapier/kubechecks/"),
+ Owner: &github.User{Login: github.String("zapier")},
+ },
+ },
+ },
+ &github.Response{Response: &http.Response{StatusCode: http.StatusOK}},
+ nil},
+ ),
+ cfg: config.ServerConfig{},
+ username: "unittestuser",
+ email: "[email protected]",
+ },
+ args: args{
+ context: context.TODO(),
+ comment: &github.IssueCommentEvent{
+ Issue: &github.Issue{
+ URL: github.String("https://github.com/zapier/kubechecks/pull/250"),
+ Number: github.Int(250),
+ Repository: &github.Repository{
+ Name: github.String("kubechecks"),
+ Owner: &github.User{
+ Name: github.String("zapier"),
+ },
+ },
+ },
+ Repo: &github.Repository{
+ DefaultBranch: github.String("main"),
+ Name: github.String("kubechecks"),
+ FullName: github.String("zapier/kubechecks"),
+ },
+ },
+ },
+ want: vcs.PullRequest{
+ BaseRef: "main",
+ HeadRef: "new-feature",
+ DefaultBranch: "main",
+ CloneURL: "https://github.com/zapier/kubechecks/",
+ Name: "kubechecks",
+ Owner: "fork",
+ CheckID: 123,
+ SHA: "dummySHAHead",
+ FullName: "zapier/kubechecks",
+ Username: "unittestuser",
+ Email: "[email protected]",
+ Labels: []string{"test label1", "test label2"},
+ Config: config.ServerConfig{},
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Client{
+ shurcoolClient: tt.fields.shurcoolClient,
+ googleClient: tt.fields.googleClient,
+ cfg: tt.fields.cfg,
+ username: tt.fields.username,
+ email: tt.fields.email,
+ }
+ actual, err := c.buildRepoFromComment(tt.args.context, tt.args.comment)
+ require.NoError(t, err)
+ assert.Equal(t, tt.want.Name, actual.Name)
+ assert.Equal(t, tt.want.Labels, actual.Labels)
+ assert.Equal(t, tt.want.CheckID, actual.CheckID)
+ assert.Equal(t, tt.want.BaseRef, actual.BaseRef)
+ assert.Equal(t, tt.want.CloneURL, actual.CloneURL)
+ assert.Equal(t, tt.want.DefaultBranch, actual.DefaultBranch)
+ assert.Equal(t, tt.want.Email, actual.Email)
+ assert.Equal(t, tt.want.FullName, actual.FullName)
+ assert.Equal(t, tt.want.HeadRef, actual.HeadRef)
+ assert.Equal(t, tt.want.Owner, actual.Owner)
+ assert.Equal(t, tt.want.Remote, actual.Remote)
+ assert.Equal(t, tt.want.SHA, actual.SHA)
+ assert.Equal(t, tt.want.Username, actual.Username)
+ })
+ }
+} Feedback & Suggestions:
Overall, the changes are well-structured and enhance the test suite. Keep up the good work! 🚀 😼 Mergecat review of pkg/vcs/gitlab_client/client_test.go@@ -1,10 +1,18 @@
package gitlab_client
import (
+ "context"
+ "fmt"
+ "net/http"
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
+ "github.com/xanzy/go-gitlab"
+ gitlabMocks "github.com/zapier/kubechecks/mocks/gitlab_client/mocks"
+ "github.com/zapier/kubechecks/pkg/config"
+ "github.com/zapier/kubechecks/pkg/vcs"
)
func TestCustomGitURLParsing(t *testing.T) {
@@ -40,3 +48,184 @@ func TestCustomGitURLParsing(t *testing.T) {
})
}
}
+
+func TestClient_GetHookByUrl(t *testing.T) {
+ type fields struct {
+ c *GLClient
+ cfg config.ServerConfig
+ username string
+ email string
+ }
+ type args struct {
+ ctx context.Context
+ repoName string
+ webhookUrl string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want *vcs.WebHookConfig
+ wantErr assert.ErrorAssertionFunc
+ }{
+ {
+ name: "normal ok",
+ fields: fields{
+ c: MockGitLabProjects("ListProjectHooks",
+ []interface{}{
+ []*gitlab.ProjectHook{
+ {
+ URL: "https://dummywebhooks.local",
+ MergeRequestsEvents: true,
+ NoteEvents: true,
+ },
+ },
+ &gitlab.Response{
+ Response: &http.Response{StatusCode: http.StatusOK},
+ },
+ nil,
+ }),
+ cfg: config.ServerConfig{},
+ username: "",
+ email: "",
+ },
+ args: args{
+ ctx: context.TODO(),
+ repoName: "test",
+ webhookUrl: "https://dummywebhooks.local",
+ },
+ want: &vcs.WebHookConfig{
+ Url: "https://dummywebhooks.local",
+ Events: []string{"merge_request", "note"},
+ },
+ wantErr: assert.NoError,
+ },
+ {
+ name: "gl client err",
+ fields: fields{
+ c: MockGitLabProjects("ListProjectHooks",
+ []interface{}{
+ nil,
+ &gitlab.Response{
+ Response: &http.Response{StatusCode: http.StatusInternalServerError},
+ },
+ fmt.Errorf("dummy error"),
+ }),
+ cfg: config.ServerConfig{},
+ username: "",
+ email: "",
+ },
+ args: args{
+ ctx: context.TODO(),
+ repoName: "test",
+ webhookUrl: "https://dummywebhooks.local",
+ },
+ want: nil,
+ wantErr: assert.Error,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Client{
+ c: tt.fields.c,
+ cfg: tt.fields.cfg,
+ username: tt.fields.username,
+ email: tt.fields.email,
+ }
+ got, err := c.GetHookByUrl(tt.args.ctx, tt.args.repoName, tt.args.webhookUrl)
+ if !tt.wantErr(t, err, fmt.Sprintf("GetHookByUrl(%v, %v, %v)", tt.args.ctx, tt.args.repoName, tt.args.webhookUrl)) {
+ return
+ }
+ assert.Equalf(t, tt.want, got, "GetHookByUrl(%v, %v, %v)", tt.args.ctx, tt.args.repoName, tt.args.webhookUrl)
+ })
+ }
+}
+
+// MockGitLabProjects is a generic function to mock Gitlab MergeRequest client methods
+func MockGitLabProjects(methodName string, returns []interface{}) *GLClient {
+ mockClient := new(gitlabMocks.MockProjectsServices)
+ mockClient.On(methodName, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(returns...)
+
+ return &GLClient{
+ Projects: mockClient,
+ }
+}
+
+func TestClient_CreateHook(t *testing.T) {
+ type fields struct {
+ c *GLClient
+ cfg config.ServerConfig
+ username string
+ email string
+ }
+ type args struct {
+ ctx context.Context
+ repoName string
+ webhookUrl string
+ webhookSecret string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ wantErr assert.ErrorAssertionFunc
+ }{
+ {
+ name: "normal ok",
+ fields: fields{
+ c: MockGitLabProjects("AddProjectHook",
+ []interface{}{
+ &gitlab.ProjectHook{
+ URL: "https://dummywebhooks.local",
+ MergeRequestsEvents: true,
+ NoteEvents: true,
+ },
+ &gitlab.Response{Response: &http.Response{StatusCode: http.StatusOK}},
+ nil,
+ }),
+ cfg: config.ServerConfig{},
+ username: "",
+ email: "",
+ },
+ args: args{
+ ctx: context.TODO(),
+ repoName: "main",
+ webhookUrl: "https://dummywebhooks.local",
+ webhookSecret: "",
+ },
+ wantErr: assert.NoError,
+ },
+ {
+ name: "gitlab error",
+ fields: fields{
+ c: MockGitLabProjects("AddProjectHook",
+ []interface{}{
+ nil,
+ &gitlab.Response{Response: &http.Response{StatusCode: http.StatusInternalServerError}},
+ fmt.Errorf("dummy error"),
+ }),
+ cfg: config.ServerConfig{},
+ username: "",
+ email: "",
+ },
+ args: args{
+ ctx: context.TODO(),
+ repoName: "main",
+ webhookUrl: "https://dummywebhooks.local",
+ webhookSecret: "",
+ },
+ wantErr: assert.Error,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Client{
+ c: tt.fields.c,
+ cfg: tt.fields.cfg,
+ username: tt.fields.username,
+ email: tt.fields.email,
+ }
+ tt.wantErr(t, c.CreateHook(tt.args.ctx, tt.args.repoName, tt.args.webhookUrl, tt.args.webhookSecret), fmt.Sprintf("CreateHook(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoName, tt.args.webhookUrl, tt.args.webhookSecret))
+ })
+ }
+} Feedback & Suggestions:
😼 Mergecat review of pkg/vcs/gitlab_client/client.go@@ -9,7 +9,7 @@ import (
"strconv"
"strings"
- "github.com/chainguard-dev/git-urls"
+ giturls "github.com/chainguard-dev/git-urls"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/xanzy/go-gitlab"
@@ -22,12 +22,21 @@ import (
const GitlabTokenHeader = "X-Gitlab-Token"
type Client struct {
- c *gitlab.Client
+ c *GLClient
cfg config.ServerConfig
username, email string
}
+type GLClient struct {
+ MergeRequests MergeRequestsServices
+ RepositoryFiles RepositoryFilesServices
+ Notes NotesServices
+ Pipelines PipelinesServices
+ Projects ProjectsServices
+ Commits CommitsServices
+}
+
func CreateGitlabClient(cfg config.ServerConfig) (*Client, error) {
// Initialize the GitLab client with access token
gitlabToken := cfg.VcsToken
@@ -54,7 +63,14 @@ func CreateGitlabClient(cfg config.ServerConfig) (*Client, error) {
}
client := &Client{
- c: c,
+ c: &GLClient{
+ MergeRequests: &MergeRequestsService{c.MergeRequests},
+ RepositoryFiles: &RepositoryFilesService{c.RepositoryFiles},
+ Notes: &NotesService{c.Notes},
+ Projects: &ProjectsService{c.Projects},
+ Commits: &CommitsService{c.Commits},
+ Pipelines: &PipelinesService{c.Pipelines},
+ },
cfg: cfg,
username: user.Username,
email: user.Email,
@@ -89,7 +105,7 @@ func (c *Client) VerifyHook(r *http.Request, secret string) ([]byte, error) {
var nilPr vcs.PullRequest
// ParseHook parses and validates a webhook event; return an err if this isn't valid
-func (c *Client) ParseHook(r *http.Request, request []byte) (vcs.PullRequest, error) {
+func (c *Client) ParseHook(ctx context.Context, r *http.Request, request []byte) (vcs.PullRequest, error) {
eventRequest, err := gitlab.ParseHook(gitlab.HookEventType(r), request)
if err != nil {
return nilPr, err
@@ -109,6 +125,20 @@ func (c *Client) ParseHook(r *http.Request, request []byte) (vcs.PullRequest, er
log.Trace().Msgf("Unhandled Action %s", event.ObjectAttributes.Action)
return nilPr, vcs.ErrInvalidType
}
+ case *gitlab.MergeCommentEvent:
+ switch event.ObjectAttributes.Action {
+ case gitlab.CommentEventActionCreate:
+ if strings.ToLower(event.ObjectAttributes.Note) == c.cfg.ReplanCommentMessage {
+ log.Info().Msgf("Got %s comment, Running again", c.cfg.ReplanCommentMessage)
+ return c.buildRepoFromComment(event), nil
+ } else {
+ log.Info().Msg("ignoring Gitlab merge comment event due to non matching string")
+ return nilPr, vcs.ErrInvalidType
+ }
+ default:
+ log.Info().Msg("ignoring Gitlab issue comment event due to non matching string")
+ return nilPr, vcs.ErrInvalidType
+ }
default:
log.Trace().Msgf("Unhandled Event: %T", event)
return nilPr, vcs.ErrInvalidType
@@ -145,6 +175,10 @@ func (c *Client) GetHookByUrl(ctx context.Context, repoName, webhookUrl string)
if hook.MergeRequestsEvents {
events = append(events, string(gitlab.MergeRequestEventTargetType))
}
+ if hook.NoteEvents {
+ events = append(events, string(gitlab.NoteEventTargetType))
+ }
+
return &vcs.WebHookConfig{
Url: hook.URL,
Events: events,
@@ -161,13 +195,14 @@ func (c *Client) CreateHook(ctx context.Context, repoName, webhookUrl, webhookSe
return errors.Wrap(err, "failed to parse repo name")
}
- _, _, err = c.c.Projects.AddProjectHook(pid, &gitlab.AddProjectHookOptions{
+ _, glStatus, err := c.c.Projects.AddProjectHook(pid, &gitlab.AddProjectHookOptions{
URL: pkg.Pointer(webhookUrl),
MergeRequestsEvents: pkg.Pointer(true),
+ NoteEvents: pkg.Pointer(true),
Token: pkg.Pointer(webhookSecret),
})
- if err != nil {
+ if err != nil && glStatus.StatusCode < http.StatusOK || glStatus.StatusCode >= http.StatusMultipleChoices {
return errors.Wrap(err, "failed to create project webhook")
}
@@ -240,3 +275,26 @@ func (c *Client) buildRepoFromEvent(event *gitlab.MergeEvent) vcs.PullRequest {
Config: c.cfg,
}
}
+
+func (c *Client) buildRepoFromComment(event *gitlab.MergeCommentEvent) vcs.PullRequest {
+ // Convert all labels from this MR to a string array of label names
+ var labels []string
+ for _, label := range event.MergeRequest.Labels {
+ labels = append(labels, label.Title)
+ }
+ return vcs.PullRequest{
+ BaseRef: event.MergeRequest.TargetBranch,
+ HeadRef: event.MergeRequest.SourceBranch,
+ DefaultBranch: event.Project.DefaultBranch,
+ FullName: event.Project.PathWithNamespace,
+ CloneURL: event.Project.GitHTTPURL,
+ Name: event.Project.Name,
+ CheckID: event.MergeRequest.IID,
+ SHA: event.MergeRequest.LastCommit.ID,
+ Username: c.username,
+ Email: c.email,
+ Labels: labels,
+
+ Config: c.cfg,
+ }
+} Feedback & Suggestions:
Overall, the changes introduce useful abstractions and features, but ensure that they are well-documented and tested to maintain code quality and security. 🚀 Dependency ReviewClick to read mergecats review!No suggestions found |
Fixes #105