Skip to content

Commit 3d13ed7

Browse files
committed
feat(cel): add direct custom param variable access
Allow custom parameters from Repository CR to be accessed directly as CEL variables without template expansion. Parameters can now be used as: param_name == "value" on top of "{{param_name}}" == "value". Jira: https://issues.redhat.com/browse/SRVKP-9118 Signed-off-by: Akshay Pant <akshay.akshaypant@gmail.com>
1 parent a8212d3 commit 3d13ed7

File tree

4 files changed

+348
-6
lines changed

4 files changed

+348
-6
lines changed

pkg/matcher/annotation_matcher.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import (
99
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode"
1010
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
1111
apipac "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
12+
"github.com/openshift-pipelines/pipelines-as-code/pkg/customparams"
1213
pacerrors "github.com/openshift-pipelines/pipelines-as-code/pkg/errors"
1314
"github.com/openshift-pipelines/pipelines-as-code/pkg/events"
1415
"github.com/openshift-pipelines/pipelines-as-code/pkg/formatting"
16+
"github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction"
1517
"github.com/openshift-pipelines/pipelines-as-code/pkg/opscomments"
1618
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
1719
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
@@ -210,6 +212,12 @@ func MatchPipelinerunByAnnotation(ctx context.Context, logger *zap.SugaredLogger
210212
}
211213
logger.Info(infomsg)
212214

215+
// Resolve custom params once for all PipelineRuns (for use in CEL expressions)
216+
customParams := resolveCustomParamsForCEL(ctx, repo, event, cs, vcx, eventEmitter, logger)
217+
if len(customParams) > 0 {
218+
logger.Debugf("resolved %d custom params from repo for CEL", len(customParams))
219+
}
220+
213221
celValidationErrors := []*pacerrors.PacYamlValidations{}
214222
for _, prun := range pruns {
215223
prMatch := Match{
@@ -280,7 +288,7 @@ func MatchPipelinerunByAnnotation(ctx context.Context, logger *zap.SugaredLogger
280288
if celExpr, ok := prun.GetObjectMeta().GetAnnotations()[keys.OnCelExpression]; ok {
281289
checkPipelineRunAnnotation(prun, eventEmitter, repo)
282290

283-
out, err := celEvaluate(ctx, celExpr, event, vcx)
291+
out, err := celEvaluate(ctx, celExpr, event, vcx, customParams)
284292
if err != nil {
285293
logger.Errorf("there was an error evaluating the CEL expression, skipping: %v", err)
286294
if checkIfCELEvaluateError(err) {
@@ -508,3 +516,40 @@ func MatchRunningPipelineRunForIncomingWebhook(eventType, incomingPipelineRun st
508516
}
509517
return nil
510518
}
519+
520+
// resolveCustomParamsForCEL resolves custom parameters from the Repository CR for use in CEL expressions.
521+
// It returns a map of parameter names to values, excluding reserved keywords.
522+
// All parameters are returned as strings, including those from secret_ref.
523+
func resolveCustomParamsForCEL(ctx context.Context, repo *apipac.Repository, event *info.Event, cs *params.Run, vcx provider.Interface, eventEmitter *events.EventEmitter, logger *zap.SugaredLogger) map[string]string {
524+
if repo == nil || repo.Spec.Params == nil {
525+
return map[string]string{}
526+
}
527+
528+
// Create kubeinteraction interface
529+
kinteract, err := kubeinteraction.NewKubernetesInteraction(cs)
530+
if err != nil {
531+
logger.Warnf("failed to create kubernetes interaction for custom params: %s", err.Error())
532+
return map[string]string{}
533+
}
534+
535+
// Use existing customparams package to resolve all params
536+
cp := customparams.NewCustomParams(event, repo, cs, kinteract, eventEmitter, vcx)
537+
allParams, _, err := cp.GetParams(ctx)
538+
if err != nil {
539+
eventEmitter.EmitMessage(repo, zap.WarnLevel, "CustomParamsCELError",
540+
fmt.Sprintf("failed to resolve custom params for CEL: %s", err.Error()))
541+
return map[string]string{}
542+
}
543+
544+
// Filter to only include params defined in repo.Spec.Params (not standard PAC params)
545+
result := make(map[string]string)
546+
if repo.Spec.Params != nil {
547+
for _, param := range *repo.Spec.Params {
548+
if value, ok := allParams[param.Name]; ok {
549+
result[param.Name] = value
550+
}
551+
}
552+
}
553+
554+
return result
555+
}

pkg/matcher/annotation_matcher_test.go

Lines changed: 264 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,262 @@ func TestMatchPipelinerunAnnotationAndRepositories(t *testing.T) {
13461346
},
13471347
},
13481348
},
1349+
{
1350+
name: "cel/custom-params-simple",
1351+
args: annotationTestArgs{
1352+
pruns: []*tektonv1.PipelineRun{
1353+
{
1354+
ObjectMeta: metav1.ObjectMeta{
1355+
Name: pipelineTargetNSName,
1356+
Annotations: map[string]string{
1357+
keys.OnCelExpression: "test_enabled == \"true\"",
1358+
},
1359+
},
1360+
},
1361+
},
1362+
runevent: info.Event{
1363+
URL: targetURL,
1364+
TriggerTarget: "push",
1365+
BaseBranch: mainBranch,
1366+
EventType: "push",
1367+
},
1368+
data: testclient.Data{
1369+
Repositories: []*v1alpha1.Repository{
1370+
testnewrepo.NewRepo(
1371+
testnewrepo.RepoTestcreationOpts{
1372+
Name: "test-good",
1373+
URL: targetURL,
1374+
InstallNamespace: targetNamespace,
1375+
Params: &[]v1alpha1.Params{
1376+
{
1377+
Name: "test_enabled",
1378+
Value: "true",
1379+
},
1380+
},
1381+
},
1382+
),
1383+
},
1384+
},
1385+
},
1386+
wantPRName: pipelineTargetNSName,
1387+
wantErr: false,
1388+
},
1389+
{
1390+
name: "cel/custom-params-multiple",
1391+
args: annotationTestArgs{
1392+
pruns: []*tektonv1.PipelineRun{
1393+
{
1394+
ObjectMeta: metav1.ObjectMeta{
1395+
Name: pipelineTargetNSName,
1396+
Annotations: map[string]string{
1397+
keys.OnCelExpression: "environment == \"production\" && deploy_enabled == \"true\"",
1398+
},
1399+
},
1400+
},
1401+
},
1402+
runevent: info.Event{
1403+
URL: targetURL,
1404+
TriggerTarget: "push",
1405+
BaseBranch: mainBranch,
1406+
EventType: "push",
1407+
},
1408+
data: testclient.Data{
1409+
Repositories: []*v1alpha1.Repository{
1410+
testnewrepo.NewRepo(
1411+
testnewrepo.RepoTestcreationOpts{
1412+
Name: "test-good",
1413+
URL: targetURL,
1414+
InstallNamespace: targetNamespace,
1415+
Params: &[]v1alpha1.Params{
1416+
{
1417+
Name: "environment",
1418+
Value: "production",
1419+
},
1420+
{
1421+
Name: "deploy_enabled",
1422+
Value: "true",
1423+
},
1424+
},
1425+
},
1426+
),
1427+
},
1428+
},
1429+
},
1430+
wantPRName: pipelineTargetNSName,
1431+
wantErr: false,
1432+
},
1433+
{
1434+
name: "cel/custom-params-not-matching",
1435+
args: annotationTestArgs{
1436+
pruns: []*tektonv1.PipelineRun{
1437+
{
1438+
ObjectMeta: metav1.ObjectMeta{
1439+
Name: pipelineTargetNSName,
1440+
Annotations: map[string]string{
1441+
keys.OnCelExpression: "deploy_enabled == \"true\"",
1442+
},
1443+
},
1444+
},
1445+
},
1446+
runevent: info.Event{
1447+
URL: targetURL,
1448+
TriggerTarget: "push",
1449+
BaseBranch: mainBranch,
1450+
EventType: "push",
1451+
},
1452+
data: testclient.Data{
1453+
Repositories: []*v1alpha1.Repository{
1454+
testnewrepo.NewRepo(
1455+
testnewrepo.RepoTestcreationOpts{
1456+
Name: "test-good",
1457+
URL: targetURL,
1458+
InstallNamespace: targetNamespace,
1459+
Params: &[]v1alpha1.Params{
1460+
{
1461+
Name: "deploy_enabled",
1462+
Value: "false",
1463+
},
1464+
},
1465+
},
1466+
),
1467+
},
1468+
},
1469+
},
1470+
wantPRName: "",
1471+
wantErr: true,
1472+
},
1473+
{
1474+
name: "cel/custom-params-with-reserved-keyword",
1475+
args: annotationTestArgs{
1476+
pruns: []*tektonv1.PipelineRun{
1477+
{
1478+
ObjectMeta: metav1.ObjectMeta{
1479+
Name: pipelineTargetNSName,
1480+
Annotations: map[string]string{
1481+
keys.OnCelExpression: "event == \"push\"",
1482+
},
1483+
},
1484+
},
1485+
},
1486+
runevent: info.Event{
1487+
URL: targetURL,
1488+
TriggerTarget: "push",
1489+
BaseBranch: mainBranch,
1490+
EventType: "push",
1491+
},
1492+
data: testclient.Data{
1493+
Repositories: []*v1alpha1.Repository{
1494+
testnewrepo.NewRepo(
1495+
testnewrepo.RepoTestcreationOpts{
1496+
Name: "test-good",
1497+
URL: targetURL,
1498+
InstallNamespace: targetNamespace,
1499+
Params: &[]v1alpha1.Params{
1500+
{
1501+
Name: "event", // Reserved keyword - should be ignored
1502+
Value: "invalid",
1503+
},
1504+
},
1505+
},
1506+
),
1507+
},
1508+
},
1509+
},
1510+
wantPRName: pipelineTargetNSName,
1511+
wantErr: false,
1512+
},
1513+
{
1514+
name: "cel/custom-params-combined-with-builtin",
1515+
args: annotationTestArgs{
1516+
pruns: []*tektonv1.PipelineRun{
1517+
{
1518+
ObjectMeta: metav1.ObjectMeta{
1519+
Name: pipelineTargetNSName,
1520+
Annotations: map[string]string{
1521+
keys.OnCelExpression: "event == \"pull_request\" && target_branch == \"" + mainBranch + "\" && run_tests == \"true\"",
1522+
},
1523+
},
1524+
},
1525+
},
1526+
runevent: info.Event{
1527+
URL: targetURL,
1528+
TriggerTarget: "pull_request",
1529+
BaseBranch: mainBranch,
1530+
HeadBranch: "feature-branch",
1531+
EventType: "pull_request",
1532+
PullRequestNumber: 123,
1533+
},
1534+
data: testclient.Data{
1535+
Repositories: []*v1alpha1.Repository{
1536+
testnewrepo.NewRepo(
1537+
testnewrepo.RepoTestcreationOpts{
1538+
Name: "test-good",
1539+
URL: targetURL,
1540+
InstallNamespace: targetNamespace,
1541+
Params: &[]v1alpha1.Params{
1542+
{
1543+
Name: "run_tests",
1544+
Value: "true",
1545+
},
1546+
},
1547+
},
1548+
),
1549+
},
1550+
},
1551+
},
1552+
wantPRName: pipelineTargetNSName,
1553+
wantErr: false,
1554+
},
1555+
{
1556+
name: "cel/custom-params-template-vs-direct-access",
1557+
args: annotationTestArgs{
1558+
pruns: []*tektonv1.PipelineRun{
1559+
{
1560+
ObjectMeta: metav1.ObjectMeta{
1561+
Name: pipelineTargetNSName,
1562+
Annotations: map[string]string{
1563+
// Old way: template substitution - {{enable_ci}} gets replaced to "true" before CEL evaluation
1564+
keys.OnCelExpression: "event == \"push\" && \"{{enable_ci}}\" == \"true\"",
1565+
},
1566+
},
1567+
},
1568+
{
1569+
ObjectMeta: metav1.ObjectMeta{
1570+
Name: "pipeline-direct-access",
1571+
Annotations: map[string]string{
1572+
// New way: direct param access as CEL variable (no {{ }})
1573+
keys.OnCelExpression: "event == \"push\" && enable_ci == \"true\"",
1574+
},
1575+
},
1576+
},
1577+
},
1578+
runevent: info.Event{
1579+
URL: targetURL,
1580+
TriggerTarget: "push",
1581+
BaseBranch: mainBranch,
1582+
EventType: "push",
1583+
},
1584+
data: testclient.Data{
1585+
Repositories: []*v1alpha1.Repository{
1586+
testnewrepo.NewRepo(
1587+
testnewrepo.RepoTestcreationOpts{
1588+
Name: "test-good",
1589+
URL: targetURL,
1590+
InstallNamespace: targetNamespace,
1591+
Params: &[]v1alpha1.Params{
1592+
{
1593+
Name: "enable_ci",
1594+
Value: "true",
1595+
},
1596+
},
1597+
},
1598+
),
1599+
},
1600+
},
1601+
},
1602+
wantPRName: "pipeline-direct-access", // Both PRs match, but direct access is returned first
1603+
wantErr: false,
1604+
},
13491605
}
13501606

13511607
for _, tt := range tests {
@@ -1419,9 +1675,16 @@ func runTest(ctx context.Context, t *testing.T, tt annotationTest, vcx provider.
14191675
}
14201676

14211677
eventEmitter := events.NewEventEmitter(cs.Kube, logger)
1678+
1679+
// Get the repository for custom params resolution
1680+
var repo *v1alpha1.Repository
1681+
if len(tt.args.data.Repositories) > 0 {
1682+
repo = tt.args.data.Repositories[0]
1683+
}
1684+
14221685
matches, err := MatchPipelinerunByAnnotation(ctx, logger,
14231686
tt.args.pruns,
1424-
client, &tt.args.runevent, vcx, eventEmitter, nil,
1687+
client, &tt.args.runevent, vcx, eventEmitter, repo,
14251688
)
14261689

14271690
if tt.wantLog != "" {

0 commit comments

Comments
 (0)