Skip to content

Commit da40ee1

Browse files
committed
fix: Annotate PLR when when started status is reported
The use of the state label (which is mutable) for deciding when to report to the SCM that the PLR was started is flaky. It was seen that the reconciler get events about PLRs with unexpected value for the state label. For example, after the status is reported to the SCM, and the label value is patched to "started", after serval reconcile iterations the label had the "queued" value again. This can happen because of unsafe patching done by controllers (not just the PAC controllers) which reconciles PLRs. Introduce a new annotation for indicating the the status was reported to the SCM. By adding an annotation which is set once, we remove the risk that its value will get overwritten by other controllers (since maps are merged when patched, values are not getting removed unless explicitly defined in the patch - https://datatracker.ietf.org/doc/html/rfc7386#section-2) In addition, at the start of each reconcile loop, ensure that we operate on the latest version of the PLR and not using a stale value from the cache. Signed-off-by: Gal Ben Haim <[email protected]>
1 parent 78a7418 commit da40ee1

File tree

3 files changed

+65
-8
lines changed

3 files changed

+65
-8
lines changed

pkg/apis/pipelinesascode/keys/keys.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ const (
6161
LogURL = pipelinesascode.GroupName + "/log-url"
6262
ExecutionOrder = pipelinesascode.GroupName + "/execution-order"
6363
// PublicGithubAPIURL default is "https://api.github.com" but it can be overridden by X-GitHub-Enterprise-Host header.
64-
PublicGithubAPIURL = "https://api.github.com"
65-
GithubApplicationID = "github-application-id"
66-
GithubPrivateKey = "github-private-key"
67-
ResultsRecordSummary = "results.tekton.dev/recordSummaryAnnotations"
64+
PublicGithubAPIURL = "https://api.github.com"
65+
GithubApplicationID = "github-application-id"
66+
GithubPrivateKey = "github-private-key"
67+
ResultsRecordSummary = "results.tekton.dev/recordSummaryAnnotations"
68+
SCMReportingPLRStarted = pipelinesascode.GroupName + "/scm-reporting-plr-started"
6869
)
6970

7071
var ParamsRe = regexp.MustCompile(`{{([^}]{2,})}}`)

pkg/pipelineascode/pipelineascode.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ func (p *PacRun) startPR(ctx context.Context, match matcher.Match) (*tektonv1.Pi
257257
whatPatching = "annotations.state and labels.state"
258258
patchAnnotations[keys.State] = kubeinteraction.StateQueued
259259
patchLabels[keys.State] = kubeinteraction.StateQueued
260+
} else {
261+
patchAnnotations[keys.SCMReportingPLRStarted] = "true"
262+
whatPatching = fmt.Sprintf("annotation.%s", keys.SCMReportingPLRStarted)
260263
}
261264

262265
if err := p.vcx.CreateStatus(ctx, p.event, status); err != nil {
@@ -278,6 +281,20 @@ func (p *PacRun) startPR(ctx context.Context, match matcher.Match) (*tektonv1.Pi
278281
// unneeded SIGSEGV's
279282
return pr, fmt.Errorf("cannot patch pipelinerun %s: %w", pr.GetGenerateName(), err)
280283
}
284+
currentReason := ""
285+
if len(pr.Status.GetConditions()) > 0 {
286+
currentReason = pr.Status.GetConditions()[0].GetReason()
287+
}
288+
289+
p.logger.Infof("PipelineRun %s/%s patched successfully - Spec.Status: %s, State annotation: '%s', SCMReportingPLRStarted annotation: '%s', Status reason: '%s', Git provider status: '%s', Patched: %s",
290+
pr.GetNamespace(),
291+
pr.GetName(),
292+
pr.Spec.Status,
293+
pr.GetAnnotations()[keys.State],
294+
pr.GetAnnotations()[keys.SCMReportingPLRStarted],
295+
currentReason,
296+
status.Status,
297+
whatPatching)
281298
}
282299

283300
return pr, nil

pkg/reconciler/reconciler.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ var (
5454
func (r *Reconciler) ReconcileKind(ctx context.Context, pr *tektonv1.PipelineRun) pkgreconciler.Event {
5555
ctx = info.StoreNS(ctx, system.Namespace())
5656
logger := logging.FromContext(ctx).With("namespace", pr.GetNamespace())
57+
58+
logger.Infof("=== RECONCILE START: PipelineRun %s/%s (resourceVersion: %s) ===", pr.GetNamespace(), pr.GetName(), pr.GetResourceVersion())
59+
60+
// Get fresh PipelineRun from API server to avoid informer cache staleness
61+
freshPR, err := r.run.Clients.Tekton.TektonV1().PipelineRuns(pr.GetNamespace()).Get(ctx, pr.GetName(), metav1.GetOptions{})
62+
if err != nil {
63+
logger.Errorf("Failed to get fresh PipelineRun %s/%s: %v", pr.GetNamespace(), pr.GetName(), err)
64+
return fmt.Errorf("cannot get fresh PipelineRun: %w", err)
65+
}
66+
67+
if freshPR.GetResourceVersion() != pr.GetResourceVersion() {
68+
logger.Infof("Using fresh PipelineRun data (cached version %s vs fresh version %s)", pr.GetResourceVersion(), freshPR.GetResourceVersion())
69+
pr = freshPR
70+
} else {
71+
logger.Infof("Cached PipelineRun data is current (resourceVersion: %s)", pr.GetResourceVersion())
72+
}
73+
5774
// if pipelineRun is in completed or failed state then return
5875
state, exist := pr.GetAnnotations()[keys.State]
5976
if exist && (state == kubeinteraction.StateCompleted || state == kubeinteraction.StateFailed) {
@@ -69,13 +86,27 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, pr *tektonv1.PipelineRun
6986
// another controller outside PaC). To maintain consistency between the PipelineRun
7087
// status and the Git provider status, we update both the PipelineRun resource and
7188
// the corresponding status on the Git provider here.
72-
if reason == string(tektonv1.PipelineRunReasonRunning) && state == kubeinteraction.StateQueued {
89+
startReported := false
90+
scmReportingPLRStarted, exist := pr.GetAnnotations()[keys.SCMReportingPLRStarted]
91+
if exist && scmReportingPLRStarted == "true" {
92+
startReported = true
93+
} else if !exist {
94+
logger.Infof("RECONCILE: PipelineRun %s/%s has no %s annotation", pr.GetNamespace(), pr.GetName(), keys.SCMReportingPLRStarted)
95+
}
96+
97+
logger.Infof("RECONCILE: Checking condition - reason='%s' (is Running: %v), state='%s' (is queued: %v) (startReported: %v)",
98+
reason, reason == string(tektonv1.PipelineRunReasonRunning), state, state == kubeinteraction.StateQueued, startReported)
99+
100+
if reason == string(tektonv1.PipelineRunReasonRunning) && !startReported {
101+
logger.Infof("RECONCILE: PipelineRun %s/%s condition met (reason=Running, state=queued), calling updatePipelineRunToInProgress", pr.GetNamespace(), pr.GetName())
73102
repoName := pr.GetAnnotations()[keys.Repository]
74103
repo, err := r.repoLister.Repositories(pr.Namespace).Get(repoName)
75104
if err != nil {
76105
return fmt.Errorf("failed to get repository CR: %w", err)
77106
}
78107
return r.updatePipelineRunToInProgress(ctx, logger, repo, pr)
108+
} else {
109+
logger.Infof("RECONCILE: Condition NOT met for %s/%s - reason='%s', state='%s' (startReported: %v)", pr.GetNamespace(), pr.GetName(), reason, state, startReported)
79110
}
80111

81112
// if its a GitHub App pipelineRun PR then process only if check run id is added otherwise wait
@@ -343,16 +374,24 @@ func (r *Reconciler) updatePipelineRunToInProgress(ctx context.Context, logger *
343374
}
344375

345376
func (r *Reconciler) updatePipelineRunState(ctx context.Context, logger *zap.SugaredLogger, pr *tektonv1.PipelineRun, state string) (*tektonv1.PipelineRun, error) {
377+
currentState := pr.GetAnnotations()[keys.State]
378+
logger.Infof("updating pipelineRun %v/%v state from %s to %s", pr.GetNamespace(), pr.GetName(), currentState, state)
379+
annotations := map[string]string{
380+
keys.State: state,
381+
}
382+
if state == kubeinteraction.StateStarted {
383+
annotations[keys.SCMReportingPLRStarted] = "true"
384+
}
385+
346386
mergePatch := map[string]any{
347387
"metadata": map[string]any{
348388
"labels": map[string]string{
349389
keys.State: state,
350390
},
351-
"annotations": map[string]string{
352-
keys.State: state,
353-
},
391+
"annotations": annotations,
354392
},
355393
}
394+
356395
// if state is started then remove pipelineRun pending status
357396
if state == kubeinteraction.StateStarted {
358397
mergePatch["spec"] = map[string]any{

0 commit comments

Comments
 (0)