Skip to content

Commit 0e31962

Browse files
authored
feat: add --dry-run flag (#15)
* feat: add --dry-run flag Fixes #14 Signed-off-by: Christian Winther <jippignu@gmail.com> * docs: update docs a bit --------- Signed-off-by: Christian Winther <jippignu@gmail.com>
1 parent bd50d4e commit 0e31962

12 files changed

+106
-43
lines changed

README.md

+38-2
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,53 @@ NAME:
156156
scm-engine evaluate - Evaluate a Merge Request
157157
158158
USAGE:
159-
scm-engine evaluate [command options]
159+
scm-engine evaluate [command options] [id, id, ...]
160160
161161
OPTIONS:
162+
--project value GitLab project (example: 'gitlab-org/gitlab') [$GITLAB_PROJECT, $CI_PROJECT_PATH]
162163
--id value, --merge-request-id value, --pull-request-id value The pull/merge to process, if not provided as a CLI flag [$CI_MERGE_REQUEST_IID]
164+
--help, -h show help
163165
164166
GLOBAL OPTIONS:
165167
--config value Path to the scm-engine config file (default: ".scm-engine.yml") [$SCM_ENGINE_CONFIG_FILE]
166168
--api-token value GitHub/GitLab API token [$SCM_ENGINE_TOKEN]
167-
--project value GitLab project (example: 'gitlab-org/gitlab') [$GITLAB_PROJECT, $CI_PROJECT_PATH]
168169
--base-url value Base URL for the SCM instance (default: "https://gitlab.com/") [$GITLAB_BASEURL, $CI_SERVER_URL]
170+
--dry-run Dry run, don't actually _do_ actions, just print them (default: false)
169171
--help, -h show help
172+
--version, -v print the version
173+
```
174+
175+
### `server`
176+
177+
Point your GitLab webhook at the `/gitlab` endpoint.
178+
179+
Support the following events, and they will both trigger an Merge Request `evaluation`
180+
181+
- [`Comments`](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#comment-events) - A comment is made or edited on an issue or merge request.
182+
- [`Merge request events`](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#merge-request-events) - A merge request is created, updated, or merged.
183+
184+
> [!TIP]
185+
> You have access to the raw webhook event payload via `webhook_event.*` fields in Expr script fields when using `server` mode. See the [GitLab Webhook Events documentation](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html) for available fields.
186+
187+
```plain
188+
NAME:
189+
scm-engine server - Start HTTP server for webhook event driven usage
190+
191+
USAGE:
192+
scm-engine server [command options]
193+
194+
OPTIONS:
195+
--webhook-secret value Used to validate received payloads. Sent with the request in the X-Gitlab-Token HTTP header [$SCM_ENGINE_WEBHOOK_SECRET]
196+
--listen value Port the HTTP server should listen on (default: "0.0.0.0:3000") [$SCM_ENGINE_LISTEN]
197+
--help, -h show help
198+
199+
GLOBAL OPTIONS:
200+
--config value Path to the scm-engine config file (default: ".scm-engine.yml") [$SCM_ENGINE_CONFIG_FILE]
201+
--api-token value GitHub/GitLab API token [$SCM_ENGINE_TOKEN]
202+
--base-url value Base URL for the SCM instance (default: "https://gitlab.com/") [$GITLAB_BASEURL, $CI_SERVER_URL]
203+
--dry-run Dry run, don't actually _do_ actions, just print them (default: false)
204+
--help, -h show help
205+
--version, -v print the version
170206
```
171207

172208
## Configuration file

cmd/cmd_evaluate.go

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
func Evaluate(cCtx *cli.Context) error {
1414
ctx := state.ContextWithProjectID(cCtx.Context, cCtx.String(FlagSCMProject))
15+
ctx = state.ContextWithDryRun(ctx, cCtx.Bool(FlagDryRun))
1516

1617
cfg, err := config.LoadFile(cCtx.String(FlagConfigFile))
1718
if err != nil {

cmd/cmd_server.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ func Server(cCtx *cli.Context) error { //nolint:unparam
176176
ReadTimeout: 5 * time.Second,
177177
WriteTimeout: 5 * time.Second,
178178
BaseContext: func(l net.Listener) context.Context {
179-
return cCtx.Context
179+
ctx := state.ContextWithDryRun(cCtx.Context, cCtx.Bool(FlagDryRun))
180+
181+
return ctx
180182
},
181183
}
182184

cmd/conventions.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package cmd
22

33
const (
4-
FlagConfigFile = "config"
54
FlagAPIToken = "api-token"
6-
FlagSCMProject = "project"
7-
FlagSCMBaseURL = "base-url"
5+
FlagConfigFile = "config"
6+
FlagDryRun = "dry-run"
87
FlagMergeRequestID = "id"
8+
FlagSCMBaseURL = "base-url"
9+
FlagSCMProject = "project"
10+
FlagServerListen = "listen"
911
FlagWebhookSecret = "webhook-secret"
10-
FlagServerListen = "listen-port"
1112
)

cmd/shared.go

+14
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ func ProcessMR(ctx context.Context, client scm.Client, cfg *config.Config, mr st
8484
}
8585

8686
func updateMergeRequest(ctx context.Context, client scm.Client, update *scm.UpdateMergeRequestOptions) error {
87+
if state.IsDryRun(ctx) {
88+
slogctx.Info(ctx, "In dry-run, dumping the update struct we would send to GitLab", slog.Any("changes", update))
89+
90+
return nil
91+
}
92+
8793
_, err := client.MergeRequests().Update(ctx, update)
8894

8995
return err
@@ -117,6 +123,10 @@ func syncLabels(ctx context.Context, client scm.Client, remote []*scm.Label, req
117123

118124
slogctx.Info(ctx, "Creating label", slog.String("label", label.Name))
119125

126+
if state.IsDryRun(ctx) {
127+
continue
128+
}
129+
120130
_, resp, err := client.Labels().Create(ctx, &scm.CreateLabelOptions{
121131
Name: &label.Name, //nolint:gosec
122132
Color: &label.Color, //nolint:gosec
@@ -148,6 +158,10 @@ func syncLabels(ctx context.Context, client scm.Client, remote []*scm.Label, req
148158

149159
slogctx.Info(ctx, "Updating label", slog.String("label", label.Name))
150160

161+
if state.IsDryRun(ctx) {
162+
continue
163+
}
164+
151165
_, _, err := client.Labels().Update(ctx, &scm.UpdateLabelOptions{
152166
Name: &label.Name, //nolint:gosec
153167
Color: &label.Color, //nolint:gosec

go.mod

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ require (
1414
github.com/iancoleman/strcase v0.3.0
1515
github.com/lmittmann/tint v1.0.4
1616
github.com/muesli/termenv v0.15.2
17-
github.com/reugn/pkgslog v0.2.0
1817
github.com/samber/slog-multi v1.0.2
1918
github.com/teacat/noire v1.1.0
2019
github.com/urfave/cli/v2 v2.27.2

go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
6767
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6868
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
6969
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
70-
github.com/reugn/pkgslog v0.2.0 h1:Kedn37OrnOh+5cBxNNwrUHR7e8175CQLk8QztbPZ+VQ=
71-
github.com/reugn/pkgslog v0.2.0/go.mod h1:Gb0SqIq+BCzAeTeWdHxiVz4S206U30WBd6gSsnjfMxo=
7270
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
7371
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
7472
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=

main.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ func main() {
6060
"SCM_ENGINE_TOKEN",
6161
},
6262
},
63-
6463
&cli.StringFlag{
6564
Name: cmd.FlagSCMBaseURL,
6665
Usage: "Base URL for the SCM instance",
@@ -70,12 +69,18 @@ func main() {
7069
"CI_SERVER_URL",
7170
},
7271
},
72+
&cli.BoolFlag{
73+
Name: cmd.FlagDryRun,
74+
Usage: "Dry run, don't actually _do_ actions, just print them",
75+
},
7376
},
7477
Commands: []*cli.Command{
7578
{
76-
Name: "evaluate",
77-
Usage: "Evaluate a Merge Request",
78-
Action: cmd.Evaluate,
79+
Name: "evaluate",
80+
Usage: "Evaluate a Merge Request",
81+
Args: true,
82+
ArgsUsage: " [id, id, ...]",
83+
Action: cmd.Evaluate,
7984
Flags: []cli.Flag{
8085
&cli.StringFlag{
8186
Name: cmd.FlagSCMProject,

pkg/scm/gitlab/client_actioner.go

+20
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"log/slog"
78

89
"github.com/jippi/scm-engine/pkg/scm"
910
"github.com/jippi/scm-engine/pkg/state"
11+
slogctx "github.com/veqryn/slog-context"
1012
"github.com/xanzy/go-gitlab"
1113
)
1214

@@ -35,11 +37,23 @@ func (c *Client) ApplyStep(ctx context.Context, update *scm.UpdateMergeRequestOp
3537
update.DiscussionLocked = gitlab.Ptr(false)
3638

3739
case "approve":
40+
if state.IsDryRun(ctx) {
41+
slogctx.Info(ctx, "Approving MR")
42+
43+
return nil
44+
}
45+
3846
_, _, err := c.wrapped.MergeRequestApprovals.ApproveMergeRequest(state.ProjectIDFromContext(ctx), state.MergeRequestIDFromContextInt(ctx), &gitlab.ApproveMergeRequestOptions{})
3947

4048
return err
4149

4250
case "unapprove":
51+
if state.IsDryRun(ctx) {
52+
slogctx.Info(ctx, "Unapproving MR")
53+
54+
return nil
55+
}
56+
4357
_, err := c.wrapped.MergeRequestApprovals.UnapproveMergeRequest(state.ProjectIDFromContext(ctx), state.MergeRequestIDFromContextInt(ctx))
4458

4559
return err
@@ -59,6 +73,12 @@ func (c *Client) ApplyStep(ctx context.Context, update *scm.UpdateMergeRequestOp
5973
return errors.New("step field 'message' must not be an empty string")
6074
}
6175

76+
if state.IsDryRun(ctx) {
77+
slogctx.Info(ctx, "Commenting on MR", slog.String("message", msgString))
78+
79+
return nil
80+
}
81+
6282
_, _, err := c.wrapped.Notes.CreateMergeRequestNote(state.ProjectIDFromContext(ctx), state.MergeRequestIDFromContextInt(ctx), &gitlab.CreateMergeRequestNoteOptions{
6383
Body: gitlab.Ptr(msgString),
6484
})

pkg/state/context.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package state
22

33
import (
44
"context"
5+
"log/slog"
56
"strconv"
67

78
slogctx "github.com/veqryn/slog-context"
@@ -11,6 +12,7 @@ type contextKey uint
1112

1213
const (
1314
projectID contextKey = iota
15+
dryRun
1416
mergeRequestID
1517
)
1618

@@ -27,12 +29,23 @@ func ProjectIDFromContext(ctx context.Context) string {
2729
}
2830

2931
func ContextWithProjectID(ctx context.Context, value string) context.Context {
30-
ctx = slogctx.With(ctx, "project_id", value)
32+
ctx = slogctx.With(ctx, slog.String("project_id", value))
3133
ctx = context.WithValue(ctx, projectID, value)
3234

3335
return ctx
3436
}
3537

38+
func ContextWithDryRun(ctx context.Context, dry bool) context.Context {
39+
ctx = slogctx.With(ctx, slog.Bool("dry_run", dry))
40+
ctx = context.WithValue(ctx, dryRun, dry)
41+
42+
return ctx
43+
}
44+
45+
func IsDryRun(ctx context.Context) bool {
46+
return ctx.Value(dryRun).(bool) //nolint:forcetypeassert
47+
}
48+
3649
func ContextWithMergeRequestID(ctx context.Context, id string) context.Context {
3750
ctx = slogctx.With(ctx, "merge_request_id", id)
3851
ctx = context.WithValue(ctx, mergeRequestID, id)

pkg/tui/context.go

-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/charmbracelet/lipgloss"
99
"github.com/muesli/termenv"
10-
"github.com/reugn/pkgslog"
1110
slogmulti "github.com/samber/slog-multi"
1211
slogctx "github.com/veqryn/slog-context"
1312
slogdedup "github.com/veqryn/slog-dedup"
@@ -33,11 +32,6 @@ func NewContext(ctx context.Context, stdout, stderr io.Writer) context.Context {
3332
ctx,
3433
slog.New(
3534
slogmulti.
36-
Pipe(
37-
func(next slog.Handler) slog.Handler {
38-
return pkgslog.NewPackageHandler(next, packageLogLevels())
39-
},
40-
).
4135
Pipe(
4236
slogctx.NewMiddleware(&slogctx.HandlerOptions{}),
4337
).

pkg/tui/logger.go

+2-22
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,6 @@ func ParseLogLevel(name string, fallback slog.Level) slog.Level {
3232
}
3333
}
3434

35-
func pkgLogLevel(name string, fallback slog.Level) slog.Level {
36-
return ParseLogLevel(os.Getenv(name+"_LOG_LEVEL"), fallback)
37-
}
38-
39-
func packageLogLevels() map[string]slog.Level {
40-
logLevel := ParseLogLevel(os.Getenv("LOG_LEVEL"), slog.LevelInfo)
41-
42-
lowestOf := func(in slog.Level) slog.Level {
43-
if in < logLevel {
44-
return in
45-
}
46-
47-
return logLevel
48-
}
49-
50-
return map[string]slog.Level{
51-
pkgPrefix + "/pkg/parser": pkgLogLevel("PARSER", lowestOf(slog.LevelWarn)),
52-
pkgPrefix + "/pkg/scanner": pkgLogLevel("SCANNER", lowestOf(slog.LevelWarn)),
53-
}
54-
}
55-
5635
func logHandler(out io.Writer) slog.Handler {
5736
logLevel := ParseLogLevel(os.Getenv("LOG_LEVEL"), slog.LevelInfo)
5837

@@ -69,7 +48,8 @@ func logHandler(out io.Writer) slog.Handler {
6948
return devslog.NewHandler(
7049
out,
7150
&devslog.Options{
72-
SortKeys: true,
51+
SortKeys: true,
52+
MaxSlicePrintSize: 999,
7353
HandlerOptions: &slog.HandlerOptions{
7454
Level: logLevel,
7555
AddSource: logLevel == slog.LevelDebug,

0 commit comments

Comments
 (0)