Skip to content

Commit 1c7d303

Browse files
author
github-actions
committed
chore: regenerated
1 parent 1763304 commit 1c7d303

8 files changed

Lines changed: 255 additions & 41 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ require (
5757
github.com/go-kit/log v0.2.1 // indirect
5858
github.com/go-logfmt/logfmt v0.5.1 // indirect
5959
github.com/go-logr/logr v1.4.3 // indirect
60+
github.com/go-logr/zapr v1.3.0 // indirect
6061
github.com/go-openapi/jsonpointer v0.21.0 // indirect
6162
github.com/go-openapi/jsonreference v0.21.0 // indirect
6263
github.com/go-openapi/swag v0.23.0 // indirect

pkg/apis/lighthouse/v1alpha1/types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ const (
4040
ErrorState PipelineState = "error"
4141
)
4242

43+
// IsTerminalPipelineState reports whether state is a finished pipeline outcome: success, failure,
44+
// aborted, or error. Triggered, pending, running, and other values are non-terminal.
45+
func IsTerminalPipelineState(state PipelineState) bool {
46+
switch state {
47+
case SuccessState, FailureState, AbortedState, ErrorState:
48+
return true
49+
}
50+
return false
51+
}
52+
4353
// Environment variables to be added to the pipeline we kick off
4454
const (
4555
// BuildIDEnv is an optional unique build ID environment variable that can be used by an engine.

pkg/apis/lighthouse/v1alpha1/types_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,25 @@ func TestPipelineOptionsSpec_GetEnvVars(t *testing.T) {
139139
})
140140
}
141141
}
142+
143+
func TestIsTerminalPipelineState(t *testing.T) {
144+
tests := []struct {
145+
state v1alpha1.PipelineState
146+
want bool
147+
}{
148+
{v1alpha1.RunningState, false},
149+
{v1alpha1.PendingState, false},
150+
{v1alpha1.SuccessState, true},
151+
{v1alpha1.FailureState, true},
152+
{v1alpha1.AbortedState, true},
153+
{v1alpha1.ErrorState, true},
154+
{v1alpha1.PipelineState("unknown"), false},
155+
}
156+
for _, tt := range tests {
157+
t.Run(string(tt.state), func(t *testing.T) {
158+
if got := v1alpha1.IsTerminalPipelineState(tt.state); got != tt.want {
159+
t.Errorf("IsTerminalPipelineState(%q) = %v, want %v", tt.state, got, tt.want)
160+
}
161+
})
162+
}
163+
}

pkg/engines/tekton/activity.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,50 @@ func convertTektonStatus(cond *apis.Condition, start, finished *metav1.Time) v1a
159159
return v1alpha1.PendingState
160160
}
161161
}
162+
163+
func timePtrEqual(a, b *metav1.Time) bool {
164+
if a == nil && b == nil {
165+
return true
166+
}
167+
if a == nil || b == nil {
168+
return false
169+
}
170+
return a.Equal(b)
171+
}
172+
173+
// TerminalActivitySyncedWithPipelineRun returns true when the LighthouseJob's Activity already matches
174+
// the terminal PipelineRun at the level ConvertPipelineRun would set without fetching TaskRuns (top-level
175+
// record fields, stage count, optional report URL).
176+
func TerminalActivitySyncedWithPipelineRun(job *v1alpha1.LighthouseJob, pr *pipelinev1.PipelineRun, expectedReportURL string, dashboardConfigured bool) bool {
177+
if job == nil || pr == nil {
178+
return false
179+
}
180+
cond := pr.Status.GetCondition(apis.ConditionSucceeded)
181+
if cond == nil || (!cond.IsTrue() && !cond.IsFalse()) {
182+
return false
183+
}
184+
act := job.Status.Activity
185+
if act == nil {
186+
return false
187+
}
188+
expectedStatus := convertTektonStatus(cond, pr.Status.StartTime, pr.Status.CompletionTime)
189+
if act.Status != expectedStatus {
190+
return false
191+
}
192+
if act.Name != pr.Name {
193+
return false
194+
}
195+
if !timePtrEqual(act.StartTime, pr.Status.StartTime) {
196+
return false
197+
}
198+
if !timePtrEqual(act.CompletionTime, pr.Status.CompletionTime) {
199+
return false
200+
}
201+
if len(act.Stages) != len(pr.Status.ChildReferences) {
202+
return false
203+
}
204+
if dashboardConfigured && job.Status.ReportURL != expectedReportURL {
205+
return false
206+
}
207+
return true
208+
}

pkg/engines/tekton/activity_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
pipelinev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
1616
tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
1717
tektonfake "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/fake"
18+
corev1 "k8s.io/api/core/v1"
1819
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
"knative.dev/pkg/apis"
1921
"sigs.k8s.io/yaml"
2022
)
2123

@@ -194,6 +196,68 @@ func loadRecord(t *testing.T, dir string) *v1alpha1.ActivityRecord {
194196
return nil
195197
}
196198

199+
func TestTerminalActivitySyncedWithPipelineRun(t *testing.T) {
200+
now := metav1.Now()
201+
job := &v1alpha1.LighthouseJob{
202+
Status: v1alpha1.LighthouseJobStatus{
203+
ReportURL: "https://dash/pipeline",
204+
Activity: &v1alpha1.ActivityRecord{
205+
Name: "foo-pr",
206+
Status: v1alpha1.SuccessState,
207+
StartTime: &now,
208+
CompletionTime: &now,
209+
Stages: []*v1alpha1.ActivityStageOrStep{{Name: "build"}},
210+
},
211+
},
212+
}
213+
214+
t.Run("synced when pipeline run is terminal and activity matches", func(t *testing.T) {
215+
pr := &pipelinev1.PipelineRun{
216+
ObjectMeta: metav1.ObjectMeta{Name: "foo-pr"},
217+
}
218+
pr.Status.StartTime = &now
219+
pr.Status.CompletionTime = &now
220+
pr.Status.ChildReferences = []pipelinev1.ChildStatusReference{{Name: "tr-1", PipelineTaskName: "build"}}
221+
pr.Status.SetCondition(&apis.Condition{
222+
Type: apis.ConditionSucceeded,
223+
Status: corev1.ConditionTrue,
224+
})
225+
assert.True(t, tekton.TerminalActivitySyncedWithPipelineRun(job, pr, "https://dash/pipeline", true))
226+
assert.False(t, tekton.TerminalActivitySyncedWithPipelineRun(job, pr, "https://other", true))
227+
assert.True(t, tekton.TerminalActivitySyncedWithPipelineRun(job, pr, "", false))
228+
})
229+
230+
t.Run("false when pipeline run has no succeeded condition", func(t *testing.T) {
231+
pr := &pipelinev1.PipelineRun{ObjectMeta: metav1.ObjectMeta{Name: "foo-pr"}}
232+
assert.False(t, tekton.TerminalActivitySyncedWithPipelineRun(job, pr, "", false))
233+
})
234+
235+
t.Run("false when pipeline run is not terminal", func(t *testing.T) {
236+
pr := &pipelinev1.PipelineRun{ObjectMeta: metav1.ObjectMeta{Name: "foo-pr"}}
237+
pr.Status.SetCondition(&apis.Condition{
238+
Type: apis.ConditionSucceeded,
239+
Status: corev1.ConditionUnknown,
240+
})
241+
assert.False(t, tekton.TerminalActivitySyncedWithPipelineRun(job, pr, "", false))
242+
})
243+
244+
t.Run("false when status cleared", func(t *testing.T) {
245+
pr := &pipelinev1.PipelineRun{
246+
ObjectMeta: metav1.ObjectMeta{Name: "foo-pr"},
247+
}
248+
pr.Status.StartTime = &now
249+
pr.Status.CompletionTime = &now
250+
pr.Status.ChildReferences = []pipelinev1.ChildStatusReference{{Name: "tr-1", PipelineTaskName: "build"}}
251+
pr.Status.SetCondition(&apis.Condition{
252+
Type: apis.ConditionSucceeded,
253+
Status: corev1.ConditionTrue,
254+
})
255+
prIncomplete := pr.DeepCopy()
256+
prIncomplete.Status = pipelinev1.PipelineRunStatus{}
257+
assert.False(t, tekton.TerminalActivitySyncedWithPipelineRun(job, prIncomplete, "", false))
258+
})
259+
}
260+
197261
// assertFileExists asserts that the given file exists
198262
func assertFileExists(t *testing.T, fileName string) bool {
199263
exists, err := util.FileExists(fileName)

pkg/engines/tekton/controller.go

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import (
1717
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1818
"k8s.io/apimachinery/pkg/runtime"
1919
ctrl "sigs.k8s.io/controller-runtime"
20+
"sigs.k8s.io/controller-runtime/pkg/builder"
2021
"sigs.k8s.io/controller-runtime/pkg/client"
22+
"sigs.k8s.io/controller-runtime/pkg/controller"
2123
"sigs.k8s.io/controller-runtime/pkg/predicate"
2224
)
2325

@@ -27,33 +29,37 @@ var apiGVStr = lighthousev1alpha1.SchemeGroupVersion.String()
2729

2830
// LighthouseJobReconciler reconciles a LighthouseJob object
2931
type LighthouseJobReconciler struct {
30-
client client.Client
31-
tektonclient tektonversioned.Interface
32-
apiReader client.Reader
33-
logger *logrus.Entry
34-
scheme *runtime.Scheme
35-
idGenerator buildIDGenerator
36-
dashboardURL string
37-
dashboardTemplate string
38-
namespace string
39-
disableLogging bool
32+
client client.Client
33+
tektonclient tektonversioned.Interface
34+
apiReader client.Reader
35+
logger *logrus.Entry
36+
scheme *runtime.Scheme
37+
idGenerator buildIDGenerator
38+
dashboardURL string
39+
dashboardTemplate string
40+
namespace string
41+
disableLogging bool
42+
skipTerminatedReconciles bool
43+
maxConcurrentReconciles int
4044
}
4145

4246
// NewLighthouseJobReconciler creates a LighthouseJob reconciler
43-
func NewLighthouseJobReconciler(client client.Client, apiReader client.Reader, scheme *runtime.Scheme, tektonclient tektonversioned.Interface, dashboardURL string, dashboardTemplate string, namespace string) *LighthouseJobReconciler {
47+
func NewLighthouseJobReconciler(client client.Client, apiReader client.Reader, scheme *runtime.Scheme, tektonclient tektonversioned.Interface, dashboardURL string, dashboardTemplate string, namespace string, skipTerminatedReconciles bool, maxConcurrentReconciles int) *LighthouseJobReconciler {
4448
if dashboardTemplate == "" {
4549
dashboardTemplate = os.Getenv("LIGHTHOUSE_DASHBOARD_TEMPLATE")
4650
}
4751
return &LighthouseJobReconciler{
48-
client: client,
49-
apiReader: apiReader,
50-
logger: logrus.NewEntry(logrus.StandardLogger()).WithField("controller", controllerName),
51-
scheme: scheme,
52-
dashboardURL: dashboardURL,
53-
dashboardTemplate: dashboardTemplate,
54-
namespace: namespace,
55-
tektonclient: tektonclient,
56-
idGenerator: &epochBuildIDGenerator{},
52+
client: client,
53+
apiReader: apiReader,
54+
logger: logrus.NewEntry(logrus.StandardLogger()).WithField("controller", controllerName),
55+
scheme: scheme,
56+
dashboardURL: dashboardURL,
57+
dashboardTemplate: dashboardTemplate,
58+
namespace: namespace,
59+
tektonclient: tektonclient,
60+
idGenerator: &epochBuildIDGenerator{},
61+
skipTerminatedReconciles: skipTerminatedReconciles,
62+
maxConcurrentReconciles: maxConcurrentReconciles,
5763
}
5864
}
5965

@@ -74,11 +80,23 @@ func (r *LighthouseJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
7480
return err
7581
}
7682

77-
return ctrl.NewControllerManagedBy(mgr).
78-
For(&lighthousev1alpha1.LighthouseJob{}).
79-
WithEventFilter(predicate.ResourceVersionChangedPredicate{}).
80-
Owns(&pipelinev1.PipelineRun{}).
81-
Complete(r)
83+
ctrlr := ctrl.NewControllerManagedBy(mgr).
84+
For(&lighthousev1alpha1.LighthouseJob{}, builder.WithPredicates(
85+
lighthouseJobPredicateFactory(r.skipTerminatedReconciles),
86+
)).
87+
Owns(&pipelinev1.PipelineRun{}, builder.WithPredicates(
88+
predicate.ResourceVersionChangedPredicate{},
89+
))
90+
91+
if r.maxConcurrentReconciles > 1 {
92+
ctrlr = ctrlr.WithOptions(
93+
controller.Options{
94+
MaxConcurrentReconciles: r.maxConcurrentReconciles,
95+
},
96+
)
97+
}
98+
99+
return ctrlr.Complete(r)
82100
}
83101

84102
// Reconcile represents an iteration of the reconciliation loop
@@ -180,6 +198,19 @@ func (r *LighthouseJobReconciler) Reconcile(ctx context.Context, req ctrl.Reques
180198
}
181199
}
182200

201+
// When terminal shortcut is enabled, skip ConvertPipelineRun + status update if the PipelineRun is
202+
// done and Activity already matches (covers Owns(PipelineRun) events; same flag as For() predicate).
203+
if r.skipTerminatedReconciles {
204+
expectedReportURL := ""
205+
if r.dashboardURL != "" {
206+
expectedReportURL = r.getPipelingetPipelineTargetURLeTargetURL(pipelineRun)
207+
}
208+
if TerminalActivitySyncedWithPipelineRun(&job, &pipelineRun, expectedReportURL, r.dashboardURL != "") {
209+
r.logger.Debugf("Skipping terminal in-sync LighthouseJob %s", req.Name)
210+
return ctrl.Result{}, nil
211+
}
212+
}
213+
183214
f := func(job *lighthousev1alpha1.LighthouseJob) error {
184215
if r.dashboardURL != "" {
185216
job.Status.ReportURL = r.getPipelingetPipelineTargetURLeTargetURL(pipelineRun)
@@ -271,3 +302,25 @@ func (r *LighthouseJobReconciler) retryModifyJob(ctx context.Context, ns client.
271302
}
272303
}
273304
}
305+
306+
// lighthouseJobPredicateFactory returns the For(LighthouseJob) predicate set. When skipTerminatedReconciles
307+
// is false, only ResourceVersionChangedPredicate is used (legacy behavior). When true, it also skips
308+
// enqueue for LighthouseJobs whose activity is already in a terminal pipeline state (see Reconcile fast path).
309+
func lighthouseJobPredicateFactory(skipTerminatedReconciles bool) predicate.Predicate {
310+
if skipTerminatedReconciles {
311+
return predicate.And(
312+
predicate.ResourceVersionChangedPredicate{},
313+
predicate.NewPredicateFuncs(func(object client.Object) bool {
314+
job, ok := object.(*lighthousev1alpha1.LighthouseJob)
315+
if !ok {
316+
return true
317+
}
318+
if job.Status.Activity != nil {
319+
return !lighthousev1alpha1.IsTerminalPipelineState(job.Status.Activity.Status)
320+
}
321+
return true
322+
}),
323+
)
324+
}
325+
return predicate.ResourceVersionChangedPredicate{}
326+
}

pkg/engines/tekton/controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func TestReconcile(t *testing.T) {
9898

9999
c := fake.NewClientBuilder().WithStatusSubresource(observedJob).WithScheme(scheme).WithObjects(state...).Build()
100100
fake.AddIndex(c, &pipelinev1.PipelineRun{}, jobOwnerKey, tektonControllerIndexFunc)
101-
reconciler := NewLighthouseJobReconciler(c, c, scheme, tektonfakeClient, dashboardBaseURL, dashboardTemplate, ns)
101+
reconciler := NewLighthouseJobReconciler(c, c, scheme, tektonfakeClient, dashboardBaseURL, dashboardTemplate, ns, false, 1)
102102
reconciler.idGenerator = &seededRandIDGenerator{}
103103
reconciler.disableLogging = true
104104

0 commit comments

Comments
 (0)