Skip to content
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

feat: add support for evaluating all open MRs #11

Merged
merged 2 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
run::cli:
# NOTE: This is integration testing file, its not meant for production usage
# The README has a production example

scm-engine::evaluate::on-change:
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golang:1.22.3
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
script:
- go mod tidy
- go run ./cmd/scm-engine/ evaluate

scm-engine::evaluate::on-schedule:
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golang:1.22.3
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- go mod tidy
- go run ./cmd/scm-engine/ evaluate all
34 changes: 29 additions & 5 deletions .scm-engine.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,44 @@

actions:
- name: Warn that the ticket if older than 30 days
if: merge_request.state != "closed" && merge_request.time_since_last_commit > duration("30d") && not merge_request.has_label("do-not-close")
if: |
// ignore MRs already closed
merge_request.state != "closed"
// if last commit happened more than 30 days ago
&& merge_request.time_since_last_commit > duration("30d")
// but still less than 45 days ago (where we close the MR)
&& merge_request.time_since_last_commit < duration("45d")
// and the label to disable this feature is not on the MR
&& not merge_request.has_label("do-not-close")
then:
- action: comment
message: "Hey, this MR is old, we will close it in 15 days if no activity has happened. If you want to disable this behavior, add the label 'do-not-close' on the MR."
message: |
Hello!

This Merge Request have not seen any commit activity for 30 days. In an effort to keep our project clean we will automatically close the Merge request after 45 days.

You can add the "do-not-close" label to the Merge Request to disable this behavior.

- name: Close ticket if older than 45 days
if: merge_request.state != "closed" && merge_request.time_since_last_commit > duration("45d") && not merge_request.has_label("do-not-close")
if: |
merge_request.state != "closed"
&& merge_request.time_since_last_commit > duration("45d")
&& not merge_request.has_label("do-not-close")
then:
- action: close
- action: comment
message: "As promised, we're closing the MR due to inactivity, bye bye"
message: |
Hello!

This Merge Request have not seen any commit activity for 45 days. In an effort to keep our project clean we will automatically close the Merge request.

You can add the "do-not-close" label to the Merge Request to disable this behavior.

- name: Approve MR if the 'break-glass-approve' label is configured
if: merge_request.state != "closed" && not merge_request.approved && merge_request.has_label("break-glass-approve")
if: |
merge_request.state != "closed"
&& not merge_request.approved
&& merge_request.has_label("break-glass-approve")
then:
- action: approve
- action: comment
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,19 @@ Using scm-engine within a GitLab CI pipeline is straight forward.
1. Setup a CI job using the `scm-engine` Docker image that will run when a pipeline is created from a Merge Request Event.

```yaml
scm-engine:
scm-engine::evaluate::on-merge-request-event:
image: ghcr.io/jippi/scm-engine:latest
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
script:
- scm-engine evaluate

scm-engine::evaluate::on-schedule:
image: ghcr.io/jippi/scm-engine:latest
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- scm-engine evaluate all
```

1. Done! Every Merge Request change should now re-run scm-engine and apply your label rules
Expand Down
18 changes: 16 additions & 2 deletions cmd/cmd_evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/jippi/scm-engine/pkg/config"
"github.com/jippi/scm-engine/pkg/scm"
"github.com/jippi/scm-engine/pkg/scm/gitlab"
"github.com/jippi/scm-engine/pkg/state"
"github.com/urfave/cli/v2"
Expand All @@ -23,6 +24,19 @@ func Evaluate(cCtx *cli.Context) error {
}

switch {
// If first arg is 'all' we will find all opened MRs and apply the rules to them
case cCtx.Args().First() == "all":
res, err := client.MergeRequests().List(ctx, &scm.ListMergeRequestsOptions{State: "opened", First: 100})
if err != nil {
return err
}

for _, mr := range res {
if err := ProcessMR(ctx, client, cfg, mr.ID); err != nil {
return err
}
}

// If the flag is set, use that for evaluation
case cCtx.String(FlagMergeRequestID) != "":
return ProcessMR(ctx, client, cfg, cCtx.String(FlagMergeRequestID))
Expand All @@ -37,7 +51,7 @@ func Evaluate(cCtx *cli.Context) error {
return err
}
}

return nil
}

return nil
}
35 changes: 35 additions & 0 deletions pkg/scm/gitlab/client_merge_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"fmt"
"net/http"

"github.com/hasura/go-graphql-client"
"github.com/jippi/scm-engine/pkg/scm"
"github.com/jippi/scm-engine/pkg/state"
go_gitlab "github.com/xanzy/go-gitlab"
"golang.org/x/oauth2"
)

var _ scm.MergeRequestClient = (*MergeRequestClient)(nil)
Expand Down Expand Up @@ -43,3 +45,36 @@ func (client *MergeRequestClient) Update(ctx context.Context, opt *scm.UpdateMer

return convertResponse(resp), err
}

func (client *MergeRequestClient) List(ctx context.Context, options *scm.ListMergeRequestsOptions) ([]scm.ListMergeRequest, error) {
httpClient := oauth2.NewClient(
ctx,
oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: client.client.token,
},
),
)

graphqlClient := graphql.NewClient(graphqlBaseURL(client.client.wrapped.BaseURL())+"/api/graphql", httpClient)

var (
result *ListMergeRequestsQuery
variables = map[string]any{
"project_id": graphql.ID(state.ProjectIDFromContext(ctx)),
"state": MergeRequestState(options.State),
"first": options.First,
}
)

if err := graphqlClient.Query(ctx, &result, variables); err != nil {
return nil, err
}

hits := []scm.ListMergeRequest{}
for _, x := range result.Project.MergeRequests.Nodes {
hits = append(hits, scm.ListMergeRequest{ID: x.ID})
}

return hits, nil
}
1 change: 1 addition & 0 deletions pkg/scm/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type LabelClient interface {

type MergeRequestClient interface {
Update(ctx context.Context, opt *UpdateMergeRequestOptions) (*Response, error)
List(ctx context.Context, options *ListMergeRequestsOptions) ([]ListMergeRequest, error)
}

type EvalContext interface {
Expand Down
10 changes: 10 additions & 0 deletions pkg/scm/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ type ListOptions struct {
Sort string `json:"sort,omitempty" url:"sort,omitempty"`
}

type ListMergeRequestsOptions struct {
ListOptions
State string
First int
}

type ListMergeRequest struct {
ID string `expr:"id" graphql:"id"`
}

// Response is a GitLab API response. This wraps the standard http.Response
// returned from GitLab and provides convenient access to things like
// pagination links.
Expand Down
9 changes: 8 additions & 1 deletion schema/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ func main() {
func nest(props []*Property) {
for _, field := range props {
if field.IsCustomType {
for _, nested := range PropMap[field.Type].Attributes {
attr, ok := PropMap[field.Type]
if !ok {
continue
}

for _, nested := range attr.Attributes {
field.AddAttribute(&Property{
Name: nested.Name,
Description: nested.Description,
Expand Down Expand Up @@ -149,6 +154,8 @@ func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
Description: model.Description,
}

fmt.Println("model", modelProperty.Name)

for _, field := range model.Fields {
tags, err := structtag.Parse(field.Tag)
if err != nil {
Expand Down
32 changes: 32 additions & 0 deletions schema/gitlab.schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ scalar Time
# Add time.Duration support
scalar Duration


type Context {
"The project the Merge Request belongs to"
Project: ContextProject @graphql(key: "project(fullPath: $project_id)")
Expand All @@ -32,6 +33,37 @@ type Context {
MergeRequest: ContextMergeRequest @generated
}

enum MergeRequestState {
all
closed
locked
merged
opened
}

input ListMergeRequestsQueryInput {
project_id: ID!
state: MergeRequestState! = "opened"
first: Int! = 100
}

type ListMergeRequestsQuery {
"The project the Merge Request belongs to"
Project: ListMergeRequestsProject @graphql(key: "project(fullPath: $project_id)")
}

type ListMergeRequestsProject {
MergeRequests: ListMergeRequestsProjectMergeRequestNodes @graphql(key: "mergeRequests(state: $state, first: $first)") @internal
}

type ListMergeRequestsProjectMergeRequestNodes {
Nodes: [ListMergeRequestsProjectMergeRequest!]
}

type ListMergeRequestsProjectMergeRequest {
ID: String! @graphql(key: "iid") @internal
}

type ContextProject {
#
# Native GraphQL fields - https://docs.gitlab.com/ee/api/graphql/reference/#project
Expand Down
Loading