Skip to content

Commit 4eb298d

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 4eb298d

22 files changed

+4330
-576
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: 42 additions & 14 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,21 @@ 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+
// For new model: only reconcile when spec changes (Generation increments), not on status-only updates
129+
// For old model: always reconcile on update (old model uses annotations which don't increment Generation)
130+
if isComponentUsingNewModel(component) {
131+
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
132+
}
117133
return true
118134
},
119135
DeleteFunc: func(e event.DeleteEvent) bool {
@@ -127,6 +143,13 @@ func (r *ComponentBuildReconciler) SetupWithManager(mgr ctrl.Manager) error {
127143
Complete(r)
128144
}
129145

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

@@ -164,7 +187,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
164187
reconcileStartTime := time.Now()
165188

166189
// Fetch the Component instance
167-
var component appstudiov1alpha1.Component
190+
var component compapiv1alpha1.Component
168191
err := r.Client.Get(ctx, req.NamespacedName, &component)
169192
if err != nil {
170193
if errors.IsNotFound(err) {
@@ -177,11 +200,15 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
177200
return ctrl.Result{}, err
178201
}
179202

203+
if isComponentUsingNewModel(&component) {
204+
return r.ReconcileNewModel(ctx, req)
205+
}
206+
180207
// Don't recreate build pipeline Service Account upon component deletion.
181208
if component.ObjectMeta.DeletionTimestamp.IsZero() {
182209
// We need to make sure the Service Account exists before checking the Component image,
183210
// 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 {
211+
if err := r.EnsureBuildPipelineServiceAccount(ctx, &component, true); err != nil {
185212
return ctrl.Result{}, err
186213
}
187214
}
@@ -202,7 +229,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
202229
log.Error(err, "failed to update component after waiting for containerImage", l.Action, l.ActionUpdate, l.Audit, "true")
203230
return ctrl.Result{}, err
204231
}
205-
r.WaitForCacheUpdate(ctx, req.NamespacedName, &component)
232+
r.WaitForCacheUpdateOldModel(ctx, req.NamespacedName, &component)
206233

207234
return ctrl.Result{}, nil
208235
}
@@ -256,7 +283,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
256283
log.Info("PaC finalizer removed", l.Action, l.ActionDelete)
257284

258285
// Try to clean up Pipelines as Code configuration
259-
_, _ = r.UndoPaCProvisionForComponent(ctx, &component)
286+
_, _ = r.UndoPaCProvisionForComponentOldModel(ctx, &component)
260287
}
261288

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

310337
return ctrl.Result{}, nil
311338
}
@@ -338,7 +365,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
338365
return ctrl.Result{}, nil
339366
}
340367

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

343370
if err != nil {
344371
if boErr, ok := err.(*boerrors.BuildOpError); ok && boErr.IsPersistent() {
@@ -368,7 +395,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
368395
}()
369396

370397
pacBuildStatus := &PaCBuildStatus{}
371-
if mergeUrl, err := r.ProvisionPaCForComponent(ctx, &component); err != nil {
398+
if mergeUrl, err := r.ProvisionPaCForComponentOldModel(ctx, &component); err != nil {
372399
if boErr, ok := err.(*boerrors.BuildOpError); ok && boErr.IsPersistent() {
373400
log.Error(err, "Pipelines as Code provision for the Component failed")
374401
pacBuildStatus.State = "error"
@@ -436,7 +463,7 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
436463
}
437464

438465
pacBuildStatus := &PaCBuildStatus{}
439-
if mergeUrl, err := r.UndoPaCProvisionForComponent(ctx, &component); err != nil {
466+
if mergeUrl, err := r.UndoPaCProvisionForComponentOldModel(ctx, &component); err != nil {
440467
if boErr, ok := err.(*boerrors.BuildOpError); ok && boErr.IsPersistent() {
441468
log.Error(err, "Pipelines as Code unprovision for the Component failed")
442469
pacBuildStatus.State = "error"
@@ -491,12 +518,13 @@ func (r *ComponentBuildReconciler) Reconcile(ctx context.Context, req ctrl.Reque
491518
// remove component from metrics map
492519
delete(bometrics.ComponentTimesForMetrics, componentIdForMetrics)
493520

494-
r.WaitForCacheUpdate(ctx, req.NamespacedName, &component)
521+
r.WaitForCacheUpdateOldModel(ctx, req.NamespacedName, &component)
495522

496523
return ctrl.Result{}, nil
497524
}
498525

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

502530
// Here we do some trick.
@@ -532,7 +560,7 @@ func (r *ComponentBuildReconciler) WaitForCacheUpdate(ctx context.Context, names
532560
}
533561
}
534562

535-
func readBuildStatus(component *appstudiov1alpha1.Component) *BuildStatus {
563+
func readBuildStatus(component *compapiv1alpha1.Component) *BuildStatus {
536564
if component.Annotations == nil {
537565
return &BuildStatus{}
538566
}
@@ -545,7 +573,7 @@ func readBuildStatus(component *appstudiov1alpha1.Component) *BuildStatus {
545573
return &BuildStatus{}
546574
}
547575

548-
func writeBuildStatus(component *appstudiov1alpha1.Component, buildStatus *BuildStatus) {
576+
func writeBuildStatus(component *compapiv1alpha1.Component, buildStatus *BuildStatus) {
549577
if component.Annotations == nil {
550578
component.Annotations = make(map[string]string)
551579
}

0 commit comments

Comments
 (0)