Skip to content

Commit e3c0d9c

Browse files
committed
feat: Add CEL expression evaluator command
This commit introduces a new command `tkn pac cel` that allows users to interactively evaluate CEL (Common Expression Language) expressions. The command is designed to help users test and debug CEL expressions, which are commonly used in Pipelines-as-Code. Key features include: - Interactive and non-interactive modes. - Support for webhook payloads and headers. - Provider auto-detection (GitHub, GitLab, Bitbucket Cloud, Bitbucket Data Center, Gitea). - Direct access to variables as per PAC documentation. - Cross-platform history with readline experience. - Comprehensive help and example expressions. Signed-off-by: Chmouel Boudjnah <[email protected]> feat: Support gosmee-generated shell scripts for headers - Allow the CLI to parse headers from gosmee scripts. - Add support for JSON, plain text and gosmee scripts header formats. - Update documentation to reflect the changes in the header file formats. - Add tests to validate the script and header parsing. Signed-off-by: Chmouel Boudjnah <[email protected]>
1 parent 87df55c commit e3c0d9c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+8572
-1
lines changed

docs/content/docs/guide/cli.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,111 @@ You can specify a different directory than the current one by using the -d/--dir
409409

410410
{{< /details >}}
411411

412+
{{< details "tkn pac cel" >}}
413+
414+
### CEL Expression Evaluator
415+
416+
`tkn pac cel` — Evaluate CEL (Common Expression Language) expressions interactively with webhook payloads.
417+
418+
This command allows you to test and debug CEL expressions as they would be evaluated by Pipelines-as-Code, using real webhook payloads and headers. It supports interactive and non-interactive modes, provider auto-detection, and persistent history.
419+
420+
To be able to have the CEL evaluator working, you need to have the payload and the headers available in a file. The best way to do this is to go to the webhook configuration on your git provider and copy the payload and headers to different files.
421+
422+
The payload is the JSON content of the webhook request, The headers file supports multiple formats:
423+
424+
1. **Plain HTTP headers format** (as shown above)
425+
2. **JSON format**:
426+
427+
```json
428+
{
429+
"X-GitHub-Event": "pull_request",
430+
"Content-Type": "application/json",
431+
"User-Agent": "GitHub-Hookshot/2d5e4d4"
432+
}
433+
```
434+
435+
3. **Gosmee-generated shell scripts**: The command automatically detects and parses shell scripts generated by [gosmee](https://github.com/chmouel/gosmee) which are generated when using the `--save` feature, extracting headers from curl commands with `-H` flags:
436+
437+
```bash
438+
#!/usr/bin/env bash
439+
curl -X POST "http://localhost:8080/" \
440+
-H "X-GitHub-Event: pull_request" \
441+
-H "Content-Type: application/json" \
442+
-H "User-Agent: GitHub-Hookshot/2d5e4d4" \
443+
-d @payload.json
444+
```
445+
446+
#### Usage
447+
448+
```shell
449+
tkn pac cel -b <body.json> -H <headers.txt>
450+
```
451+
452+
* `-b, --body`: Path to JSON body file (webhook payload)
453+
* `-H, --headers`: Path to headers file (plain text, JSON, or gosmee script)
454+
* `-p, --provider`: Provider (auto, github, gitlab, bitbucket-cloud, bitbucket-datacenter, gitea)
455+
456+
#### Interactive Mode
457+
458+
If run in a terminal, you'll get a prompt:
459+
460+
```console
461+
CEL expression>
462+
```
463+
464+
* Use ↑/↓ arrows to navigate history.
465+
* History is saved and loaded automatically.
466+
* Press Enter on an empty line to exit.
467+
468+
#### Non-Interactive Mode
469+
470+
Pipe expressions via stdin:
471+
472+
```shell
473+
echo 'event == "pull_request"' | tkn pac cel -b body.json -H headers.txt
474+
```
475+
476+
#### Available Variables
477+
478+
* **Direct variables** (top-level, as per PAC documentation):
479+
* `event` — event type (push, pull_request)
480+
* `target_branch` — target branch name
481+
* `source_branch` — source branch name
482+
* `target_url` — target repository URL
483+
* `source_url` — source repository URL
484+
* `event_title` — PR title or commit message
485+
486+
* **Webhook payload** (`body.*`): All fields from the webhook JSON.
487+
* **HTTP headers** (`headers.*`): All HTTP headers.
488+
* **Files** (`files.*`): Always empty in CLI mode.
489+
**Note:** `fileChanged`, `fileDeleted`, `fileModified` and similar functions are **not implemented yet** in the CLI.
490+
* **PAC Parameters** (`pac.*`): All variables for backward compatibility.
491+
492+
#### Example Expressions
493+
494+
```text
495+
event == "pull_request" && target_branch == "main"
496+
event == "pull_request" && source_branch.matches(".*feat/.*")
497+
body.action == "synchronize"
498+
!body.pull_request.draft
499+
headers['x-github-event'] == "pull_request"
500+
event == "pull_request" && target_branch != "experimental"
501+
```
502+
503+
#### Limitations
504+
505+
* `files.*` variables are always empty in CLI mode.
506+
* Functions like `fileChanged`, `fileDeleted`, `fileModified` are **not implemented yet** in the CLI.
507+
508+
#### Cross-Platform History
509+
510+
* History is saved in a cache directory:
511+
* Linux/macOS: `~/.cache/tkn-pac/cel-history`
512+
* Windows: `%USERPROFILE%\.cache\tkn-pac\cel-history`
513+
* The directory is created automatically if it does not exist.
514+
515+
{{< /details >}}
516+
412517
## Screenshot
413518

414519
![tkn-plug-in](/images/tkn-pac-cli.png)

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
code.gitea.io/sdk/gitea v0.21.0
1010
github.com/AlecAivazis/survey/v2 v2.3.7
1111
github.com/bradleyfalzon/ghinstallation/v2 v2.15.0
12+
github.com/chzyer/readline v1.5.1
1213
github.com/cloudevents/sdk-go/v2 v2.16.0
1314
github.com/fvbommel/sortorder v1.1.0
1415
github.com/gobwas/glob v0.2.3
@@ -129,7 +130,7 @@ require (
129130
golang.org/x/crypto v0.37.0 // indirect
130131
golang.org/x/net v0.39.0 // indirect
131132
golang.org/x/sys v0.33.0 // indirect
132-
golang.org/x/term v0.31.0 // indirect
133+
golang.org/x/term v0.31.0
133134
golang.org/x/time v0.11.0 // indirect
134135
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
135136
google.golang.org/api v0.231.0 // indirect

go.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,14 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
8888
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
8989
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
9090
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
91+
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
92+
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
9193
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
94+
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
95+
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
9296
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
97+
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
98+
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
9399
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
94100
github.com/cloudevents/sdk-go/observability/opencensus/v2 v2.15.2 h1:AbtPqiUDzKup5JpTZzO297/QXgL/TAdpdXQCNwLzlaM=
95101
github.com/cloudevents/sdk-go/observability/opencensus/v2 v2.15.2/go.mod h1:ZbYLE+yaEQ2j4vbRc9qzvGmg30A9LhwFt/1bSebNnbU=
@@ -675,6 +681,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
675681
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
676682
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
677683
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
684+
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
678685
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
679686
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
680687
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

pkg/cel/cel.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,26 @@ func Value(query string, body any, headers, pacParams map[string]string, changed
5555
decls.NewVariable("headers", mapStrDyn),
5656
decls.NewVariable("pac", mapStrDyn),
5757
decls.NewVariable("files", mapStrDyn),
58+
// Direct variables as per documentation
59+
decls.NewVariable("event", types.StringType),
60+
decls.NewVariable("target_branch", types.StringType),
61+
decls.NewVariable("source_branch", types.StringType),
62+
decls.NewVariable("target_url", types.StringType),
63+
decls.NewVariable("source_url", types.StringType),
64+
decls.NewVariable("event_title", types.StringType),
5865
))
5966
val, err := evaluate(query, celDec, map[string]any{
6067
"body": jsonMap,
6168
"pac": pacParams,
6269
"headers": headers,
6370
"files": changedFiles,
71+
// Direct variables
72+
"event": pacParams["event"],
73+
"target_branch": pacParams["target_branch"],
74+
"source_branch": pacParams["source_branch"],
75+
"target_url": pacParams["target_url"],
76+
"source_url": pacParams["source_url"],
77+
"event_title": pacParams["event_title"],
6478
})
6579
if err != nil {
6680
return nil, err

0 commit comments

Comments
 (0)