Skip to content

Commit 4969c51

Browse files
committed
fix: report cancelled status when PipelineRun is deleted
When a PipelineRun is deleted while it is in 'Running' or 'Queued' state (i.e., not yet completed), the status on the Git provider (e.g., GitHub) remained stuck in 'Pending' or 'Running'. This commit adds logic to the Finalizer to detect when a PipelineRun is being deleted (finalized) while in an active state. It now: 1. Reports a "cancelled" status to the Git provider, ensuring the commit status is correctly updated to "cancelled" instead of hanging. 2. Added unit test case to confirm that status is reported cancelled. This improves the user experience by accurately reflecting the PipelineRun lifecycle events on the Git provider UI. https://issues.redhat.com/browse/SRVKP-8318 Signed-off-by: Zaki Shaikh <[email protected]>
1 parent 610dd2c commit 4969c51

File tree

4 files changed

+166
-59
lines changed

4 files changed

+166
-59
lines changed

pkg/reconciler/finalizer.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package reconciler
22

33
import (
44
"context"
5+
"fmt"
56
"strings"
67

78
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
89
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
910
"github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction"
11+
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider"
1012
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
13+
"go.uber.org/zap"
1114
"k8s.io/apimachinery/pkg/api/errors"
1215
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1316
"knative.dev/pkg/logging"
@@ -59,6 +62,38 @@ func (r *Reconciler) FinalizeKind(ctx context.Context, pr *tektonv1.PipelineRun)
5962
}
6063
return nil
6164
}
65+
66+
// report the PipelineRun as cancelled as it was queued or started but it is deleted
67+
// but its status is still in progress or queued on git provider
68+
if err := r.reportPipelineRunAsCancelled(ctx, logger, repo, pr); err != nil {
69+
logger.Errorf("failed to report started pipeline run as cancelled: %w", err)
70+
return err
71+
}
6272
}
6373
return nil
6474
}
75+
76+
func (r *Reconciler) reportPipelineRunAsCancelled(ctx context.Context, logger *zap.SugaredLogger, repo *v1alpha1.Repository, pr *tektonv1.PipelineRun) error {
77+
detectedProvider, event, err := r.initGitProviderClient(ctx, logger, repo, pr)
78+
if err != nil {
79+
logger.Error(err)
80+
return nil
81+
}
82+
83+
consoleURL := r.run.Clients.ConsoleUI().DetailURL(pr)
84+
status := provider.StatusOpts{
85+
Conclusion: "cancelled",
86+
Text: fmt.Sprintf("PipelineRun %s was deleted", pr.GetName()),
87+
DetailsURL: consoleURL,
88+
PipelineRunName: pr.GetName(),
89+
PipelineRun: pr,
90+
OriginalPipelineRunName: pr.GetAnnotations()[keys.OriginalPRName],
91+
}
92+
93+
if err := createStatusWithRetry(ctx, logger, detectedProvider, event, status); err != nil {
94+
return fmt.Errorf("failed to report cancelled status to provider: %w", err)
95+
}
96+
97+
logger.Info("updated cancelled status on provider platform for pipelineRun ", pr.GetName())
98+
return nil
99+
}

pkg/reconciler/finalizer_test.go

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package reconciler
22

33
import (
4+
"strings"
45
"testing"
56

67
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
78
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
9+
"github.com/openshift-pipelines/pipelines-as-code/pkg/consoleui"
810
"github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction"
911
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
1012
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients"
1113
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
1214
"github.com/openshift-pipelines/pipelines-as-code/pkg/sync"
1315
testclient "github.com/openshift-pipelines/pipelines-as-code/pkg/test/clients"
16+
testkubernetestint "github.com/openshift-pipelines/pipelines-as-code/pkg/test/kubernetestint"
1417
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
1518
"go.uber.org/zap"
1619
zapobserver "go.uber.org/zap/zaptest/observer"
@@ -29,6 +32,11 @@ var (
2932
Spec: v1alpha1.RepositorySpec{
3033
URL: "https://github.com/sm43/pac-app",
3134
ConcurrencyLimit: &concurrency,
35+
GitProvider: &v1alpha1.GitProvider{
36+
Secret: &v1alpha1.Secret{
37+
Name: "pac-git-basic-auth-owner-repo",
38+
},
39+
},
3240
},
3341
}
3442
)
@@ -43,8 +51,12 @@ func getTestPR(name, state string) *tektonv1.PipelineRun {
4351
Name: name,
4452
Namespace: finalizeTestRepo.Namespace,
4553
Annotations: map[string]string{
46-
keys.State: state,
47-
keys.Repository: finalizeTestRepo.Name,
54+
keys.State: state,
55+
keys.Repository: finalizeTestRepo.Name,
56+
keys.GitProvider: "github",
57+
keys.SHA: "123afc",
58+
keys.URLOrg: "sm43",
59+
keys.URLRepository: "pac-app",
4860
},
4961
},
5062
Spec: tektonv1.PipelineRunSpec{
@@ -92,6 +104,15 @@ func TestReconciler_FinalizeKind(t *testing.T) {
92104
},
93105
skipAddingRepo: true,
94106
},
107+
{
108+
name: "cancelled status reported",
109+
pipelinerun: getTestPR("pr3", kubeinteraction.StateStarted),
110+
addToQueue: []*tektonv1.PipelineRun{
111+
getTestPR("pr1", kubeinteraction.StateStarted),
112+
getTestPR("pr2", kubeinteraction.StateQueued),
113+
getTestPR("pr3", kubeinteraction.StateQueued),
114+
},
115+
},
95116
}
96117

97118
for _, tt := range tests {
@@ -104,18 +125,29 @@ func TestReconciler_FinalizeKind(t *testing.T) {
104125
testData.Repositories = []*v1alpha1.Repository{}
105126
}
106127
stdata, informers := testclient.SeedTestData(t, ctx, testData)
128+
kinterfaceTest := &testkubernetestint.KinterfaceTest{
129+
GetSecretResult: map[string]string{
130+
"pac-git-basic-auth-owner-repo": "https://whateveryousayboss",
131+
},
132+
}
133+
134+
cs := &params.Run{
135+
Clients: clients.Clients{
136+
PipelineAsCode: stdata.PipelineAsCode,
137+
Log: fakelogger,
138+
},
139+
Info: info.Info{
140+
Kube: &info.KubeOpts{Namespace: "pac"},
141+
Controller: &info.ControllerInfo{GlobalRepository: "pac"},
142+
Pac: info.NewPacOpts(),
143+
},
144+
}
145+
cs.Clients.SetConsoleUI(consoleui.FallBackConsole{})
107146
r := Reconciler{
108147
repoLister: informers.Repository.Lister(),
109148
qm: sync.NewQueueManager(fakelogger),
110-
run: &params.Run{
111-
Clients: clients.Clients{
112-
PipelineAsCode: stdata.PipelineAsCode,
113-
},
114-
Info: info.Info{
115-
Kube: &info.KubeOpts{Namespace: "pac"},
116-
Controller: &info.ControllerInfo{GlobalRepository: "pac"},
117-
},
118-
},
149+
run: cs,
150+
kinteract: kinterfaceTest,
119151
}
120152

121153
if len(tt.addToQueue) != 0 {
@@ -125,7 +157,9 @@ func TestReconciler_FinalizeKind(t *testing.T) {
125157
}
126158
}
127159
err := r.FinalizeKind(ctx, tt.pipelinerun)
128-
assert.NilError(t, err)
160+
if err != nil && !strings.Contains(err.Error(), "401 Bad credentials []") {
161+
t.Fatalf("expected no error, got %v", err)
162+
}
129163

130164
// if repo was deleted then no queue will be there
131165
if tt.skipAddingRepo {

pkg/reconciler/reconciler.go

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -293,40 +293,10 @@ func (r *Reconciler) updatePipelineRunToInProgress(ctx context.Context, logger *
293293
if err != nil {
294294
return fmt.Errorf("cannot update state: %w", err)
295295
}
296-
pacInfo := r.run.Info.GetPacOpts()
297-
detectedProvider, event, err := r.detectProvider(ctx, logger, pr)
298-
if err != nil {
299-
logger.Error(err)
300-
return nil
301-
}
302-
detectedProvider.SetPacInfo(&pacInfo)
303-
304-
if event.InstallationID > 0 {
305-
event.Provider.WebhookSecret, _ = pac.GetCurrentNSWebhookSecret(ctx, r.kinteract, r.run)
306-
} else {
307-
// secretNS is needed when git provider is other than Github.
308-
secretNS := repo.GetNamespace()
309-
if repo.Spec.GitProvider != nil && repo.Spec.GitProvider.Secret == nil && r.globalRepo != nil && r.globalRepo.Spec.GitProvider != nil && r.globalRepo.Spec.GitProvider.Secret != nil {
310-
secretNS = r.globalRepo.GetNamespace()
311-
}
312-
313-
secretFromRepo := pac.SecretFromRepository{
314-
K8int: r.kinteract,
315-
Config: detectedProvider.GetConfig(),
316-
Event: event,
317-
Repo: repo,
318-
WebhookType: pacInfo.WebhookType,
319-
Logger: logger,
320-
Namespace: secretNS,
321-
}
322-
if err := secretFromRepo.Get(ctx); err != nil {
323-
return fmt.Errorf("cannot get secret from repository: %w", err)
324-
}
325-
}
326296

327-
err = detectedProvider.SetClient(ctx, r.run, event, repo, r.eventEmitter)
297+
detectedProvider, event, err := r.initGitProviderClient(ctx, logger, repo, pr)
328298
if err != nil {
329-
return fmt.Errorf("cannot set client: %w", err)
299+
return fmt.Errorf("cannot initialize git provider client: %w", err)
330300
}
331301

332302
consoleURL := r.run.Clients.ConsoleUI().DetailURL(pr)
@@ -364,6 +334,45 @@ func (r *Reconciler) updatePipelineRunToInProgress(ctx context.Context, logger *
364334
return nil
365335
}
366336

337+
func (r *Reconciler) initGitProviderClient(ctx context.Context, logger *zap.SugaredLogger, repo *v1alpha1.Repository, pr *tektonv1.PipelineRun) (provider.Interface, *info.Event, error) {
338+
pacInfo := r.run.Info.GetPacOpts()
339+
detectedProvider, event, err := r.detectProvider(ctx, logger, pr)
340+
if err != nil {
341+
return nil, nil, fmt.Errorf("cannot detect provider: %w", err)
342+
}
343+
detectedProvider.SetPacInfo(&pacInfo)
344+
345+
if event.InstallationID > 0 {
346+
event.Provider.WebhookSecret, _ = pac.GetCurrentNSWebhookSecret(ctx, r.kinteract, r.run)
347+
} else {
348+
// secretNS is needed when git provider is other than Github.
349+
secretNS := repo.GetNamespace()
350+
if repo.Spec.GitProvider != nil && repo.Spec.GitProvider.Secret == nil && r.globalRepo != nil && r.globalRepo.Spec.GitProvider != nil && r.globalRepo.Spec.GitProvider.Secret != nil {
351+
secretNS = r.globalRepo.GetNamespace()
352+
}
353+
354+
secretFromRepo := pac.SecretFromRepository{
355+
K8int: r.kinteract,
356+
Config: detectedProvider.GetConfig(),
357+
Event: event,
358+
Repo: repo,
359+
WebhookType: pacInfo.WebhookType,
360+
Logger: logger,
361+
Namespace: secretNS,
362+
}
363+
if err := secretFromRepo.Get(ctx); err != nil {
364+
return nil, nil, fmt.Errorf("cannot get secret from repository: %w", err)
365+
}
366+
}
367+
368+
err = detectedProvider.SetClient(ctx, r.run, event, repo, r.eventEmitter)
369+
if err != nil {
370+
return nil, nil, fmt.Errorf("cannot set client: %w", err)
371+
}
372+
373+
return detectedProvider, event, nil
374+
}
375+
367376
func (r *Reconciler) updatePipelineRunState(ctx context.Context, logger *zap.SugaredLogger, pr *tektonv1.PipelineRun, state string) (*tektonv1.PipelineRun, error) {
368377
currentState := pr.GetAnnotations()[keys.State]
369378
logger.Infof("updating pipelineRun %v/%v state from %s to %s", pr.GetNamespace(), pr.GetName(), currentState, state)

pkg/reconciler/reconciler_test.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/openshift-pipelines/pipelines-as-code/pkg/sync"
2525
testclient "github.com/openshift-pipelines/pipelines-as-code/pkg/test/clients"
2626
ghtesthelper "github.com/openshift-pipelines/pipelines-as-code/pkg/test/github"
27+
testkubernetestint "github.com/openshift-pipelines/pipelines-as-code/pkg/test/kubernetestint"
2728
tektontest "github.com/openshift-pipelines/pipelines-as-code/pkg/test/tekton"
2829
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
2930
"go.uber.org/zap"
@@ -315,7 +316,7 @@ func TestUpdatePipelineRunState(t *testing.T) {
315316

316317
func TestReconcileKind_SCMReportingLogic(t *testing.T) {
317318
observer, _ := zapobserver.New(zap.InfoLevel)
318-
_ = zap.New(observer).Sugar()
319+
logger := zap.New(observer).Sugar()
319320

320321
tests := []struct {
321322
name string
@@ -330,8 +331,12 @@ func TestReconcileKind_SCMReportingLogic(t *testing.T) {
330331
Namespace: "test",
331332
Name: "test-pr",
332333
Annotations: map[string]string{
333-
keys.State: kubeinteraction.StateQueued,
334-
keys.Repository: "test-repo",
334+
keys.State: kubeinteraction.StateQueued,
335+
keys.Repository: "test-repo",
336+
keys.GitProvider: "github",
337+
keys.SHA: "123afc",
338+
keys.URLOrg: "random",
339+
keys.URLRepository: "app",
335340
},
336341
},
337342
Spec: tektonv1.PipelineRunSpec{},
@@ -360,6 +365,10 @@ func TestReconcileKind_SCMReportingLogic(t *testing.T) {
360365
keys.State: kubeinteraction.StateStarted,
361366
keys.Repository: "test-repo",
362367
keys.SCMReportingPLRStarted: "true",
368+
keys.GitProvider: "github",
369+
keys.SHA: "123afc",
370+
keys.URLOrg: "random",
371+
keys.URLRepository: "app",
363372
},
364373
},
365374
Spec: tektonv1.PipelineRunSpec{},
@@ -385,8 +394,12 @@ func TestReconcileKind_SCMReportingLogic(t *testing.T) {
385394
Namespace: "test",
386395
Name: "test-pr",
387396
Annotations: map[string]string{
388-
keys.State: kubeinteraction.StateQueued,
389-
keys.Repository: "test-repo",
397+
keys.State: kubeinteraction.StateQueued,
398+
keys.Repository: "test-repo",
399+
keys.GitProvider: "github",
400+
keys.SHA: "123afc",
401+
keys.URLOrg: "random",
402+
keys.URLRepository: "app",
390403
},
391404
},
392405
Spec: tektonv1.PipelineRunSpec{
@@ -420,6 +433,11 @@ func TestReconcileKind_SCMReportingLogic(t *testing.T) {
420433
},
421434
Spec: v1alpha1.RepositorySpec{
422435
URL: randomURL,
436+
GitProvider: &v1alpha1.GitProvider{
437+
Secret: &v1alpha1.Secret{
438+
Name: "pac-git-basic-auth-owner-repo",
439+
},
440+
},
423441
},
424442
}
425443

@@ -432,19 +450,30 @@ func TestReconcileKind_SCMReportingLogic(t *testing.T) {
432450
// Track if updatePipelineRunToInProgress was called by checking state changes
433451
originalState := tt.pipelineRun.GetAnnotations()[keys.State]
434452

435-
r := &Reconciler{
436-
repoLister: informers.Repository.Lister(),
437-
run: &params.Run{
438-
Clients: clients.Clients{
439-
Tekton: stdata.Pipeline,
440-
},
441-
Info: info.Info{
442-
Pac: &info.PacOpts{
443-
Settings: settings.Settings{},
444-
},
453+
kinterfaceTest := &testkubernetestint.KinterfaceTest{
454+
GetSecretResult: map[string]string{
455+
"pac-git-basic-auth-owner-repo": "https://whateveryousayboss",
456+
},
457+
}
458+
459+
cs := &params.Run{
460+
Clients: clients.Clients{
461+
Tekton: stdata.Pipeline,
462+
Log: logger,
463+
},
464+
Info: info.Info{
465+
Pac: &info.PacOpts{
466+
Settings: settings.Settings{},
445467
},
446468
},
447469
}
470+
cs.Clients.SetConsoleUI(consoleui.FallBackConsole{})
471+
472+
r := &Reconciler{
473+
repoLister: informers.Repository.Lister(),
474+
run: cs,
475+
kinteract: kinterfaceTest,
476+
}
448477

449478
err := r.ReconcileKind(ctx, tt.pipelineRun)
450479

0 commit comments

Comments
 (0)