Skip to content

feat: support PipelineRun running status reporting with Kueue #2120

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

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
8 changes: 8 additions & 0 deletions docs/content/docs/guide/repositorycrd.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ of the pipelineruns will be executed in alphabetical order, one after the
other. At any given time, only one pipeline run will be in the running state,
while the rest will be queued.

### Kueue - Kubernetes-native Job Queueing

Pipelines-as-Code now accommodates [Kueue](https://kueue.sigs.k8s.io/) as an alternative, Kubernetes-native solution for queuing PipelineRun.
To get started, you can deploy the experimental integration provided by the [konflux-ci/tekton-kueue](https://github.com/konflux-ci/tekton-kueue) project. This allows you to schedule PipelineRuns through Kueue's queuing mechanism.

Note: The [konflux-ci/tekton-kueue](https://github.com/konflux-ci/tekton-kueue) project and the Pipelines-as-Code integration is only intended for testing
It is only meant for experimentation and should not be used in production environments.

## Scoping GitHub token to a list of private and public repositories within and outside namespaces

By default, the GitHub token that Pipelines-as-Code generates is scoped only to the repository where the payload comes from.
Expand Down
26 changes: 20 additions & 6 deletions pkg/pipelineascode/pipelineascode.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction"
"github.com/openshift-pipelines/pipelines-as-code/pkg/matcher"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/settings"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
Expand Down Expand Up @@ -241,12 +240,23 @@ func (p *PacRun) startPR(ctx context.Context, match matcher.Match) (*tektonv1.Pi
OriginalPipelineRunName: pr.GetAnnotations()[keys.OriginalPRName],
}

patchAnnotations := map[string]string{}
patchLabels := map[string]string{}
whatPatching := ""
// if pipelineRun is in pending state then report status as queued
if pr.Spec.Status == tektonv1.PipelineRunSpecStatusPending {
status.Status = queuedStatus
if status.Text, err = mt.MakeTemplate(p.vcx.GetTemplate(provider.QueueingPipelineType)); err != nil {
return nil, fmt.Errorf("cannot create message template: %w", err)
}
// If the PipelineRun is in the "queued" state, add the appropriate label and annotation.
// These are later used by the watcher to determine whether the PipelineRun status
// should be reported back to the Git provider. We do add the `state` annotations and label when
// concurrency is enabled but this would happen when PipelineRun's status has been changed by
// the other controller and PaC is not aware of that change.
whatPatching = "annotations.state and labels.state"
patchAnnotations[keys.State] = kubeinteraction.StateQueued
patchLabels[keys.State] = kubeinteraction.StateQueued
}

if err := p.vcx.CreateStatus(ctx, p.event, status); err != nil {
Expand All @@ -257,7 +267,12 @@ func (p *PacRun) startPR(ctx context.Context, match matcher.Match) (*tektonv1.Pi

// Patch pipelineRun with logURL annotation, skips for GitHub App as we patch logURL while patching CheckrunID
if _, ok := pr.Annotations[keys.InstallationID]; !ok {
pr, err = action.PatchPipelineRun(ctx, p.logger, "logURL", p.run.Clients.Tekton, pr, getLogURLMergePatch(p.run.Clients, pr))
patchAnnotations[keys.LogURL] = p.run.Clients.ConsoleUI().DetailURL(pr)
whatPatching = "annotations.logURL, " + whatPatching
}

if len(patchAnnotations) > 0 || len(patchLabels) > 0 {
pr, err = action.PatchPipelineRun(ctx, p.logger, whatPatching, p.run.Clients.Tekton, pr, getMergePatch(patchAnnotations, patchLabels))
if err != nil {
// we still return the created PR with error, and allow caller to decide what to do with the PR, and avoid
// unneeded SIGSEGV's
Expand All @@ -268,12 +283,11 @@ func (p *PacRun) startPR(ctx context.Context, match matcher.Match) (*tektonv1.Pi
return pr, nil
}

func getLogURLMergePatch(clients clients.Clients, pr *tektonv1.PipelineRun) map[string]any {
func getMergePatch(annotations, labels map[string]string) map[string]any {
return map[string]any{
"metadata": map[string]any{
"annotations": map[string]string{
keys.LogURL: clients.ConsoleUI().DetailURL(pr),
},
"annotations": annotations,
"labels": labels,
},
}
}
Expand Down
59 changes: 49 additions & 10 deletions pkg/pipelineascode/pipelineascode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,49 @@ func TestRun(t *testing.T) {
finalStatusText: "User fantasio is not allowed to trigger CI by GitOps comment on push commit in this repo.",
skipReplyingOrgPublicMembers: true,
},
{
name: "pull request/pipelinerun created in pending state (state changed by other controller)",
runevent: info.Event{
Event: &github.PullRequestEvent{
PullRequest: &github.PullRequest{
Number: github.Ptr(666),
},
},
SHA: "fromwebhook",
Organization: "owner",
Sender: "owner",
Repository: "repo",
URL: "https://service/documentation",
HeadBranch: "press",
BaseBranch: "main",
EventType: "pull_request",
TriggerTarget: "pull_request",
PullRequestNumber: 666,
InstallationID: 1234,
},
tektondir: "testdata/pending_pipelinerun",
},
{
name: "pull request/pipelinerun created in pending state without installationID (state changed by other controller)",
runevent: info.Event{
Event: &github.PullRequestEvent{
PullRequest: &github.PullRequest{
Number: github.Ptr(666),
},
},
SHA: "fromwebhook",
Organization: "owner",
Sender: "owner",
Repository: "repo",
URL: "https://service/documentation",
HeadBranch: "press",
BaseBranch: "main",
EventType: "pull_request",
TriggerTarget: "pull_request",
PullRequestNumber: 666,
},
tektondir: "testdata/pending_pipelinerun",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -675,10 +718,10 @@ func TestRun(t *testing.T) {
secretName, ok := pr.GetAnnotations()[keys.GitAuthSecret]
assert.Assert(t, ok, "Cannot find secret %s on annotations", secretName)
}
if tt.concurrencyLimit > 0 {
concurrencyLimit, ok := pr.GetAnnotations()[keys.State]
assert.Assert(t, ok, "State hasn't been set on PR", concurrencyLimit)
assert.Equal(t, concurrencyLimit, kubeinteraction.StateQueued)
if tt.concurrencyLimit > 0 || pr.Spec.Status == pipelinev1.PipelineRunSpecStatusPending {
state, ok := pr.GetAnnotations()[keys.State]
assert.Assert(t, ok, "State hasn't been set on PR", state)
assert.Equal(t, state, kubeinteraction.StateQueued)
}
}
}
Expand All @@ -690,12 +733,8 @@ func TestGetLogURLMergePatch(t *testing.T) {
con := consoleui.FallBackConsole{}
clients := clients.Clients{}
clients.SetConsoleUI(con)
pr := &pipelinev1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pipeline-run",
},
}
result := getLogURLMergePatch(clients, pr)
ann := map[string]string{keys.LogURL: con.URL()}
result := getMergePatch(ann, map[string]string{})
m, ok := result["metadata"].(map[string]any)
assert.Assert(t, ok)
a, ok := m["annotations"].(map[string]string)
Expand Down
17 changes: 17 additions & 0 deletions pkg/pipelineascode/testdata/pending_pipelinerun/.tekton/run.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: pull_request-test1
annotations:
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/on-event: "[pull_request]"
spec:
status: PipelineRunPending
pipelineSpec:
tasks:
- name: max
taskSpec:
steps:
- name: success
image: registry.access.redhat.com/ubi9/ubi-minimal
script: 'exit 0'
18 changes: 18 additions & 0 deletions pkg/reconciler/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, pr *tektonv1.PipelineRun
return nil
}

reason := ""
if len(pr.Status.GetConditions()) > 0 {
reason = pr.Status.GetConditions()[0].GetReason()
}
// This condition handles cases where the PipelineRun has entered a "Running" state,
// but its status in the Git provider remains "queued" (e.g., due to updates made by
// another controller outside PaC). To maintain consistency between the PipelineRun
// status and the Git provider status, we update both the PipelineRun resource and
// the corresponding status on the Git provider here.
if reason == string(tektonv1.PipelineRunReasonRunning) && state == kubeinteraction.StateQueued {
repoName := pr.GetAnnotations()[keys.Repository]
repo, err := r.repoLister.Repositories(pr.Namespace).Get(repoName)
if err != nil {
return fmt.Errorf("failed to get repository CR: %w", err)
}
return r.updatePipelineRunToInProgress(ctx, logger, repo, pr)
}

// if its a GitHub App pipelineRun PR then process only if check run id is added otherwise wait
if _, ok := pr.Annotations[keys.InstallationID]; ok {
if _, ok := pr.Annotations[keys.CheckRunID]; !ok {
Expand Down
Loading