Skip to content

Commit 8be80a2

Browse files
committed
Add new component build model with per-version lifecycle management
Implement new component model that manages build configurations and pipelines at the version level. Each version can be independently onboarded, triggered, and offboarded through component spec actions. Key features: - Per-version PaC configuration and pipeline management - Action-based workflow (create configuration, trigger build, offboard) - Version status tracking with error reporting - Cache synchronization to prevent race conditions between reconciles - Support for invalid version cleanup from actions The new model is enabled via component annotation or default setting. STONEBLD-4018 Signed-off-by: Robert Cerven <rcerven@redhat.com>
1 parent 1aae2f2 commit 8be80a2

24 files changed

+8363
-714
lines changed

cmd/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import (
6161
l "github.com/konflux-ci/build-service/pkg/logs"
6262
pacwebhook "github.com/konflux-ci/build-service/pkg/pacwebhook"
6363

64-
appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1"
64+
compapiv1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1"
6565
imagerepositoryapi "github.com/konflux-ci/image-controller/api/v1alpha1"
6666
releaseapi "github.com/konflux-ci/release-service/api/v1alpha1"
6767
pacv1alpha1 "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
@@ -78,7 +78,7 @@ var (
7878
func init() {
7979
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
8080

81-
utilruntime.Must(appstudiov1alpha1.AddToScheme(scheme))
81+
utilruntime.Must(compapiv1alpha1.AddToScheme(scheme))
8282
// +kubebuilder:scaffold:scheme
8383
}
8484

config/rbac/role.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ rules:
3232
- appstudio.redhat.com
3333
resources:
3434
- components
35+
- components/status
3536
verbs:
3637
- get
3738
- list
@@ -41,7 +42,6 @@ rules:
4142
- apiGroups:
4243
- appstudio.redhat.com
4344
resources:
44-
- components/status
4545
- imagerepositories
4646
- imagerepositories/status
4747
- releaseplanadmissions

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ require (
3333

3434
// If you update dependencies below you must also update internal/controller/suite_test.go
3535
require (
36-
github.com/konflux-ci/application-api v0.0.0-20240812090716-e7eb2ecfb409
36+
github.com/konflux-ci/application-api v0.0.0-20260205151641-c691ffebedf8
3737
github.com/konflux-ci/image-controller v0.0.0-20250424143112-69ec692d353c
3838
github.com/konflux-ci/release-service v0.0.0-20240610124538-758a1d48d002
3939
github.com/openshift-pipelines/pipelines-as-code v0.37.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
309309
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
310310
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
311311
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
312-
github.com/konflux-ci/application-api v0.0.0-20240812090716-e7eb2ecfb409 h1:hAhhcfc/EXW9eKS827PmyYuhk+Y4KkVOsT53Uo9OzCU=
313-
github.com/konflux-ci/application-api v0.0.0-20240812090716-e7eb2ecfb409/go.mod h1:948Z+a1IbfRT0RtoHzWWSN9YEucSbMJTHaMhz7dVICc=
312+
github.com/konflux-ci/application-api v0.0.0-20260205151641-c691ffebedf8 h1:HjVmLXbIzsqOAU1gN+qhJdUIIaDiUtd2nc9YvLg8iHo=
313+
github.com/konflux-ci/application-api v0.0.0-20260205151641-c691ffebedf8/go.mod h1:948Z+a1IbfRT0RtoHzWWSN9YEucSbMJTHaMhz7dVICc=
314314
github.com/konflux-ci/coverport/instrumentation/go v0.0.0-20251127115143-b5207b335f8b h1:NoriO1KRc+7d2/JA07JizqxP0LlA2oJdD5AuQOvEIjE=
315315
github.com/konflux-ci/coverport/instrumentation/go v0.0.0-20251127115143-b5207b335f8b/go.mod h1:WVMHU9A2464s/vjH1xOTm4LJDD4xP+VlEiU+KM0gkSU=
316316
github.com/konflux-ci/image-controller v0.0.0-20250424143112-69ec692d353c h1:+LtI4WT2KDDpCbVki47dLIu03NH6+Qj0d/MKNu3MZYk=

internal/controller/component_build_controller.go

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import (
3535
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
3636
"sigs.k8s.io/controller-runtime/pkg/predicate"
3737

38-
appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1"
38+
compapiv1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1"
3939

4040
"github.com/konflux-ci/build-service/pkg/boerrors"
4141
"github.com/konflux-ci/build-service/pkg/bometrics"
@@ -63,12 +63,18 @@ const (
6363

6464
gitCommitShaAnnotationName = "build.appstudio.redhat.com/commit_sha"
6565
gitRepoAtShaAnnotationName = "build.appstudio.openshift.io/repo"
66+
ContextAnnotationName = "appstudio.openshift.io/context"
67+
VersionAnnotationName = "appstudio.openshift.io/version"
6668

6769
defaultBuildPipelineAnnotation = "build.appstudio.openshift.io/pipeline"
6870
buildPipelineConfigMapResourceName = "build-pipeline-config"
6971
buildPipelineConfigName = "config.yaml"
7072

7173
waitForContainerImageMessage = "waiting for spec.containerImage to be set by ImageRepository with annotation image-controller.appstudio.redhat.com/update-component-image"
74+
75+
ComponentModelVersionAnnotation = "build.appstudio.openshift.io/component_model_version"
76+
DefaultComponentModelVersion = "v1"
77+
NewComponentModelVersion = "v2"
7278
)
7379

7480
type BuildStatus struct {
@@ -109,11 +115,22 @@ type ComponentBuildReconciler struct {
109115
// SetupWithManager sets up the controller with the Manager.
110116
func (r *ComponentBuildReconciler) SetupWithManager(mgr ctrl.Manager) error {
111117
return ctrl.NewControllerManagedBy(mgr).
112-
For(&appstudiov1alpha1.Component{}, builder.WithPredicates(predicate.Funcs{
118+
For(&compapiv1alpha1.Component{}, builder.WithPredicates(predicate.Funcs{
113119
CreateFunc: func(e event.CreateEvent) bool {
114120
return true
115121
},
116122
UpdateFunc: func(e event.UpdateEvent) bool {
123+
component, ok := e.ObjectNew.(*compapiv1alpha1.Component)
124+
if !ok {
125+
return false
126+
}
127+
128+
// TODO remove newModel handling after only new model is used
129+
// For new model: only reconcile when spec changes (Generation increments), not on status-only updates
130+
// For old model: always reconcile on update (old model uses annotations which don't increment Generation)
131+
if isComponentUsingNewModel(component) {
132+
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
133+
}
117134
return true
118135
},
119136
DeleteFunc: func(e event.DeleteEvent) bool {
@@ -127,6 +144,13 @@ func (r *ComponentBuildReconciler) SetupWithManager(mgr ctrl.Manager) error {
127144
Complete(r)
128145
}
129146

147+
// isComponentUsingNewModel checks if the component is configured to use the new model (v2).
148+
// Returns true if the component has the new model annotation set to v2 or if the default model is v2.
149+
func isComponentUsingNewModel(component *compapiv1alpha1.Component) bool {
150+
requestedModel, requestedModelAnnotationExists := component.Annotations[ComponentModelVersionAnnotation]
151+
return (requestedModelAnnotationExists && requestedModel == NewComponentModelVersion) || DefaultComponentModelVersion == NewComponentModelVersion
152+
}
153+
130154
func updateMetricsTimes(componentIdForMetrics string, requestedAction string, reconcileStartTime time.Time) {
131155
componentInfo, timeRecorded := bometrics.ComponentTimesForMetrics[componentIdForMetrics]
132156

@@ -159,12 +183,12 @@ func updateMetricsTimes(componentIdForMetrics string, requestedAction string, re
159183
//+kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch
160184

161185
func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
162-
log := ctrllog.FromContext(ctx).WithName("ComponentOnboarding")
186+
log := ctrllog.FromContext(ctx)
163187
ctx = ctrllog.IntoContext(ctx, log)
164188
reconcileStartTime := time.Now()
165189

166190
// Fetch the Component instance
167-
var component appstudiov1alpha1.Component
191+
var component compapiv1alpha1.Component
168192
err := r.Client.Get(ctx, req.NamespacedName, &component)
169193
if err != nil {
170194
if errors.IsNotFound(err) {
@@ -177,11 +201,17 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
177201
return ctrl.Result{}, err
178202
}
179203

204+
if isComponentUsingNewModel(&component) {
205+
return r.ReconcileNewModel(ctx, req)
206+
}
207+
208+
log = ctrllog.FromContext(ctx).WithName("ComponentOnboarding")
209+
180210
// Don't recreate build pipeline Service Account upon component deletion.
181211
if component.ObjectMeta.DeletionTimestamp.IsZero() {
182212
// We need to make sure the Service Account exists before checking the Component image,
183213
// because Image Controller operator expects the Service Account to exist to link push secret to it.
184-
if err := r.EnsureBuildPipelineServiceAccount(ctx, &component); err != nil {
214+
if err := r.EnsureBuildPipelineServiceAccount(ctx, &component, true); err != nil {
185215
return ctrl.Result{}, err
186216
}
187217
}
@@ -202,7 +232,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
202232
log.Error(err, "failed to update component after waiting for containerImage", l.Action, l.ActionUpdate, l.Audit, "true")
203233
return ctrl.Result{}, err
204234
}
205-
r.WaitForCacheUpdate(ctx, req.NamespacedName, &component)
235+
r.WaitForCacheUpdateOldModel(ctx, req.NamespacedName, &component)
206236

207237
return ctrl.Result{}, nil
208238
}
@@ -256,7 +286,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
256286
log.Info("PaC finalizer removed", l.Action, l.ActionDelete)
257287

258288
// Try to clean up Pipelines as Code configuration
259-
_, _ = r.UndoPaCProvisionForComponent(ctx, &component)
289+
_, _ = r.UndoPaCProvisionForComponentOldModel(ctx, &component)
260290
}
261291

262292
// Clean up common build pipelines Role Binding
@@ -305,7 +335,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
305335
return ctrl.Result{}, err
306336
}
307337
log.Info(fmt.Sprintf("updated component after wrong %s annotation", defaultBuildPipelineAnnotation))
308-
r.WaitForCacheUpdate(ctx, req.NamespacedName, &component)
338+
r.WaitForCacheUpdateOldModel(ctx, req.NamespacedName, &component)
309339

310340
return ctrl.Result{}, nil
311341
}
@@ -338,7 +368,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
338368
return ctrl.Result{}, nil
339369
}
340370

341-
reconcileRequired, err := r.TriggerPaCBuild(ctx, &component)
371+
reconcileRequired, err := r.TriggerPaCBuildOldModel(ctx, &component)
342372

343373
if err != nil {
344374
if boErr, ok := err.(*boerrors.BuildOpError); ok && boErr.IsPersistent() {
@@ -368,7 +398,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
368398
}()
369399

370400
pacBuildStatus := &PaCBuildStatus{}
371-
if mergeUrl, err := r.ProvisionPaCForComponent(ctx, &component); err != nil {
401+
if mergeUrl, err := r.ProvisionPaCForComponentOldModel(ctx, &component); err != nil {
372402
if boErr, ok := err.(*boerrors.BuildOpError); ok && boErr.IsPersistent() {
373403
log.Error(err, "Pipelines as Code provision for the Component failed")
374404
pacBuildStatus.State = "error"
@@ -436,7 +466,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
436466
}
437467

438468
pacBuildStatus := &PaCBuildStatus{}
439-
if mergeUrl, err := r.UndoPaCProvisionForComponent(ctx, &component); err != nil {
469+
if mergeUrl, err := r.UndoPaCProvisionForComponentOldModel(ctx, &component); err != nil {
440470
if boErr, ok := err.(*boerrors.BuildOpError); ok && boErr.IsPersistent() {
441471
log.Error(err, "Pipelines as Code unprovision for the Component failed")
442472
pacBuildStatus.State = "error"
@@ -491,12 +521,13 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
491521
// remove component from metrics map
492522
delete(bometrics.ComponentTimesForMetrics, componentIdForMetrics)
493523

494-
r.WaitForCacheUpdate(ctx, req.NamespacedName, &component)
524+
r.WaitForCacheUpdateOldModel(ctx, req.NamespacedName, &component)
495525

496526
return ctrl.Result{}, nil
497527
}
498528

499-
func (r *ComponentBuildReconciler) WaitForCacheUpdate(ctx context.Context, namespace types.NamespacedName, component *appstudiov1alpha1.Component) {
529+
// TODO remove after only new model is used
530+
func (r *ComponentBuildReconciler) WaitForCacheUpdateOldModel(ctx context.Context, namespace types.NamespacedName, component *compapiv1alpha1.Component) {
500531
log := ctrllog.FromContext(ctx)
501532

502533
// Here we do some trick.
@@ -532,7 +563,7 @@ func (r *ComponentBuildReconciler) WaitForCacheUpdate(ctx context.Context, names
532563
}
533564
}
534565

535-
func readBuildStatus(component *appstudiov1alpha1.Component) *BuildStatus {
566+
func readBuildStatus(component *compapiv1alpha1.Component) *BuildStatus {
536567
if component.Annotations == nil {
537568
return &BuildStatus{}
538569
}
@@ -545,7 +576,7 @@ func readBuildStatus(component *appstudiov1alpha1.Component) *BuildStatus {
545576
return &BuildStatus{}
546577
}
547578

548-
func writeBuildStatus(component *appstudiov1alpha1.Component, buildStatus *BuildStatus) {
579+
func writeBuildStatus(component *compapiv1alpha1.Component, buildStatus *BuildStatus) {
549580
if component.Annotations == nil {
550581
component.Annotations = make(map[string]string)
551582
}

0 commit comments

Comments
 (0)