diff --git a/api/v1alpha1/collectors.go b/api/v1alpha1/collectors.go index 504f4d12..ef289843 100644 --- a/api/v1alpha1/collectors.go +++ b/api/v1alpha1/collectors.go @@ -1,8 +1,22 @@ package v1alpha1 -// Collector represents a reference to a Collector to be executed as part of the release workflow. +// Collectors holds the list of collectors to be executed as part of the release workflow along with the +// ServiceAccount to be used in the PipelineRun. // +kubebuilder:object:generate=true -type Collector struct { +type Collectors struct { + // Items is the list of Collectors to be executed as part of the release workflow + // +required + Items []CollectorItem `json:"items"` + + // ServiceAccountName is the ServiceAccount to use during the execution of the Collectors Pipeline + // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` +} + +// CollectorItem represents all the information about an specific collector which will be executed in the +// CollectorsPipeline. +type CollectorItem struct { // Name of the collector // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ // +required diff --git a/api/v1alpha1/release_conditions.go b/api/v1alpha1/release_conditions.go index b678a112..770caa09 100644 --- a/api/v1alpha1/release_conditions.go +++ b/api/v1alpha1/release_conditions.go @@ -6,9 +6,15 @@ const ( // finalProcessedConditionType is the type used to track the status of a Release Final Pipeline processing finalProcessedConditionType conditions.ConditionType = "FinalPipelineProcessed" + // managedCollectorsProcessedConditionType is the type used to track the status of a Release Managed Collectors Pipeline processing + managedCollectorsProcessedConditionType conditions.ConditionType = "ManagedCollectorsPipelineProcessed" + // managedProcessedConditionType is the type used to track the status of a Release Managed Pipeline processing managedProcessedConditionType conditions.ConditionType = "ManagedPipelineProcessed" + // tenantCollectorsProcessedConditionType is the type used to track the status of a Release Tenant Collectors Pipeline processing + tenantCollectorsProcessedConditionType conditions.ConditionType = "TenantCollectorsPipelineProcessed" + // tenantProcessedConditionType is the type used to track the status of a Release Tenant Pipeline processing tenantProcessedConditionType conditions.ConditionType = "TenantPipelineProcessed" diff --git a/api/v1alpha1/release_types.go b/api/v1alpha1/release_types.go index ec84313f..e3fde8b7 100644 --- a/api/v1alpha1/release_types.go +++ b/api/v1alpha1/release_types.go @@ -72,6 +72,10 @@ type ReleaseStatus struct { // +optional Conditions []metav1.Condition `json:"conditions"` + // CollectorsProcessing contains information about the release collectors processing + // +optional + CollectorsProcessing CollectorsInfo `json:"collectorsProcessing,omitempty"` + // FinalProcessing contains information about the release final processing // +optional FinalProcessing PipelineInfo `json:"finalProcessing,omitempty"` @@ -121,6 +125,15 @@ type AttributionInfo struct { StandingAuthorization bool `json:"standingAuthorization,omitempty"` } +// CollectorsInfo defines the observed state of the release collectors. +type CollectorsInfo struct { + // ManagedCollectorsProcessing contains information about the release managed collectors processing + ManagedCollectorsProcessing PipelineInfo `json:"managedCollectorsProcessing,omitempty"` + + // TenantCollectorsProcessing contains information about the release tenant collectors processing + TenantCollectorsProcessing PipelineInfo `json:"tenantCollectorsProcessing,omitempty"` +} + // PipelineInfo defines the observed state of a release pipeline processing. type PipelineInfo struct { // CompletionTime is the time when the Release processing was completed @@ -175,11 +188,21 @@ func (r *Release) HasFinalPipelineProcessingFinished() bool { return r.hasPhaseFinished(finalProcessedConditionType) } +// HasManagedCollectorsPipelineProcessingFinished checks whether the Release Managed Collectors Pipeline processing has finished, regardless of the result. +func (r *Release) HasManagedCollectorsPipelineProcessingFinished() bool { + return r.hasPhaseFinished(managedCollectorsProcessedConditionType) +} + // HasManagedPipelineProcessingFinished checks whether the Release Managed Pipeline processing has finished, regardless of the result. func (r *Release) HasManagedPipelineProcessingFinished() bool { return r.hasPhaseFinished(managedProcessedConditionType) } +// HasTenantCollectorsPipelineProcessingFinished checks whether the Release Tenant Collectors Pipeline processing has finished, regardless of the result. +func (r *Release) HasTenantCollectorsPipelineProcessingFinished() bool { + return r.hasPhaseFinished(tenantCollectorsProcessedConditionType) +} + // HasTenantPipelineProcessingFinished checks whether the Release Tenant Pipeline processing has finished, regardless of the result. func (r *Release) HasTenantPipelineProcessingFinished() bool { return r.hasPhaseFinished(tenantProcessedConditionType) @@ -205,11 +228,21 @@ func (r *Release) IsFinalPipelineProcessed() bool { return meta.IsStatusConditionTrue(r.Status.Conditions, finalProcessedConditionType.String()) } +// IsManagedCollectorsPipelineProcessed checks whether the Release Managed Collectors Pipeline was successfully processed. +func (r *Release) IsManagedCollectorsPipelineProcessed() bool { + return meta.IsStatusConditionTrue(r.Status.Conditions, managedCollectorsProcessedConditionType.String()) +} + // IsManagedPipelineProcessed checks whether the Release Managed Pipeline was successfully processed. func (r *Release) IsManagedPipelineProcessed() bool { return meta.IsStatusConditionTrue(r.Status.Conditions, managedProcessedConditionType.String()) } +// IsTenantCollectorsPipelineProcessed checks whether the Release Tenant Collectors Pipeline was successfully processed. +func (r *Release) IsTenantCollectorsPipelineProcessed() bool { + return meta.IsStatusConditionTrue(r.Status.Conditions, tenantCollectorsProcessedConditionType.String()) +} + // IsTenantPipelineProcessed checks whether the Release Tenant Pipeline was successfully processed. func (r *Release) IsTenantPipelineProcessed() bool { return meta.IsStatusConditionTrue(r.Status.Conditions, tenantProcessedConditionType.String()) @@ -220,11 +253,21 @@ func (r *Release) IsFinalPipelineProcessing() bool { return r.isPhaseProgressing(finalProcessedConditionType) } +// IsManagedCollectorsPipelineProcessing checks whether the Release Managed Collectors Pipeline processing is in progress. +func (r *Release) IsManagedCollectorsPipelineProcessing() bool { + return r.isPhaseProgressing(managedCollectorsProcessedConditionType) +} + // IsManagedPipelineProcessing checks whether the Release Managed Pipeline processing is in progress. func (r *Release) IsManagedPipelineProcessing() bool { return r.isPhaseProgressing(managedProcessedConditionType) } +// IsTenantCollectorsPipelineProcessing checks whether the Release Tenant Collectors Pipeline processing is in progress. +func (r *Release) IsTenantCollectorsPipelineProcessing() bool { + return r.isPhaseProgressing(tenantCollectorsProcessedConditionType) +} + // IsTenantPipelineProcessing checks whether the Release Tenant Pipeline processing is in progress. func (r *Release) IsTenantPipelineProcessing() bool { return r.isPhaseProgressing(tenantProcessedConditionType) @@ -263,6 +306,24 @@ func (r *Release) MarkFinalPipelineProcessed() { ) } +// MarkManagedCollectorsPipelineProcessed marks the Release Managed Collectors Pipeline as processed. +func (r *Release) MarkManagedCollectorsPipelineProcessed() { + if !r.IsManagedCollectorsPipelineProcessing() || r.HasManagedCollectorsPipelineProcessingFinished() { + return + } + + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime = &metav1.Time{Time: time.Now()} + conditions.SetCondition(&r.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + + go metrics.RegisterCompletedReleasePipelineProcessing( + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime, + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime, + SucceededReason.String(), + r.Status.Target, + metadata.ManagedCollectorsPipelineType, + ) +} + // MarkManagedPipelineProcessed marks the Release Managed Pipeline as processed. func (r *Release) MarkManagedPipelineProcessed() { if !r.IsManagedPipelineProcessing() || r.HasManagedPipelineProcessingFinished() { @@ -281,6 +342,24 @@ func (r *Release) MarkManagedPipelineProcessed() { ) } +// MarkTenantCollectorsPipelineProcessed marks the Release Tenant Collectors Pipeline as processed. +func (r *Release) MarkTenantCollectorsPipelineProcessed() { + if !r.IsTenantCollectorsPipelineProcessing() || r.HasTenantCollectorsPipelineProcessingFinished() { + return + } + + r.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime = &metav1.Time{Time: time.Now()} + conditions.SetCondition(&r.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + + go metrics.RegisterCompletedReleasePipelineProcessing( + r.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime, + r.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime, + SucceededReason.String(), + r.Status.Target, + metadata.TenantCollectorsPipelineType, + ) +} + // MarkTenantPipelineProcessed marks the Release Tenant Pipeline as processed. func (r *Release) MarkTenantPipelineProcessed() { if !r.IsTenantPipelineProcessing() || r.HasTenantPipelineProcessingFinished() { @@ -320,6 +399,27 @@ func (r *Release) MarkFinalPipelineProcessing() { ) } +// MarkManagedCollectorsPipelineProcessing marks the Release Managed Collectors Pipeline as processing. +func (r *Release) MarkManagedCollectorsPipelineProcessing() { + if r.HasManagedCollectorsPipelineProcessingFinished() { + return + } + + if !r.IsManagedCollectorsPipelineProcessing() { + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime = &metav1.Time{Time: time.Now()} + } + + conditions.SetCondition(&r.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionFalse, ProgressingReason) + + go metrics.RegisterNewReleasePipelineProcessing( + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime, + r.Status.StartTime, + ProgressingReason.String(), + r.Status.Target, + metadata.ManagedPipelineType, + ) +} + // MarkManagedPipelineProcessing marks the Release Managed Pipeline as processing. func (r *Release) MarkManagedPipelineProcessing() { if r.HasManagedPipelineProcessingFinished() { @@ -341,6 +441,27 @@ func (r *Release) MarkManagedPipelineProcessing() { ) } +// MarkTenantCollectorsPipelineProcessing marks the Release Tenant Collectors Pipeline as processing. +func (r *Release) MarkTenantCollectorsPipelineProcessing() { + if r.HasTenantCollectorsPipelineProcessingFinished() { + return + } + + if !r.IsTenantCollectorsPipelineProcessing() { + r.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime = &metav1.Time{Time: time.Now()} + } + + conditions.SetCondition(&r.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionFalse, ProgressingReason) + + go metrics.RegisterNewReleasePipelineProcessing( + r.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime, + r.Status.StartTime, + ProgressingReason.String(), + r.Status.Target, + metadata.TenantCollectorsPipelineType, + ) +} + // MarkTenantPipelineProcessing marks the Release Tenant Pipeline as processing. func (r *Release) MarkTenantPipelineProcessing() { if r.HasTenantPipelineProcessingFinished() { @@ -380,6 +501,24 @@ func (r *Release) MarkFinalPipelineProcessingFailed(message string) { ) } +// MarkManagedCollectorsPipelineProcessingFailed marks the Release Managed Collectors Pipeline processing as failed. +func (r *Release) MarkManagedCollectorsPipelineProcessingFailed(message string) { + if !r.IsManagedCollectorsPipelineProcessing() || r.HasManagedCollectorsPipelineProcessingFinished() { + return + } + + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime = &metav1.Time{Time: time.Now()} + conditions.SetConditionWithMessage(&r.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionFalse, FailedReason, message) + + go metrics.RegisterCompletedReleasePipelineProcessing( + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime, + r.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime, + FailedReason.String(), + r.Status.Target, + metadata.ManagedCollectorsPipelineType, + ) +} + // MarkManagedPipelineProcessingFailed marks the Release Managed Pipeline processing as failed. func (r *Release) MarkManagedPipelineProcessingFailed(message string) { if !r.IsManagedPipelineProcessing() || r.HasManagedPipelineProcessingFinished() { @@ -398,6 +537,24 @@ func (r *Release) MarkManagedPipelineProcessingFailed(message string) { ) } +// MarkTenantCollectorsPipelineProcessingFailed marks the Release Tenant Collectors Pipeline processing as failed. +func (r *Release) MarkTenantCollectorsPipelineProcessingFailed(message string) { + if !r.IsTenantCollectorsPipelineProcessing() || r.HasTenantCollectorsPipelineProcessingFinished() { + return + } + + r.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime = &metav1.Time{Time: time.Now()} + conditions.SetConditionWithMessage(&r.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionFalse, FailedReason, message) + + go metrics.RegisterCompletedReleasePipelineProcessing( + r.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime, + r.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime, + FailedReason.String(), + r.Status.Target, + metadata.TenantCollectorsPipelineType, + ) +} + // MarkTenantPipelineProcessingFailed marks the Release Tenant Pipeline processing as failed. func (r *Release) MarkTenantPipelineProcessingFailed(message string) { if !r.IsTenantPipelineProcessing() || r.HasTenantPipelineProcessingFinished() { @@ -425,6 +582,15 @@ func (r *Release) MarkFinalPipelineProcessingSkipped() { conditions.SetCondition(&r.Status.Conditions, finalProcessedConditionType, metav1.ConditionTrue, SkippedReason) } +// MarkManagedCollectorsPipelineProcessingSkipped marks the Release Managed Collectors Pipeline processing as skipped. +func (r *Release) MarkManagedCollectorsPipelineProcessingSkipped() { + if r.HasManagedCollectorsPipelineProcessingFinished() { + return + } + + conditions.SetCondition(&r.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionTrue, SkippedReason) +} + // MarkManagedPipelineProcessingSkipped marks the Release Managed Pipeline processing as skipped. func (r *Release) MarkManagedPipelineProcessingSkipped() { if r.HasManagedPipelineProcessingFinished() { @@ -434,6 +600,15 @@ func (r *Release) MarkManagedPipelineProcessingSkipped() { conditions.SetCondition(&r.Status.Conditions, managedProcessedConditionType, metav1.ConditionTrue, SkippedReason) } +// MarkTenantCollectorsPipelineProcessingSkipped marks the Release Tenant Collectors Pipeline processing as skipped. +func (r *Release) MarkTenantCollectorsPipelineProcessingSkipped() { + if r.HasTenantCollectorsPipelineProcessingFinished() { + return + } + + conditions.SetCondition(&r.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionTrue, SkippedReason) +} + // MarkTenantPipelineProcessingSkipped marks the Release Tenant Pipeline processing as skipped. func (r *Release) MarkTenantPipelineProcessingSkipped() { if r.HasTenantPipelineProcessingFinished() { @@ -455,12 +630,14 @@ func (r *Release) MarkReleased() { go metrics.RegisterCompletedRelease( r.Status.StartTime, r.Status.CompletionTime, + r.getPhaseReason(finalProcessedConditionType), + r.getPhaseReason(managedCollectorsProcessedConditionType), r.getPhaseReason(managedProcessedConditionType), - SucceededReason.String(), - r.Status.Target, + r.getPhaseReason(tenantCollectorsProcessedConditionType), r.getPhaseReason(tenantProcessedConditionType), - r.getPhaseReason(finalProcessedConditionType), r.getPhaseReason(validatedConditionType), + SucceededReason.String(), + r.Status.Target, ) } @@ -491,12 +668,14 @@ func (r *Release) MarkReleaseFailed(message string) { go metrics.RegisterCompletedRelease( r.Status.StartTime, r.Status.CompletionTime, - r.getPhaseReason(tenantProcessedConditionType), - r.getPhaseReason(managedProcessedConditionType), r.getPhaseReason(finalProcessedConditionType), + r.getPhaseReason(managedCollectorsProcessedConditionType), + r.getPhaseReason(managedProcessedConditionType), + r.getPhaseReason(tenantCollectorsProcessedConditionType), + r.getPhaseReason(tenantProcessedConditionType), + r.getPhaseReason(validatedConditionType), FailedReason.String(), r.Status.Target, - r.getPhaseReason(validatedConditionType), ) } diff --git a/api/v1alpha1/release_types_test.go b/api/v1alpha1/release_types_test.go index 7e8f23bc..18a74826 100644 --- a/api/v1alpha1/release_types_test.go +++ b/api/v1alpha1/release_types_test.go @@ -58,6 +58,34 @@ var _ = Describe("Release type", func() { }) }) + When("HasManagedCollectorsPipelineProcessingFinished method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should return true when the managed collectors pipeline processed condition status is True", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + Expect(release.HasManagedCollectorsPipelineProcessingFinished()).To(BeTrue()) + }) + + It("should return false when the managed collectors pipeline processed condition status is False and the reason is Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionFalse, ProgressingReason) + Expect(release.HasManagedCollectorsPipelineProcessingFinished()).To(BeFalse()) + }) + + It("Should return true when the managed collectors pipeline processed condition status is False and the reason is not Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionFalse, FailedReason) + Expect(release.HasManagedCollectorsPipelineProcessingFinished()).To(BeTrue()) + }) + + It("should return false when the managed collectors pipeline processed condition status is Unknown", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionUnknown, ProgressingReason) + Expect(release.HasManagedCollectorsPipelineProcessingFinished()).To(BeFalse()) + }) + }) + When("HasManagedPipelineProcessingFinished method is called", func() { var release *Release @@ -86,6 +114,34 @@ var _ = Describe("Release type", func() { }) }) + When("HasTenantCollectorsPipelineProcessingFinished method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should return true when the tenant collectors pipeline processed condition status is True", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + Expect(release.HasTenantCollectorsPipelineProcessingFinished()).To(BeTrue()) + }) + + It("should return false when the tenant collectors pipeline processed condition status is False and the reason is Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionFalse, ProgressingReason) + Expect(release.HasTenantCollectorsPipelineProcessingFinished()).To(BeFalse()) + }) + + It("Should return true when the tenant collectors pipeline processed condition status is False and the reason is not Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionFalse, FailedReason) + Expect(release.HasTenantCollectorsPipelineProcessingFinished()).To(BeTrue()) + }) + + It("should return false when the tenant collectors pipeline processed condition status is Unknown", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionUnknown, ProgressingReason) + Expect(release.HasTenantCollectorsPipelineProcessingFinished()).To(BeFalse()) + }) + }) + When("HasTenantPipelineProcessingFinished method is called", func() { var release *Release @@ -213,6 +269,33 @@ var _ = Describe("Release type", func() { }) + When("IsManagedCollectorsPipelineProcessed method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should return true when the managed collectors pipeline processed condition status is True", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + Expect(release.IsManagedCollectorsPipelineProcessed()).To(BeTrue()) + }) + + It("should return false when the managed collectors pipeline processed condition status is False", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionFalse, SucceededReason) + Expect(release.IsManagedCollectorsPipelineProcessed()).To(BeFalse()) + }) + + It("should return false when the managed collectors pipeline processed condition status is Unknown", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionUnknown, SucceededReason) + Expect(release.IsManagedCollectorsPipelineProcessed()).To(BeFalse()) + }) + + It("should return false when the managed collectors pipeline processed condition is missing", func() { + Expect(release.IsManagedCollectorsPipelineProcessed()).To(BeFalse()) + }) + }) + When("IsManagedPipelineProcessed method is called", func() { var release *Release @@ -240,6 +323,33 @@ var _ = Describe("Release type", func() { }) }) + When("IsTenantCollectorsPipelineProcessed method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should return true when the tenant collectors pipeline processed condition status is True", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + Expect(release.IsTenantCollectorsPipelineProcessed()).To(BeTrue()) + }) + + It("should return false when the tenant collectors pipeline processed condition status is False", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionFalse, SucceededReason) + Expect(release.IsTenantCollectorsPipelineProcessed()).To(BeFalse()) + }) + + It("should return false when the tenant collectors pipeline processed condition status is Unknown", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionUnknown, SucceededReason) + Expect(release.IsTenantCollectorsPipelineProcessed()).To(BeFalse()) + }) + + It("should return false when the tenant collectors pipeline processed condition is missing", func() { + Expect(release.IsTenantCollectorsPipelineProcessed()).To(BeFalse()) + }) + }) + When("IsTenantPipelineProcessed method is called", func() { var release *Release @@ -295,6 +405,38 @@ var _ = Describe("Release type", func() { }) + When("IsManagedCollectorsPipelineProcessing method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should return false when the managed collectors pipeline processed condition is missing", func() { + Expect(release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should return false when the managed collectors pipeline processed condition status is True", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + Expect(release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should return true when the managed collectors pipeline processed condition status is False and the reason is Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionFalse, ProgressingReason) + Expect(release.IsManagedCollectorsPipelineProcessing()).To(BeTrue()) + }) + + It("should return false when the managed collectors pipeline processed condition status is False and the reason is not Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionFalse, FailedReason) + Expect(release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should return false when the managed collectors pipeline processed condition status is Unknown", func() { + conditions.SetCondition(&release.Status.Conditions, managedCollectorsProcessedConditionType, metav1.ConditionUnknown, ProgressingReason) + Expect(release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + }) + }) + When("IsManagedPipelineProcessing method is called", func() { var release *Release @@ -327,6 +469,38 @@ var _ = Describe("Release type", func() { }) }) + When("IsTenantCollectorsPipelineProcessing method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should return false when the tenant collectors pipeline processed condition is missing", func() { + Expect(release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should return false when the tenant collectors pipeline processed condition status is True", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionTrue, SucceededReason) + Expect(release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should return true when the tenant collectors pipeline processed condition status is False and the reason is Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionFalse, ProgressingReason) + Expect(release.IsTenantCollectorsPipelineProcessing()).To(BeTrue()) + }) + + It("should return false when the tenant collectors pipeline processed condition status is False and the reason is not Progressing", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionFalse, FailedReason) + Expect(release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should return false when the tenant collectors pipeline processed condition status is Unknown", func() { + conditions.SetCondition(&release.Status.Conditions, tenantCollectorsProcessedConditionType, metav1.ConditionUnknown, ProgressingReason) + Expect(release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + }) + }) + When("IsTenantPipelineProcessing method is called", func() { var release *Release @@ -487,6 +661,48 @@ var _ = Describe("Release type", func() { }) }) + When("MarkManagedCollectorsPipelineProcessed method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release managed collectors pipeline processing has not started", func() { + release.MarkManagedCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("should do nothing if the Release managed collectors pipeline processing finished", func() { + release.MarkManagedCollectorsPipelineProcessing() + release.MarkManagedCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime = &metav1.Time{} + release.MarkManagedCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + }) + + It("should register the completion time", func() { + release.MarkManagedCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + release.MarkManagedCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkManagedCollectorsPipelineProcessing() + release.MarkManagedCollectorsPipelineProcessed() + + condition := meta.FindStatusCondition(release.Status.Conditions, managedCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Reason": Equal(SucceededReason.String()), + "Status": Equal(metav1.ConditionTrue), + })) + }) + }) + When("MarkManagedPipelineProcessed method is called", func() { var release *Release @@ -529,6 +745,48 @@ var _ = Describe("Release type", func() { }) }) + When("MarkTenantCollectorsPipelineProcessed method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release tenant collectors pipeline processing has not started", func() { + release.MarkTenantCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("should do nothing if the Release tenant collectors pipeline processing finished", func() { + release.MarkTenantCollectorsPipelineProcessing() + release.MarkTenantCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime = &metav1.Time{} + release.MarkTenantCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + }) + + It("should register the completion time", func() { + release.MarkTenantCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + release.MarkTenantCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkTenantCollectorsPipelineProcessing() + release.MarkTenantCollectorsPipelineProcessed() + + condition := meta.FindStatusCondition(release.Status.Conditions, tenantCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Reason": Equal(SucceededReason.String()), + "Status": Equal(metav1.ConditionTrue), + })) + }) + }) + When("MarkTenantPipelineProcessed method is called", func() { var release *Release @@ -615,6 +873,49 @@ var _ = Describe("Release type", func() { }) + When("MarkManagedCollectorsPipelineProcessing method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release managed collectors pipeline processing finished", func() { + release.MarkManagedCollectorsPipelineProcessing() + release.MarkManagedCollectorsPipelineProcessed() + Expect(release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + release.MarkManagedCollectorsPipelineProcessing() + Expect(release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should register the start time if the managed collectors pipeline is not processing", func() { + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime).To(BeNil()) + release.MarkManagedCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime).NotTo(BeNil()) + }) + + It("should not register the start time if the managed collectors pipeline is processing already", func() { + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime).To(BeNil()) + release.MarkManagedCollectorsPipelineProcessing() + release.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime = &metav1.Time{} + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime.IsZero()).To(BeTrue()) + release.MarkManagedCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.StartTime.IsZero()).To(BeTrue()) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkManagedCollectorsPipelineProcessing() + + condition := meta.FindStatusCondition(release.Status.Conditions, managedCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Reason": Equal(ProgressingReason.String()), + "Status": Equal(metav1.ConditionFalse), + })) + }) + }) + When("MarkManagedPipelineProcessing method is called", func() { var release *Release @@ -658,6 +959,49 @@ var _ = Describe("Release type", func() { }) }) + When("MarkTenantCollectorsPipelineProcessing method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release tenant collectors pipeline processing finished", func() { + release.MarkTenantCollectorsPipelineProcessing() + release.MarkTenantCollectorsPipelineProcessed() + Expect(release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + release.MarkTenantCollectorsPipelineProcessing() + Expect(release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should register the start time if the tenant collectors pipeline is not processing", func() { + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime).To(BeNil()) + release.MarkTenantCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime).NotTo(BeNil()) + }) + + It("should not register the start time if the tenant collectors pipeline is processing already", func() { + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime).To(BeNil()) + release.MarkTenantCollectorsPipelineProcessing() + release.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime = &metav1.Time{} + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime.IsZero()).To(BeTrue()) + release.MarkTenantCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.StartTime.IsZero()).To(BeTrue()) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkTenantCollectorsPipelineProcessing() + + condition := meta.FindStatusCondition(release.Status.Conditions, tenantCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Reason": Equal(ProgressingReason.String()), + "Status": Equal(metav1.ConditionFalse), + })) + }) + }) + When("MarkTenantPipelineProcessing method is called", func() { var release *Release @@ -745,6 +1089,49 @@ var _ = Describe("Release type", func() { }) + When("MarkManagedCollectorsPipelineProcessingFailed method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release managed collectors pipeline processing has not started", func() { + release.MarkManagedCollectorsPipelineProcessingFailed("") + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("should do nothing if the Release managed collectors pipeline processing finished", func() { + release.MarkManagedCollectorsPipelineProcessing() + release.MarkManagedCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime = &metav1.Time{} + release.MarkManagedCollectorsPipelineProcessingFailed("") + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + }) + + It("should register the completion time", func() { + release.MarkManagedCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + release.MarkManagedCollectorsPipelineProcessingFailed("") + Expect(release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkManagedCollectorsPipelineProcessing() + release.MarkManagedCollectorsPipelineProcessingFailed("foo") + + condition := meta.FindStatusCondition(release.Status.Conditions, managedCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Message": Equal("foo"), + "Reason": Equal(FailedReason.String()), + "Status": Equal(metav1.ConditionFalse), + })) + }) + }) + When("MarkManagedPipelineProcessingFailed method is called", func() { var release *Release @@ -788,6 +1175,49 @@ var _ = Describe("Release type", func() { }) }) + When("MarkTenantCollectorsPipelineProcessingFailed method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release tenant collectors pipeline processing has not started", func() { + release.MarkTenantCollectorsPipelineProcessingFailed("") + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("should do nothing if the Release tenant collectors pipeline processing finished", func() { + release.MarkTenantCollectorsPipelineProcessing() + release.MarkTenantCollectorsPipelineProcessed() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime = &metav1.Time{} + release.MarkTenantCollectorsPipelineProcessingFailed("") + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + }) + + It("should register the completion time", func() { + release.MarkTenantCollectorsPipelineProcessing() + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeTrue()) + release.MarkTenantCollectorsPipelineProcessingFailed("") + Expect(release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime.IsZero()).To(BeFalse()) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkTenantCollectorsPipelineProcessing() + release.MarkTenantCollectorsPipelineProcessingFailed("foo") + + condition := meta.FindStatusCondition(release.Status.Conditions, tenantCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Message": Equal("foo"), + "Reason": Equal(FailedReason.String()), + "Status": Equal(metav1.ConditionFalse), + })) + }) + }) + When("MarkTenantPipelineProcessingFailed method is called", func() { var release *Release @@ -865,6 +1295,40 @@ var _ = Describe("Release type", func() { }) }) + When("MarkManagedCollectorsPipelineProcessingSkipped method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release managed collectors pipeline processing finished already", func() { + release.MarkManagedCollectorsPipelineProcessing() + release.MarkManagedCollectorsPipelineProcessingFailed("error") + release.MarkManagedCollectorsPipelineProcessingSkipped() + + condition := meta.FindStatusCondition(release.Status.Conditions, managedCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Message": Equal("error"), + "Reason": Equal(FailedReason.String()), + "Status": Equal(metav1.ConditionFalse), + })) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkManagedCollectorsPipelineProcessingSkipped() + + condition := meta.FindStatusCondition(release.Status.Conditions, managedCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Reason": Equal(SkippedReason.String()), + "Status": Equal(metav1.ConditionTrue), + })) + }) + }) + When("MarkManagedPipelineProcessingSkipped method is called", func() { var release *Release @@ -899,6 +1363,40 @@ var _ = Describe("Release type", func() { }) }) + When("MarkTenantCollectorsPipelineProcessingSkipped method is called", func() { + var release *Release + + BeforeEach(func() { + release = &Release{} + }) + + It("should do nothing if the Release tenant collectors pipeline processing finished already", func() { + release.MarkTenantCollectorsPipelineProcessing() + release.MarkTenantCollectorsPipelineProcessingFailed("error") + release.MarkTenantCollectorsPipelineProcessingSkipped() + + condition := meta.FindStatusCondition(release.Status.Conditions, tenantCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Message": Equal("error"), + "Reason": Equal(FailedReason.String()), + "Status": Equal(metav1.ConditionFalse), + })) + }) + + It("should register the condition", func() { + Expect(release.Status.Conditions).To(HaveLen(0)) + release.MarkTenantCollectorsPipelineProcessingSkipped() + + condition := meta.FindStatusCondition(release.Status.Conditions, tenantCollectorsProcessedConditionType.String()) + Expect(condition).NotTo(BeNil()) + Expect(*condition).To(MatchFields(IgnoreExtras, Fields{ + "Reason": Equal(SkippedReason.String()), + "Status": Equal(metav1.ConditionTrue), + })) + }) + }) + When("MarkTenantPipelineProcessingSkipped method is called", func() { var release *Release diff --git a/api/v1alpha1/releaseplan_types.go b/api/v1alpha1/releaseplan_types.go index d1ab9698..59c78e4c 100644 --- a/api/v1alpha1/releaseplan_types.go +++ b/api/v1alpha1/releaseplan_types.go @@ -35,9 +35,9 @@ type ReleasePlanSpec struct { // +required Application string `json:"application"` - // Collectors is a list of data collectors to be executed as part of the release process + // Collectors contains all the information of the collectors to be executed as part of the release workflow // +optional - Collectors []Collector `json:"collectors,omitempty"` + Collectors *Collectors `json:"collectors,omitempty"` // Data is an unstructured key used for providing data for the managed Release Pipeline // +kubebuilder:pruning:PreserveUnknownFields diff --git a/api/v1alpha1/releaseplanadmission_types.go b/api/v1alpha1/releaseplanadmission_types.go index 4445c2bc..7916e41a 100644 --- a/api/v1alpha1/releaseplanadmission_types.go +++ b/api/v1alpha1/releaseplanadmission_types.go @@ -34,9 +34,9 @@ type ReleasePlanAdmissionSpec struct { // +required Applications []string `json:"applications"` - // Collectors is a list of data collectors to be executed as part of the release process + // Collectors contains all the information of the collectors to be executed as part of the release workflow // +optional - Collectors []Collector `json:"collectors,omitempty"` + Collectors *Collectors `json:"collectors,omitempty"` // Data is an unstructured key used for providing data for the managed Release Pipeline // +kubebuilder:pruning:PreserveUnknownFields diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f59b291b..1c455ffd 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -42,7 +42,7 @@ func (in *AttributionInfo) DeepCopy() *AttributionInfo { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Collector) DeepCopyInto(out *Collector) { +func (in *CollectorItem) DeepCopyInto(out *CollectorItem) { *out = *in if in.Params != nil { in, out := &in.Params, &out.Params @@ -51,12 +51,51 @@ func (in *Collector) DeepCopyInto(out *Collector) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collector. -func (in *Collector) DeepCopy() *Collector { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorItem. +func (in *CollectorItem) DeepCopy() *CollectorItem { if in == nil { return nil } - out := new(Collector) + out := new(CollectorItem) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Collectors) DeepCopyInto(out *Collectors) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CollectorItem, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collectors. +func (in *Collectors) DeepCopy() *Collectors { + if in == nil { + return nil + } + out := new(Collectors) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CollectorsInfo) DeepCopyInto(out *CollectorsInfo) { + *out = *in + in.ManagedCollectorsProcessing.DeepCopyInto(&out.ManagedCollectorsProcessing) + in.TenantCollectorsProcessing.DeepCopyInto(&out.TenantCollectorsProcessing) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorsInfo. +func (in *CollectorsInfo) DeepCopy() *CollectorsInfo { + if in == nil { + return nil + } + out := new(CollectorsInfo) in.DeepCopyInto(out) return out } @@ -284,10 +323,8 @@ func (in *ReleasePlanAdmissionSpec) DeepCopyInto(out *ReleasePlanAdmissionSpec) } if in.Collectors != nil { in, out := &in.Collectors, &out.Collectors - *out = make([]Collector, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + *out = new(Collectors) + (*in).DeepCopyInto(*out) } if in.Data != nil { in, out := &in.Data, &out.Data @@ -375,10 +412,8 @@ func (in *ReleasePlanSpec) DeepCopyInto(out *ReleasePlanSpec) { *out = *in if in.Collectors != nil { in, out := &in.Collectors, &out.Collectors - *out = make([]Collector, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + *out = new(Collectors) + (*in).DeepCopyInto(*out) } if in.Data != nil { in, out := &in.Data, &out.Data @@ -561,6 +596,7 @@ func (in *ReleaseStatus) DeepCopyInto(out *ReleaseStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + in.CollectorsProcessing.DeepCopyInto(&out.CollectorsProcessing) in.FinalProcessing.DeepCopyInto(&out.FinalProcessing) in.ManagedProcessing.DeepCopyInto(&out.ManagedProcessing) in.TenantProcessing.DeepCopyInto(&out.TenantProcessing) diff --git a/config/crd/bases/appstudio.redhat.com_releaseplanadmissions.yaml b/config/crd/bases/appstudio.redhat.com_releaseplanadmissions.yaml index e1f69356..f2d0777c 100644 --- a/config/crd/bases/appstudio.redhat.com_releaseplanadmissions.yaml +++ b/config/crd/bases/appstudio.redhat.com_releaseplanadmissions.yaml @@ -56,45 +56,59 @@ spec: type: string type: array collectors: - description: Collectors is a list of data collectors to be executed - as part of the release process - items: - description: Collector represents a reference to a Collector to - be executed as part of the release workflow. - properties: - name: - description: Name of the collector - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - params: - description: Params is a slice of parameters for a given collector - items: - description: Param represents a parameter for a collector - properties: - name: - description: Name is the name of the parameter - type: string - value: - description: Value is the value of the parameter - type: string - required: - - name - - value - type: object - type: array - timeout: - description: Timeout in seconds for the collector to execute - type: integer - type: - description: Type is the type of collector to be used - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - - params - - type - type: object - type: array + description: Collectors contains all the information of the collectors + to be executed as part of the release workflow + properties: + items: + description: Items is the list of Collectors to be executed as + part of the release workflow + items: + description: |- + CollectorItem represents all the information about an specific collector which will be executed in the + CollectorsPipeline. + properties: + name: + description: Name of the collector + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + params: + description: Params is a slice of parameters for a given + collector + items: + description: Param represents a parameter for a collector + properties: + name: + description: Name is the name of the parameter + type: string + value: + description: Value is the value of the parameter + type: string + required: + - name + - value + type: object + type: array + timeout: + description: Timeout in seconds for the collector to execute + type: integer + type: + description: Type is the type of collector to be used + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + - params + - type + type: object + type: array + serviceAccountName: + description: ServiceAccountName is the ServiceAccount to use during + the execution of the Collectors Pipeline + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - items + type: object data: description: Data is an unstructured key used for providing data for the managed Release Pipeline diff --git a/config/crd/bases/appstudio.redhat.com_releaseplans.yaml b/config/crd/bases/appstudio.redhat.com_releaseplans.yaml index ed8e8fd4..88c2eaa4 100644 --- a/config/crd/bases/appstudio.redhat.com_releaseplans.yaml +++ b/config/crd/bases/appstudio.redhat.com_releaseplans.yaml @@ -54,45 +54,59 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string collectors: - description: Collectors is a list of data collectors to be executed - as part of the release process - items: - description: Collector represents a reference to a Collector to - be executed as part of the release workflow. - properties: - name: - description: Name of the collector - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - params: - description: Params is a slice of parameters for a given collector - items: - description: Param represents a parameter for a collector - properties: - name: - description: Name is the name of the parameter - type: string - value: - description: Value is the value of the parameter - type: string - required: - - name - - value - type: object - type: array - timeout: - description: Timeout in seconds for the collector to execute - type: integer - type: - description: Type is the type of collector to be used - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - name - - params - - type - type: object - type: array + description: Collectors contains all the information of the collectors + to be executed as part of the release workflow + properties: + items: + description: Items is the list of Collectors to be executed as + part of the release workflow + items: + description: |- + CollectorItem represents all the information about an specific collector which will be executed in the + CollectorsPipeline. + properties: + name: + description: Name of the collector + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + params: + description: Params is a slice of parameters for a given + collector + items: + description: Param represents a parameter for a collector + properties: + name: + description: Name is the name of the parameter + type: string + value: + description: Value is the value of the parameter + type: string + required: + - name + - value + type: object + type: array + timeout: + description: Timeout in seconds for the collector to execute + type: integer + type: + description: Type is the type of collector to be used + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + - params + - type + type: object + type: array + serviceAccountName: + description: ServiceAccountName is the ServiceAccount to use during + the execution of the Collectors Pipeline + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - items + type: object data: description: Data is an unstructured key used for providing data for the managed Release Pipeline diff --git a/config/crd/bases/appstudio.redhat.com_releases.yaml b/config/crd/bases/appstudio.redhat.com_releases.yaml index 1c445106..fd318d8a 100644 --- a/config/crd/bases/appstudio.redhat.com_releases.yaml +++ b/config/crd/bases/appstudio.redhat.com_releases.yaml @@ -106,6 +106,63 @@ spec: the collectors results generated by the Collectors Pipeline type: object x-kubernetes-preserve-unknown-fields: true + collectorsProcessing: + description: CollectorsProcessing contains information about the release + collectors processing + properties: + managedCollectorsProcessing: + description: ManagedCollectorsProcessing contains information + about the release managed collectors processing + properties: + completionTime: + description: CompletionTime is the time when the Release processing + was completed + format: date-time + type: string + pipelineRun: + description: PipelineRun contains the namespaced name of the + managed Release PipelineRun executed as part of this release + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?\/[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + roleBinding: + description: |- + RoleBinding contains the namespaced name of the roleBinding created for the managed Release PipelineRun + executed as part of this release + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?\/[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + startTime: + description: StartTime is the time when the Release processing + started + format: date-time + type: string + type: object + tenantCollectorsProcessing: + description: TenantCollectorsProcessing contains information about + the release tenant collectors processing + properties: + completionTime: + description: CompletionTime is the time when the Release processing + was completed + format: date-time + type: string + pipelineRun: + description: PipelineRun contains the namespaced name of the + managed Release PipelineRun executed as part of this release + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?\/[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + roleBinding: + description: |- + RoleBinding contains the namespaced name of the roleBinding created for the managed Release PipelineRun + executed as part of this release + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?\/[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + startTime: + description: StartTime is the time when the Release processing + started + format: date-time + type: string + type: object + type: object completionTime: description: CompletionTime is the time when a Release was completed format: date-time diff --git a/controllers/release/adapter.go b/controllers/release/adapter.go index 486c3980..14378ec2 100644 --- a/controllers/release/adapter.go +++ b/controllers/release/adapter.go @@ -183,10 +183,147 @@ func (a *adapter) EnsureReleaseIsRunning() (controller.OperationResult, error) { return controller.ContinueProcessing() } +// EnsureManagedCollectorsPipelineIsProcessed is an operation that will ensure that a Managed Collectors Release +// PipelineRun associated to the Release being processed exist. Otherwise, it will be created. +func (a *adapter) EnsureManagedCollectorsPipelineIsProcessed() (controller.OperationResult, error) { + if a.release.HasManagedCollectorsPipelineProcessingFinished() || !a.release.HasTenantCollectorsPipelineProcessingFinished() { + return controller.ContinueProcessing() + } + + pipelineRun, err := a.loader.GetReleasePipelineRun(a.ctx, a.client, a.release, metadata.ManagedCollectorsPipelineType) + if err != nil && !errors.IsNotFound(err) { + return controller.RequeueWithError(err) + } + + if pipelineRun == nil || !a.release.IsManagedCollectorsPipelineProcessing() { + releasePlan, err := a.loader.GetReleasePlan(a.ctx, a.client, a.release) + if err != nil { + return controller.RequeueWithError(err) + } + + var releasePlanAdmission *v1alpha1.ReleasePlanAdmission + if releasePlan.Spec.Target != "" { + releasePlanAdmission, err = a.loader.GetActiveReleasePlanAdmission(a.ctx, a.client, releasePlan) + if err != nil { + return controller.RequeueWithError(err) + } + } + + if releasePlanAdmission == nil || releasePlanAdmission.Spec.Collectors == nil { + patch := client.MergeFrom(a.release.DeepCopy()) + a.release.MarkManagedCollectorsPipelineProcessingSkipped() + return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch)) + } + + if pipelineRun == nil { + pipelineRun, err = a.createManagedCollectorsPipelineRun(releasePlanAdmission) + if err != nil { + return controller.RequeueWithError(err) + } + + a.logger.Info(fmt.Sprintf("Created %s Release PipelineRun", metadata.ManagedCollectorsPipelineType), + "PipelineRun.Name", pipelineRun.Name, "PipelineRun.Namespace", pipelineRun.Namespace) + } + + return controller.RequeueOnErrorOrContinue(a.registerManagedCollectorsProcessingData(pipelineRun)) + } + + return controller.ContinueProcessing() +} + +// EnsureManagedCollectorsPipelineIsTracked is an operation that will ensure that the Release Managed Collectors PipelineRun status +// is tracked in the Release being processed. +func (a *adapter) EnsureManagedCollectorsPipelineIsTracked() (controller.OperationResult, error) { + if !a.release.IsManagedCollectorsPipelineProcessing() || a.release.HasManagedCollectorsPipelineProcessingFinished() { + return controller.ContinueProcessing() + } + + pipelineRun, err := a.loader.GetReleasePipelineRun(a.ctx, a.client, a.release, metadata.ManagedCollectorsPipelineType) + if err != nil { + return controller.RequeueWithError(err) + } + if pipelineRun != nil { + err = a.registerManagedCollectorsProcessingStatus(pipelineRun) + if err != nil { + return controller.RequeueWithError(err) + } + } + + return controller.ContinueProcessing() +} + +// EnsureTenantCollectorsPipelineIsProcessed is an operation that will ensure that a Tenant Collectors Release +// PipelineRun associated to the Release being processed exist. Otherwise, it will be created. +func (a *adapter) EnsureTenantCollectorsPipelineIsProcessed() (controller.OperationResult, error) { + if a.release.HasTenantCollectorsPipelineProcessingFinished() { + return controller.ContinueProcessing() + } + + pipelineRun, err := a.loader.GetReleasePipelineRun(a.ctx, a.client, a.release, metadata.TenantCollectorsPipelineType) + if err != nil && !errors.IsNotFound(err) { + return controller.RequeueWithError(err) + } + + if pipelineRun == nil || !a.release.IsTenantCollectorsPipelineProcessed() { + releasePlan, err := a.loader.GetReleasePlan(a.ctx, a.client, a.release) + if err != nil { + return controller.RequeueWithError(err) + } + if releasePlan.Spec.Collectors == nil { + patch := client.MergeFrom(a.release.DeepCopy()) + a.release.MarkTenantCollectorsPipelineProcessingSkipped() + return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch)) + } + + var releasePlanAdmission *v1alpha1.ReleasePlanAdmission + if releasePlan.Spec.Target != "" { + releasePlanAdmission, err = a.loader.GetActiveReleasePlanAdmission(a.ctx, a.client, releasePlan) + if err != nil { + return controller.RequeueWithError(err) + } + } + + if pipelineRun == nil { + pipelineRun, err = a.createTenantCollectorsPipelineRun(releasePlan, releasePlanAdmission) + if err != nil { + return controller.RequeueWithError(err) + } + + a.logger.Info(fmt.Sprintf("Created %s Release PipelineRun", metadata.TenantCollectorsPipelineType), + "PipelineRun.Name", pipelineRun.Name, "PipelineRun.Namespace", pipelineRun.Namespace) + } + + return controller.RequeueOnErrorOrContinue(a.registerTenantCollectorsProcessingData(pipelineRun)) + } + + return controller.ContinueProcessing() +} + +// EnsureTenantCollectorsPipelineIsTracked is an operation that will ensure that the Release Tenant Collectors PipelineRun status +// is tracked in the Release being processed. +func (a *adapter) EnsureTenantCollectorsPipelineIsTracked() (controller.OperationResult, error) { + if !a.release.IsTenantCollectorsPipelineProcessing() || a.release.HasTenantCollectorsPipelineProcessingFinished() { + return controller.ContinueProcessing() + } + + pipelineRun, err := a.loader.GetReleasePipelineRun(a.ctx, a.client, a.release, metadata.TenantCollectorsPipelineType) + if err != nil { + return controller.RequeueWithError(err) + } + if pipelineRun != nil { + err = a.registerTenantCollectorsProcessingStatus(pipelineRun) + if err != nil { + return controller.RequeueWithError(err) + } + } + + return controller.ContinueProcessing() +} + // EnsureTenantPipelineIsProcessed is an operation that will ensure that a Tenant Release PipelineRun associated to the Release // being processed exist. Otherwise, it will be created. func (a *adapter) EnsureTenantPipelineIsProcessed() (controller.OperationResult, error) { - if a.release.HasTenantPipelineProcessingFinished() { + if a.release.HasTenantPipelineProcessingFinished() || !a.release.HasManagedCollectorsPipelineProcessingFinished() { return controller.ContinueProcessing() } @@ -479,6 +616,135 @@ func (a *adapter) cleanupProcessingResources(pipelineRun *tektonv1.PipelineRun, return nil } +// getCollectorsPipelineRunBuilder generates a builder to use while creating a collectors PipelineRun. +func (a *adapter) getCollectorsPipelineRunBuilder(pipelineType, namespace, revision string) *utils.PipelineRunBuilder { + previousRelease, err := a.loader.GetPreviousRelease(a.ctx, a.client, a.release) + previousReleaseNamespaceName := "" + if err == nil && previousRelease != nil { + previousReleaseNamespaceName = fmt.Sprintf("%s%c%s", + previousRelease.Namespace, types.Separator, previousRelease.Name) + } + + return utils.NewPipelineRunBuilder(pipelineType, namespace). + WithAnnotations(metadata.GetAnnotationsWithPrefix(a.release, integrationgitops.PipelinesAsCodePrefix)). + WithFinalizer(metadata.ReleaseFinalizer). + WithLabels(map[string]string{ + metadata.PipelinesTypeLabel: pipelineType, + metadata.ReleaseNameLabel: a.release.Name, + metadata.ReleaseNamespaceLabel: a.release.Namespace, + }). + WithObjectReferences(a.release). + WithOwner(a.release). + WithParams( + tektonv1.Param{ + Name: "previousRelease", + Value: tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: previousReleaseNamespaceName, + }, + }, + ). + WithPipelineRef((&utils.PipelineRef{ + Resolver: "git", + Params: []utils.Param{ + { + Name: "url", + Value: "https://github.com/konflux-ci/release-service-catalog.git", + }, + { + Name: "revision", + Value: revision, + }, + { + Name: "pathInRepo", + Value: "pipelines/run-collectors/run-collectors.yaml", + }, + }, + }).ToTektonPipelineRef()). + WithWorkspaceFromVolumeTemplate( + os.Getenv("DEFAULT_RELEASE_WORKSPACE_NAME"), + os.Getenv("DEFAULT_RELEASE_WORKSPACE_SIZE"), + ) +} + +// createManagedCollectorsPipelineRun creates a PipelineRun to run the collectors Pipeline for collectors in the ReleasePlanAdmission. +func (a *adapter) createManagedCollectorsPipelineRun(releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*tektonv1.PipelineRun, error) { + revision, err := releasePlanAdmission.Spec.Pipeline.PipelineRef.GetRevision() + if err != nil { + revision = "production" + } + + var pipelineRun *tektonv1.PipelineRun + pipelineRun, err = a.getCollectorsPipelineRunBuilder(metadata.ManagedCollectorsPipelineType, releasePlanAdmission.Namespace, revision). + WithParams( + tektonv1.Param{ + Name: "collectorsResourceType", + Value: tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: "releaseplanadmission", + }, + }, + tektonv1.Param{ + Name: "collectorsResource", + Value: tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: fmt.Sprintf("%s%c%s", + releasePlanAdmission.Namespace, types.Separator, releasePlanAdmission.Name), + }, + }, + ). + WithServiceAccount(releasePlanAdmission.Spec.Collectors.ServiceAccountName). + Build() + + err = a.client.Create(a.ctx, pipelineRun) + if err != nil { + return nil, err + } + + return pipelineRun, nil +} + +// createTenantCollectorsPipelineRun creates a PipelineRun to run the collectors Pipeline for collectors in the ReleasePlan. +func (a *adapter) createTenantCollectorsPipelineRun(releasePlan *v1alpha1.ReleasePlan, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*tektonv1.PipelineRun, error) { + var revision string + var err error + + if releasePlanAdmission != nil { + revision, err = releasePlanAdmission.Spec.Pipeline.PipelineRef.GetRevision() + if err != nil { + revision = "production" + } + } + var pipelineRun *tektonv1.PipelineRun + pipelineRun, err = a.getCollectorsPipelineRunBuilder(metadata.TenantCollectorsPipelineType, releasePlan.Namespace, revision). + WithParams( + tektonv1.Param{ + Name: "collectorsResourceType", + Value: tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: "releaseplan", + }, + }, + tektonv1.Param{ + Name: "collectorsResource", + Value: tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: fmt.Sprintf("%s%c%s", + releasePlan.Namespace, types.Separator, releasePlan.Name), + }, + }, + ). + WithServiceAccount(releasePlan.Spec.Collectors.ServiceAccountName). + Build() + + err = a.client.Create(a.ctx, pipelineRun) + if err != nil { + return nil, err + } + + return pipelineRun, nil +} + // createFinalPipelineRun creates and returns a new Final Release PipelineRun. The new PipelineRun will include owner // annotations, so it triggers Release reconciles whenever it changes. The Pipeline information and the parameters to it // will be extracted from the given ReleasePlan. The Release's Snapshot will also be passed to the release @@ -640,6 +906,42 @@ func (a *adapter) createRoleBindingForClusterRole(clusterRole string, releasePla // EnsureFinalizersAreCalled will remove the finalizers and delete the pipelineRuns. If the pipelineRuns were deleted in // EnsureReleaseProcessingResourcesAreCleanedUp, they could be removed before all the tracking data is saved. func (a *adapter) finalizeRelease(delete bool) error { + // Cleanup Managed Collectors Processing Resources + managedCollectorsPipelineRun, err := a.loader.GetReleasePipelineRun(a.ctx, a.client, a.release, metadata.ManagedCollectorsPipelineType) + if err != nil && !errors.IsNotFound(err) { + return err + } + + err = a.cleanupProcessingResources(managedCollectorsPipelineRun, nil) + if err != nil { + return err + } + + if delete && managedCollectorsPipelineRun != nil { + err = a.client.Delete(a.ctx, managedCollectorsPipelineRun) + if err != nil && !errors.IsNotFound(err) { + return err + } + } + + // Cleanup Tenant Collectors Processing Resources + tenantCollectorsPipelineRun, err := a.loader.GetReleasePipelineRun(a.ctx, a.client, a.release, metadata.TenantCollectorsPipelineType) + if err != nil && !errors.IsNotFound(err) { + return err + } + + err = a.cleanupProcessingResources(tenantCollectorsPipelineRun, nil) + if err != nil { + return err + } + + if delete && tenantCollectorsPipelineRun != nil { + err = a.client.Delete(a.ctx, tenantCollectorsPipelineRun) + if err != nil && !errors.IsNotFound(err) { + return err + } + } + // Cleanup Tenant Processing Resources tenantPipelineRun, err := a.loader.GetReleasePipelineRun(a.ctx, a.client, a.release, metadata.TenantPipelineType) if err != nil && !errors.IsNotFound(err) { @@ -716,6 +1018,25 @@ func (a *adapter) getEmptyReleaseServiceConfig(namespace string) *v1alpha1.Relea return releaseServiceConfig } +// registerManagedCollectorsProcessingData adds all the Release Tenant Collectors processing information to its Status +// and marks it as tenant collectors processing. +func (a *adapter) registerTenantCollectorsProcessingData(releasePipelineRun *tektonv1.PipelineRun) error { + if releasePipelineRun == nil { + return nil + } + + patch := client.MergeFrom(a.release.DeepCopy()) + + a.release.Status.CollectorsProcessing.TenantCollectorsProcessing.PipelineRun = fmt.Sprintf("%s%c%s", + releasePipelineRun.Namespace, types.Separator, releasePipelineRun.Name) + + a.release.MarkTenantCollectorsPipelineProcessing() + + e := a.client.Status().Patch(a.ctx, a.release, patch) + + return e +} + // registerTenantProcessingData adds all the Release Tenant processing information to its Status and marks it as tenant processing. func (a *adapter) registerTenantProcessingData(releasePipelineRun *tektonv1.PipelineRun) error { if releasePipelineRun == nil { @@ -748,6 +1069,23 @@ func (a *adapter) registerFinalProcessingData(releasePipelineRun *tektonv1.Pipel return a.client.Status().Patch(a.ctx, a.release, patch) } +// registerManagedCollectorsProcessingData adds all the Release Managed Collectors processing information to its Status +// and marks it as managed collectors processing. +func (a *adapter) registerManagedCollectorsProcessingData(releasePipelineRun *tektonv1.PipelineRun) error { + if releasePipelineRun == nil { + return nil + } + + patch := client.MergeFrom(a.release.DeepCopy()) + + a.release.Status.CollectorsProcessing.ManagedCollectorsProcessing.PipelineRun = fmt.Sprintf("%s%c%s", + releasePipelineRun.Namespace, types.Separator, releasePipelineRun.Name) + + a.release.MarkManagedCollectorsPipelineProcessing() + + return a.client.Status().Patch(a.ctx, a.release, patch) +} + // registerProcessingData adds all the Release Managed processing information to its Status and marks it as managed processing. func (a *adapter) registerManagedProcessingData(releasePipelineRun *tektonv1.PipelineRun, roleBinding *rbac.RoleBinding) error { if releasePipelineRun == nil { @@ -768,6 +1106,27 @@ func (a *adapter) registerManagedProcessingData(releasePipelineRun *tektonv1.Pip return a.client.Status().Patch(a.ctx, a.release, patch) } +// registerTenantCollectorsProcessingStatus updates the status of the Release being processed by monitoring the status of the +// associated tenant collectors Release PipelineRun and setting the appropriate state in the Release. If the PipelineRun hasn't +// started/succeeded, no action will be taken. +func (a *adapter) registerTenantCollectorsProcessingStatus(pipelineRun *tektonv1.PipelineRun) error { + if pipelineRun == nil || !pipelineRun.IsDone() { + return nil + } + + patch := client.MergeFrom(a.release.DeepCopy()) + + condition := pipelineRun.Status.GetCondition(apis.ConditionSucceeded) + if condition.IsTrue() { + a.release.MarkTenantCollectorsPipelineProcessed() + } else { + a.release.MarkTenantCollectorsPipelineProcessingFailed(condition.Message) + a.release.MarkReleaseFailed("Release processing failed on tenant collectors pipelineRun") + } + + return a.client.Status().Patch(a.ctx, a.release, patch) +} + // registerTenantProcessingStatus updates the status of the Release being processed by monitoring the status of the // associated tenant Release PipelineRun and setting the appropriate state in the Release. If the PipelineRun hasn't // started/succeeded, no action will be taken. @@ -790,6 +1149,27 @@ func (a *adapter) registerTenantProcessingStatus(pipelineRun *tektonv1.PipelineR return a.client.Status().Patch(a.ctx, a.release, patch) } +// registerManagedCollectorsProcessingStatus updates the status of the Release being processed by monitoring the status of the +// associated managed collectors Release PipelineRun and setting the appropriate state in the Release. If the PipelineRun hasn't +// started/succeeded, no action will be taken. +func (a *adapter) registerManagedCollectorsProcessingStatus(pipelineRun *tektonv1.PipelineRun) error { + if pipelineRun == nil || !pipelineRun.IsDone() { + return nil + } + + patch := client.MergeFrom(a.release.DeepCopy()) + + condition := pipelineRun.Status.GetCondition(apis.ConditionSucceeded) + if condition.IsTrue() { + a.release.MarkManagedCollectorsPipelineProcessed() + } else { + a.release.MarkManagedCollectorsPipelineProcessingFailed(condition.Message) + a.release.MarkReleaseFailed("Release processing failed on managed collectors pipelineRun") + } + + return a.client.Status().Patch(a.ctx, a.release, patch) +} + // registerManagedProcessingStatus updates the status of the Release being processed by monitoring the status of the // associated managed Release PipelineRun and setting the appropriate state in the Release. If the PipelineRun hasn't // started/succeeded, no action will be taken. diff --git a/controllers/release/adapter_test.go b/controllers/release/adapter_test.go index 2cb14bd8..3b2e5590 100644 --- a/controllers/release/adapter_test.go +++ b/controllers/release/adapter_test.go @@ -490,6 +490,157 @@ var _ = Describe("Release adapter", Ordered, func() { }) + When("EnsureManagedCollectorsPipelineIsProcessed is called", func() { + var adapter *adapter + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + adapter.releaseServiceConfig = releaseServiceConfig + }) + + It("should do nothing if the Release managed collectors pipeline is complete", func() { + adapter.release.MarkManagedCollectorsPipelineProcessing() + adapter.release.MarkManagedCollectorsPipelineProcessed() + + result, err := adapter.EnsureManagedCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should do nothing if the Release tenant collectors pipeline processing has not yet completed", func() { + adapter.release.MarkTenantCollectorsPipelineProcessing() + + result, err := adapter.EnsureManagedCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsFinalPipelineProcessing()).To(BeFalse()) + }) + + It("should requeue with error if fetching the Release managed collectors pipeline returns an error besides not found", func() { + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePipelineRunContextKey, + Err: fmt.Errorf("some error"), + }, + }) + + adapter.release.MarkTenantCollectorsPipelineProcessingSkipped() + result, err := adapter.EnsureManagedCollectorsPipelineIsProcessed() + Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).To(HaveOccurred()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should continue and mark tenant collectors processing as skipped if the ReleasePlanAdmission have no collectors defined", func() { + newReleasePlan := releasePlan.DeepCopy() + newReleasePlan.Spec.Target = releasePlanAdmission.Namespace + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePlanContextKey, + Resource: newReleasePlan, + }, + { + ContextKey: loader.ReleasePlanAdmissionContextKey, + Resource: releasePlanAdmission, + }, + }) + + adapter.release.MarkTenantCollectorsPipelineProcessingSkipped() + result, err := adapter.EnsureManagedCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessing()).To(BeFalse()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessed()).To(BeTrue()) + }) + + It("should register the processing data if the PipelineRun already exists", func() { + newReleasePlanAdmission := releasePlanAdmission.DeepCopy() + newReleasePlanAdmission.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePipelineRunContextKey, + Resource: &tektonv1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pipeline-run", + Namespace: "default", + }, + }, + }, + { + ContextKey: loader.ReleasePlanAdmissionContextKey, + Resource: newReleasePlanAdmission, + }, + }) + + adapter.release.MarkTenantCollectorsPipelineProcessingSkipped() + adapter.release.MarkManagedCollectorsPipelineProcessing() + result, err := adapter.EnsureManagedCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessing()).To(BeTrue()) + }) + + It("should requeue the Release if any of the resources is not found", func() { + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePlanContextKey, + Err: errors.NewNotFound(schema.GroupResource{}, ""), + Resource: nil, + }, + }) + + adapter.release.MarkTenantCollectorsPipelineProcessingSkipped() + result, err := adapter.EnsureManagedCollectorsPipelineIsProcessed() + Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).To(HaveOccurred()) + }) + + It("should create a pipelineRun and register the processing data if all the required resources are present", func() { + newReleasePlanAdmission := releasePlanAdmission.DeepCopy() + newReleasePlanAdmission.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePlanAdmissionContextKey, + Resource: newReleasePlanAdmission, + }, + }) + + adapter.release.MarkTenantCollectorsPipelineProcessingSkipped() + result, err := adapter.EnsureManagedCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessing()).To(BeTrue()) + + pipelineRun, err := adapter.loader.GetReleasePipelineRun(adapter.ctx, adapter.client, adapter.release, metadata.ManagedCollectorsPipelineType) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.client.Delete(adapter.ctx, pipelineRun)).To(Succeed()) + }) + }) + When("EnsureManagedPipelineIsProcessed is called", func() { var adapter *adapter @@ -818,6 +969,137 @@ var _ = Describe("Release adapter", Ordered, func() { }) }) + When("EnsureTenantCollectorsPipelineIsProcessed is called", func() { + var adapter *adapter + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + adapter.releaseServiceConfig = releaseServiceConfig + }) + + It("should do nothing if the Release tenant collectors pipeline is complete", func() { + adapter.release.MarkTenantCollectorsPipelineProcessing() + adapter.release.MarkTenantCollectorsPipelineProcessed() + + result, err := adapter.EnsureTenantCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should requeue with error if fetching the Release tenant collectors pipeline returns an error besides not found", func() { + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePipelineRunContextKey, + Err: fmt.Errorf("some error"), + }, + }) + + result, err := adapter.EnsureTenantCollectorsPipelineIsProcessed() + Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).To(HaveOccurred()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + }) + + It("should continue and mark tenant collectors processing as skipped if the ReleasePlan have no collectors defined", func() { + result, err := adapter.EnsureTenantCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessing()).To(BeFalse()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessed()).To(BeTrue()) + }) + + It("should register the processing data if the PipelineRun already exists", func() { + newReleasePlan := releasePlan.DeepCopy() + newReleasePlan.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePipelineRunContextKey, + Resource: &tektonv1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pipeline-run", + Namespace: "default", + }, + }, + }, + { + ContextKey: loader.ReleasePlanContextKey, + Resource: newReleasePlan, + }, + { + ContextKey: loader.ReleasePlanAdmissionContextKey, + Resource: releasePlanAdmission, + }, + }) + + result, err := adapter.EnsureTenantCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessing()).To(BeTrue()) + }) + + It("should requeue the Release if any of the resources is not found", func() { + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePlanContextKey, + Err: errors.NewNotFound(schema.GroupResource{}, ""), + Resource: nil, + }, + }) + + result, err := adapter.EnsureTenantCollectorsPipelineIsProcessed() + Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).To(HaveOccurred()) + }) + + It("should create a pipelineRun and register the processing data if all the required resources are present", func() { + newReleasePlan := releasePlan.DeepCopy() + newReleasePlan.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + { + ContextKey: loader.ReleasePlanContextKey, + Resource: newReleasePlan, + }, + { + ContextKey: loader.ReleasePlanAdmissionContextKey, + Resource: releasePlanAdmission, + }, + }) + + result, err := adapter.EnsureTenantCollectorsPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessing()).To(BeTrue()) + + pipelineRun, err := adapter.loader.GetReleasePipelineRun(adapter.ctx, adapter.client, adapter.release, metadata.TenantCollectorsPipelineType) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.client.Delete(adapter.ctx, pipelineRun)).To(Succeed()) + }) + }) + When("EnsureTenantPipelineIsProcessed is called", func() { var adapter *adapter @@ -840,6 +1122,15 @@ var _ = Describe("Release adapter", Ordered, func() { Expect(adapter.release.IsTenantPipelineProcessing()).To(BeFalse()) }) + It("should do nothing if the Release managed collectors pipeline processing has not yet completed", func() { + adapter.release.MarkManagedCollectorsPipelineProcessing() + + result, err := adapter.EnsureTenantPipelineIsProcessed() + Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + Expect(adapter.release.IsFinalPipelineProcessing()).To(BeFalse()) + }) + It("should requeue with error if fetching the Release managed pipeline returns an error besides not found", func() { adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ { @@ -848,6 +1139,7 @@ var _ = Describe("Release adapter", Ordered, func() { }, }) + adapter.release.MarkManagedCollectorsPipelineProcessingSkipped() result, err := adapter.EnsureTenantPipelineIsProcessed() Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue()) Expect(err).To(HaveOccurred()) @@ -866,6 +1158,7 @@ var _ = Describe("Release adapter", Ordered, func() { }, }) + adapter.release.MarkManagedCollectorsPipelineProcessingSkipped() result, err := adapter.EnsureTenantPipelineIsProcessed() Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) Expect(err).NotTo(HaveOccurred()) @@ -885,6 +1178,7 @@ var _ = Describe("Release adapter", Ordered, func() { }, }, }) + adapter.release.MarkManagedCollectorsPipelineProcessingSkipped() adapter.release.MarkTenantPipelineProcessing() result, err := adapter.EnsureTenantPipelineIsProcessed() @@ -902,6 +1196,7 @@ var _ = Describe("Release adapter", Ordered, func() { }, }) + adapter.release.MarkManagedCollectorsPipelineProcessingSkipped() result, err := adapter.EnsureTenantPipelineIsProcessed() Expect(result.RequeueRequest && !result.CancelRequest).To(BeTrue()) Expect(err).To(HaveOccurred()) @@ -952,6 +1247,7 @@ var _ = Describe("Release adapter", Ordered, func() { }, }) + adapter.release.MarkManagedCollectorsPipelineProcessingSkipped() result, err := adapter.EnsureTenantPipelineIsProcessed() Expect(!result.RequeueRequest && !result.CancelRequest).To(BeTrue()) Expect(err).NotTo(HaveOccurred()) @@ -1449,11 +1745,10 @@ var _ = Describe("Release adapter", Ordered, func() { }) }) - When("createTenantPipelineRun is called", func() { + When("createManagedCollectorsPipelineRun is called", func() { var ( - adapter *adapter - pipelineRun *tektonv1.PipelineRun - newReleasePlan *v1alpha1.ReleasePlan + adapter *adapter + pipelineRun *tektonv1.PipelineRun ) AfterEach(func() { @@ -1465,37 +1760,206 @@ var _ = Describe("Release adapter", Ordered, func() { BeforeEach(func() { adapter = createReleaseAndAdapter() - parameterizedPipeline := tektonutils.ParameterizedPipeline{} - parameterizedPipeline.PipelineRef = tektonutils.PipelineRef{ - Resolver: "git", - Params: []tektonutils.Param{ - {Name: "url", Value: "my-url"}, - {Name: "revision", Value: "my-revision"}, - {Name: "pathInRepo", Value: "my-path"}, + newReleasePlanAdmission := releasePlanAdmission.DeepCopy() + newReleasePlanAdmission.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, }, } - parameterizedPipeline.Params = []tektonutils.Param{ - {Name: "parameter1", Value: "value1"}, - {Name: "parameter2", Value: "value2"}, - } - parameterizedPipeline.Timeouts = tektonv1.TimeoutFields{ - Pipeline: &metav1.Duration{Duration: 1 * time.Hour}, - } - newReleasePlan = &v1alpha1.ReleasePlan{ - ObjectMeta: metav1.ObjectMeta{ - Name: "release-plan", - Namespace: "default", - }, - Spec: v1alpha1.ReleasePlanSpec{ - Application: application.Name, - TenantPipeline: ¶meterizedPipeline, - ReleaseGracePeriodDays: 6, - }, - } - newReleasePlan.Kind = "ReleasePlan" + var err error + pipelineRun, err = adapter.createManagedCollectorsPipelineRun(newReleasePlanAdmission) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + }) - adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ + It("returns a PipelineRun with the right prefix", func() { + Expect(reflect.TypeOf(pipelineRun)).To(Equal(reflect.TypeOf(&tektonv1.PipelineRun{}))) + Expect(pipelineRun.Name).To(HavePrefix(metadata.ManagedCollectorsPipelineType)) + }) + + It("has the release reference", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", strings.ToLower(adapter.release.Kind)))) + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", + fmt.Sprintf("%s%c%s", adapter.release.Namespace, types.Separator, adapter.release.Name)))) + }) + + It("has owner annotations", func() { + Expect(pipelineRun.GetAnnotations()[handler.NamespacedNameAnnotation]).To(ContainSubstring(adapter.release.Name)) + Expect(pipelineRun.GetAnnotations()[handler.TypeAnnotation]).To(ContainSubstring("Release")) + }) + + It("has release labels", func() { + Expect(pipelineRun.GetLabels()[metadata.PipelinesTypeLabel]).To(Equal(metadata.ManagedCollectorsPipelineType)) + Expect(pipelineRun.GetLabels()[metadata.ReleaseNameLabel]).To(Equal(adapter.release.Name)) + Expect(pipelineRun.GetLabels()[metadata.ReleaseNamespaceLabel]).To(Equal(testNamespace)) + }) + + It("contains a parameter with the collectorsResourceType", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", "taskGitUrl"))) + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", "releaseplanadmission"))) + }) + + It("contains a parameter with the taskGitUrl", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", "taskGitUrl"))) + var url string + resolverParams := pipelineRun.Spec.PipelineRef.ResolverRef.Params + for i := range resolverParams { + if resolverParams[i].Name == "url" { + url = resolverParams[i].Value.StringVal + } + } + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", url))) + }) + + It("contains a parameter with the taskGitRevision", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", "taskGitRevision"))) + var revision string + resolverParams := pipelineRun.Spec.PipelineRef.ResolverRef.Params + for i := range resolverParams { + if resolverParams[i].Name == "revision" { + revision = resolverParams[i].Value.StringVal + } + } + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", revision))) + }) + }) + + When("createTenantCollectorsPipelineRun is called", func() { + var ( + adapter *adapter + pipelineRun *tektonv1.PipelineRun + ) + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + + Expect(k8sClient.Delete(ctx, pipelineRun)).To(Succeed()) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + + newReleasePlan := releasePlan.DeepCopy() + newReleasePlan.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + + var err error + pipelineRun, err = adapter.createTenantCollectorsPipelineRun(newReleasePlan, releasePlanAdmission) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("returns a PipelineRun with the right prefix", func() { + Expect(reflect.TypeOf(pipelineRun)).To(Equal(reflect.TypeOf(&tektonv1.PipelineRun{}))) + Expect(pipelineRun.Name).To(HavePrefix(metadata.TenantCollectorsPipelineType)) + }) + + It("has the release reference", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", strings.ToLower(adapter.release.Kind)))) + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", + fmt.Sprintf("%s%c%s", adapter.release.Namespace, types.Separator, adapter.release.Name)))) + }) + + It("has owner annotations", func() { + Expect(pipelineRun.GetAnnotations()[handler.NamespacedNameAnnotation]).To(ContainSubstring(adapter.release.Name)) + Expect(pipelineRun.GetAnnotations()[handler.TypeAnnotation]).To(ContainSubstring("Release")) + }) + + It("has release labels", func() { + Expect(pipelineRun.GetLabels()[metadata.PipelinesTypeLabel]).To(Equal(metadata.TenantCollectorsPipelineType)) + Expect(pipelineRun.GetLabels()[metadata.ReleaseNameLabel]).To(Equal(adapter.release.Name)) + Expect(pipelineRun.GetLabels()[metadata.ReleaseNamespaceLabel]).To(Equal(testNamespace)) + }) + + It("contains a parameter with the collectorsResourceType", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", "taskGitUrl"))) + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", "releaseplan"))) + }) + + It("contains a parameter with the taskGitUrl", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", "taskGitUrl"))) + var url string + resolverParams := pipelineRun.Spec.PipelineRef.ResolverRef.Params + for i := range resolverParams { + if resolverParams[i].Name == "url" { + url = resolverParams[i].Value.StringVal + } + } + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", url))) + }) + + It("contains a parameter with the taskGitRevision", func() { + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Name", "taskGitRevision"))) + var revision string + resolverParams := pipelineRun.Spec.PipelineRef.ResolverRef.Params + for i := range resolverParams { + if resolverParams[i].Name == "revision" { + revision = resolverParams[i].Value.StringVal + } + } + Expect(pipelineRun.Spec.Params).Should(ContainElement(HaveField("Value.StringVal", revision))) + }) + }) + + When("createTenantPipelineRun is called", func() { + var ( + adapter *adapter + pipelineRun *tektonv1.PipelineRun + newReleasePlan *v1alpha1.ReleasePlan + ) + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + + Expect(k8sClient.Delete(ctx, pipelineRun)).To(Succeed()) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + + parameterizedPipeline := tektonutils.ParameterizedPipeline{} + parameterizedPipeline.PipelineRef = tektonutils.PipelineRef{ + Resolver: "git", + Params: []tektonutils.Param{ + {Name: "url", Value: "my-url"}, + {Name: "revision", Value: "my-revision"}, + {Name: "pathInRepo", Value: "my-path"}, + }, + } + parameterizedPipeline.Params = []tektonutils.Param{ + {Name: "parameter1", Value: "value1"}, + {Name: "parameter2", Value: "value2"}, + } + parameterizedPipeline.Timeouts = tektonv1.TimeoutFields{ + Pipeline: &metav1.Duration{Duration: 1 * time.Hour}, + } + + newReleasePlan = &v1alpha1.ReleasePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release-plan", + Namespace: "default", + }, + Spec: v1alpha1.ReleasePlanSpec{ + Application: application.Name, + TenantPipeline: ¶meterizedPipeline, + ReleaseGracePeriodDays: 6, + }, + } + newReleasePlan.Kind = "ReleasePlan" + + adapter.ctx = toolkit.GetMockedContext(ctx, []toolkit.MockData{ { ContextKey: loader.ReleasePlanContextKey, Resource: newReleasePlan, @@ -1935,6 +2399,52 @@ var _ = Describe("Release adapter", Ordered, func() { newReleasePlan.Kind = "ReleasePlan" }) + It("finalizes the Release and removes the finalizer from the Managed Collector PipelineRun when called with false", func() { + newReleasePlanAdmission := releasePlanAdmission.DeepCopy() + newReleasePlanAdmission.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + pipelineRun, err := adapter.createManagedCollectorsPipelineRun(newReleasePlanAdmission) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + + Expect(adapter.finalizeRelease(false)).To(Succeed()) + pipelineRun, err = adapter.loader.GetReleasePipelineRun(adapter.ctx, adapter.client, adapter.release, metadata.ManagedCollectorsPipelineType) + Expect(err).NotTo(HaveOccurred()) + Expect(pipelineRun).NotTo(BeNil()) + Expect(pipelineRun.Finalizers).To(HaveLen(0)) + Expect(k8sClient.Delete(ctx, pipelineRun)).To(Succeed()) + }) + + It("finalizes the Release and removes the finalizer from the Tenant Collector PipelineRun when called with false", func() { + newReleasePlan := releasePlan.DeepCopy() + newReleasePlan.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + pipelineRun, err := adapter.createTenantCollectorsPipelineRun(newReleasePlan, releasePlanAdmission) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + + Expect(adapter.finalizeRelease(false)).To(Succeed()) + pipelineRun, err = adapter.loader.GetReleasePipelineRun(adapter.ctx, adapter.client, adapter.release, metadata.TenantCollectorsPipelineType) + Expect(err).NotTo(HaveOccurred()) + Expect(pipelineRun).NotTo(BeNil()) + Expect(pipelineRun.Finalizers).To(HaveLen(0)) + Expect(k8sClient.Delete(ctx, pipelineRun)).To(Succeed()) + }) + It("finalizes the Release and removes the finalizer from the Tenant PipelineRun when called with false", func() { pipelineRun, err := adapter.createTenantPipelineRun(newReleasePlan, snapshot) Expect(pipelineRun).NotTo(BeNil()) @@ -1982,6 +2492,48 @@ var _ = Describe("Release adapter", Ordered, func() { Expect(k8sClient.Delete(ctx, pipelineRun)).To(Succeed()) }) + It("finalizes the Release and deletes the Managed Collectors PipelineRun when called with true", func() { + newReleasePlanAdmission := releasePlanAdmission.DeepCopy() + newReleasePlanAdmission.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + pipelineRun, err := adapter.createManagedCollectorsPipelineRun(newReleasePlanAdmission) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + + Expect(adapter.finalizeRelease(true)).To(Succeed()) + pipelineRun, err = adapter.loader.GetReleasePipelineRun(adapter.ctx, adapter.client, adapter.release, metadata.ManagedCollectorsPipelineType) + Expect(err).NotTo(HaveOccurred()) + Expect(pipelineRun).To(BeNil()) + }) + + It("finalizes the Release and deletes the Tenant Collectors PipelineRun when called with true", func() { + newReleasePlan := releasePlan.DeepCopy() + newReleasePlan.Spec.Collectors = &v1alpha1.Collectors{ + Items: []v1alpha1.CollectorItem{ + { + Name: "foo", + Type: "bar", + Params: []v1alpha1.Param{}, + }, + }, + } + pipelineRun, err := adapter.createTenantCollectorsPipelineRun(newReleasePlan, releasePlanAdmission) + Expect(pipelineRun).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + + Expect(adapter.finalizeRelease(true)).To(Succeed()) + pipelineRun, err = adapter.loader.GetReleasePipelineRun(adapter.ctx, adapter.client, adapter.release, metadata.TenantCollectorsPipelineType) + Expect(err).NotTo(HaveOccurred()) + Expect(pipelineRun).To(BeNil()) + }) + It("finalizes the Release and deletes the Tenant PipelineRun when called with true", func() { pipelineRun, err := adapter.createTenantPipelineRun(newReleasePlan, snapshot) Expect(pipelineRun).NotTo(BeNil()) @@ -2044,6 +2596,66 @@ var _ = Describe("Release adapter", Ordered, func() { }) }) + When("registerManagedCollectorsProcessingData is called", func() { + var adapter *adapter + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + }) + + It("does nothing if there is no PipelineRun", func() { + Expect(adapter.registerManagedCollectorsProcessingData(nil)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.ManagedCollectorsProcessing.PipelineRun).To(BeEmpty()) + }) + + It("registers the Release managed collectors processing data", func() { + pipelineRun := &tektonv1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pipeline-run", + Namespace: "default", + }, + } + Expect(adapter.registerManagedCollectorsProcessingData(pipelineRun)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.ManagedCollectorsProcessing.PipelineRun).To(Equal(fmt.Sprintf("%s%c%s", + pipelineRun.Namespace, types.Separator, pipelineRun.Name))) + Expect(adapter.release.IsManagedCollectorsPipelineProcessing()).To(BeTrue()) + }) + }) + + When("registerTenantCollectorsProcessingData is called", func() { + var adapter *adapter + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + }) + + It("does nothing if there is no PipelineRun", func() { + Expect(adapter.registerTenantCollectorsProcessingData(nil)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.TenantCollectorsProcessing.PipelineRun).To(BeEmpty()) + }) + + It("registers the Release tenant collectors processing data", func() { + pipelineRun := &tektonv1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pipeline-run", + Namespace: "default", + }, + } + Expect(adapter.registerTenantCollectorsProcessingData(pipelineRun)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.TenantCollectorsProcessing.PipelineRun).To(Equal(fmt.Sprintf("%s%c%s", + pipelineRun.Namespace, types.Separator, pipelineRun.Name))) + Expect(adapter.release.IsTenantCollectorsPipelineProcessing()).To(BeTrue()) + }) + }) + When("registerTenantProcessingData is called", func() { var adapter *adapter @@ -2155,6 +2767,90 @@ var _ = Describe("Release adapter", Ordered, func() { }) }) + When("registerManagedCollectorsProcessingStatus is called", func() { + var adapter *adapter + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + }) + + It("does nothing if there is no PipelineRun", func() { + Expect(adapter.registerManagedCollectorsProcessingStatus(nil)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("does nothing if the PipelineRun is not done", func() { + pipelineRun := &tektonv1.PipelineRun{} + Expect(adapter.registerManagedCollectorsProcessingStatus(pipelineRun)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.ManagedCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("sets the Release as Managed Collectors Processed if the PipelineRun succeeded", func() { + pipelineRun := &tektonv1.PipelineRun{} + pipelineRun.Status.MarkSucceeded("", "") + adapter.release.MarkManagedCollectorsPipelineProcessing() + + Expect(adapter.registerManagedCollectorsProcessingStatus(pipelineRun)).To(Succeed()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessed()).To(BeTrue()) + }) + + It("sets the Release as ManagedCollectors Processing failed if the PipelineRun didn't succeed", func() { + pipelineRun := &tektonv1.PipelineRun{} + pipelineRun.Status.MarkFailed("", "") + adapter.release.MarkManagedCollectorsPipelineProcessing() + + Expect(adapter.registerManagedCollectorsProcessingStatus(pipelineRun)).To(Succeed()) + Expect(adapter.release.HasManagedCollectorsPipelineProcessingFinished()).To(BeTrue()) + Expect(adapter.release.IsManagedCollectorsPipelineProcessed()).To(BeFalse()) + }) + }) + + When("registerTenantCollectorsProcessingStatus is called", func() { + var adapter *adapter + + AfterEach(func() { + _ = adapter.client.Delete(ctx, adapter.release) + }) + + BeforeEach(func() { + adapter = createReleaseAndAdapter() + }) + + It("does nothing if there is no PipelineRun", func() { + Expect(adapter.registerTenantCollectorsProcessingStatus(nil)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("does nothing if the PipelineRun is not done", func() { + pipelineRun := &tektonv1.PipelineRun{} + Expect(adapter.registerTenantCollectorsProcessingStatus(pipelineRun)).To(Succeed()) + Expect(adapter.release.Status.CollectorsProcessing.TenantCollectorsProcessing.CompletionTime).To(BeNil()) + }) + + It("sets the Release as Tenant Collectors Processed if the PipelineRun succeeded", func() { + pipelineRun := &tektonv1.PipelineRun{} + pipelineRun.Status.MarkSucceeded("", "") + adapter.release.MarkTenantCollectorsPipelineProcessing() + + Expect(adapter.registerTenantCollectorsProcessingStatus(pipelineRun)).To(Succeed()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessed()).To(BeTrue()) + }) + + It("sets the Release as Tenant Collectors Processing failed if the PipelineRun didn't succeed", func() { + pipelineRun := &tektonv1.PipelineRun{} + pipelineRun.Status.MarkFailed("", "") + adapter.release.MarkTenantCollectorsPipelineProcessing() + + Expect(adapter.registerTenantCollectorsProcessingStatus(pipelineRun)).To(Succeed()) + Expect(adapter.release.HasTenantCollectorsPipelineProcessingFinished()).To(BeTrue()) + Expect(adapter.release.IsTenantCollectorsPipelineProcessed()).To(BeFalse()) + }) + }) + When("registerTenantProcessingStatus is called", func() { var adapter *adapter diff --git a/controllers/release/controller.go b/controllers/release/controller.go index 424f127b..960987d9 100644 --- a/controllers/release/controller.go +++ b/controllers/release/controller.go @@ -81,6 +81,10 @@ func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu adapter.EnsureReleaseIsValid, adapter.EnsureFinalizerIsAdded, adapter.EnsureReleaseExpirationTimeIsAdded, + adapter.EnsureTenantCollectorsPipelineIsProcessed, + adapter.EnsureTenantCollectorsPipelineIsTracked, + adapter.EnsureManagedCollectorsPipelineIsProcessed, + adapter.EnsureManagedCollectorsPipelineIsTracked, adapter.EnsureTenantPipelineIsProcessed, adapter.EnsureTenantPipelineProcessingIsTracked, adapter.EnsureManagedPipelineIsProcessed, diff --git a/loader/loader.go b/loader/loader.go index 19a81e51..10f55253 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -254,7 +254,8 @@ func (l *loader) GetRoleBindingFromReleaseStatus(ctx context.Context, cli client // GetReleasePipelineRun returns the Release PipelineRun of the specified type referenced by the given Release // or nil if it's not found. In the case the List operation fails, an error will be returned. func (l *loader) GetReleasePipelineRun(ctx context.Context, cli client.Client, release *v1alpha1.Release, pipelineType string) (*tektonv1.PipelineRun, error) { - if pipelineType != metadata.ManagedPipelineType && pipelineType != metadata.TenantPipelineType && pipelineType != metadata.FinalPipelineType { + if pipelineType != metadata.ManagedCollectorsPipelineType && pipelineType != metadata.ManagedPipelineType && + pipelineType != metadata.TenantCollectorsPipelineType && pipelineType != metadata.TenantPipelineType && pipelineType != metadata.FinalPipelineType { return nil, fmt.Errorf("cannot fetch Release PipelineRun with invalid type %s", pipelineType) } diff --git a/metadata/labels.go b/metadata/labels.go index 0f7c272d..9f8d5842 100644 --- a/metadata/labels.go +++ b/metadata/labels.go @@ -59,6 +59,12 @@ var ( // ApplicationNameLabel is the label used to specify the application associated with the PipelineRun ApplicationNameLabel = fmt.Sprintf("%s/%s", rhtapDomain, "application") + // ManagedCollectorsPipelineType is the value to be used in the PipelinesTypeLabel for managed collector Pipelines + ManagedCollectorsPipelineType = "managed-collectors" + + // TenantCollectorsPipelineType is the value to be used in the PipelinesTypeLabel for tenant collector Pipelines + TenantCollectorsPipelineType = "tenant-collectors" + // FinalPipelineType is the value to be used in the PipelinesTypeLabel for final Pipelines FinalPipelineType = "final" diff --git a/metrics/release.go b/metrics/release.go index 99467443..2ab57424 100644 --- a/metrics/release.go +++ b/metrics/release.go @@ -75,9 +75,11 @@ var ( // Prometheus fails if these are not in alphabetical order releaseDurationSecondsLabels = []string{ "final_pipeline_processing_reason", + "managed_collectors_pipeline_processing_reason", "managed_pipeline_processing_reason", "release_reason", "target", + "tenant_collectors_pipeline_processing_reason", "tenant_pipeline_processing_reason", "validation_reason", } @@ -109,9 +111,11 @@ var ( // Prometheus fails if these are not in alphabetical order releaseTotalLabels = []string{ "final_pipeline_processing_reason", + "managed_collectors_pipeline_processing_reason", "managed_pipeline_processing_reason", "release_reason", "target", + "tenant_collectors_pipeline_processing_reason", "tenant_pipeline_processing_reason", "validation_reason", } @@ -125,19 +129,22 @@ var ( // observation for the Release duration and increasing the total number of releases. If either the startTime or the // completionTime parameters are nil, no action will be taken. func RegisterCompletedRelease(startTime, completionTime *metav1.Time, - finalProcessingReason, managedProcessingReason, releaseReason, target, tenantProcessingReason, validationReason string) { + finalProcessingReason, managedCollectorsProcessingReason, managedProcessingReason, releaseReason, target, + tenantCollectorsProcessingReason, tenantProcessingReason, validationReason string) { if startTime == nil || completionTime == nil { return } // Prometheus fails if these are not in alphabetical order labels := prometheus.Labels{ - "final_pipeline_processing_reason": finalProcessingReason, - "managed_pipeline_processing_reason": managedProcessingReason, - "release_reason": releaseReason, - "target": target, - "tenant_pipeline_processing_reason": tenantProcessingReason, - "validation_reason": validationReason, + "final_pipeline_processing_reason": finalProcessingReason, + "managed_collectors_pipeline_processing_reason": managedCollectorsProcessingReason, + "managed_pipeline_processing_reason": managedProcessingReason, + "release_reason": releaseReason, + "target": target, + "tenant_collectors_pipeline_processing_reason": tenantCollectorsProcessingReason, + "tenant_pipeline_processing_reason": tenantProcessingReason, + "validation_reason": validationReason, } ReleaseConcurrentTotal.WithLabelValues().Dec() ReleaseDurationSeconds. @@ -185,7 +192,7 @@ func RegisterNewRelease() { ReleaseConcurrentTotal.WithLabelValues().Inc() } -// RegisterNewReleaseManagedPipelineProcessing registers a new Release Pipeline processing, adding a +// RegisterNewReleasePipelineProcessing registers a new Release Pipeline processing, adding a // new observation for the Release start pipeline processing duration and increasing the number of // concurrent processings. If either the startTime or the processingStartTime are nil, no action will be taken. func RegisterNewReleasePipelineProcessing(startTime, processingStartTime *metav1.Time, reason, target, pipelineType string) { diff --git a/metrics/release_test.go b/metrics/release_test.go index a6289d5b..b9f9b80f 100644 --- a/metrics/release_test.go +++ b/metrics/release_test.go @@ -43,19 +43,19 @@ var _ = Describe("Release metrics", Ordered, func() { It("does nothing if the start time is nil", func() { Expect(testutil.ToFloat64(ReleaseConcurrentTotal.WithLabelValues())).To(Equal(float64(0))) - RegisterCompletedRelease(nil, completionTime, "", "", "", "", "", "") + RegisterCompletedRelease(nil, completionTime, "", "", "", "", "", "", "", "") Expect(testutil.ToFloat64(ReleaseConcurrentTotal.WithLabelValues())).To(Equal(float64(0))) }) It("does nothing if the completion time is nil", func() { Expect(testutil.ToFloat64(ReleaseConcurrentTotal.WithLabelValues())).To(Equal(float64(0))) - RegisterCompletedRelease(startTime, nil, "", "", "", "", "", "") + RegisterCompletedRelease(startTime, nil, "", "", "", "", "", "", "", "") Expect(testutil.ToFloat64(ReleaseConcurrentTotal.WithLabelValues())).To(Equal(float64(0))) }) It("decrements ReleaseConcurrentTotal", func() { Expect(testutil.ToFloat64(ReleaseConcurrentTotal.WithLabelValues())).To(Equal(float64(0))) - RegisterCompletedRelease(startTime, completionTime, "", "", "", "", "", "") + RegisterCompletedRelease(startTime, completionTime, "", "", "", "", "", "", "", "") Expect(testutil.ToFloat64(ReleaseConcurrentTotal.WithLabelValues())).To(Equal(float64(-1))) }) @@ -67,6 +67,8 @@ var _ = Describe("Release metrics", Ordered, func() { releaseDurationSecondsLabels[3], releaseDurationSecondsLabels[4], releaseDurationSecondsLabels[5], + releaseDurationSecondsLabels[6], + releaseDurationSecondsLabels[7], ) Expect(testutil.CollectAndCompare(ReleaseDurationSeconds, test.NewHistogramReader( @@ -84,6 +86,8 @@ var _ = Describe("Release metrics", Ordered, func() { releaseTotalLabels[3], releaseTotalLabels[4], releaseTotalLabels[5], + releaseTotalLabels[6], + releaseTotalLabels[7], ) Expect(testutil.CollectAndCompare(ReleaseTotal, test.NewCounterReader( diff --git a/tekton/utils.go b/tekton/utils.go index b7047204..4bae8ca7 100644 --- a/tekton/utils.go +++ b/tekton/utils.go @@ -32,7 +32,11 @@ func isReleasePipelineRun(object client.Object) bool { labelValue, found := object.GetLabels()[metadata.PipelinesTypeLabel] - return found && (labelValue == metadata.FinalPipelineType || labelValue == metadata.ManagedPipelineType || labelValue == metadata.TenantPipelineType) + return found && (labelValue == metadata.TenantCollectorsPipelineType || + labelValue == metadata.ManagedCollectorsPipelineType || + labelValue == metadata.FinalPipelineType || + labelValue == metadata.ManagedPipelineType || + labelValue == metadata.TenantPipelineType) } // hasPipelineSucceeded returns a boolean indicating whether the PipelineRun succeeded or not. diff --git a/tekton/utils/pipeline.go b/tekton/utils/pipeline.go index 95ae0e2c..7b9c4f32 100644 --- a/tekton/utils/pipeline.go +++ b/tekton/utils/pipeline.go @@ -16,7 +16,10 @@ limitations under the License. package utils -import tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" +import ( + "fmt" + tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" +) // Param defines the parameters for a given resolver in PipelineRef type Param struct { @@ -64,6 +67,17 @@ type ParameterizedPipeline struct { Params []Param `json:"params,omitempty"` } +// GetRevision returns the value of the revision param. If not found an error will be raised. +func (pr *PipelineRef) GetRevision() (string, error) { + for _, param := range pr.Params { + if param.Name == "revision" { + return param.Value, nil + } + } + + return "", fmt.Errorf("no revision found") +} + // ToTektonPipelineRef converts a PipelineRef object to Tekton's own PipelineRef type and returns it. func (pr *PipelineRef) ToTektonPipelineRef() *tektonv1.PipelineRef { params := tektonv1.Params{} diff --git a/tekton/utils/pipeline_test.go b/tekton/utils/pipeline_test.go index 0d75e118..00429e41 100644 --- a/tekton/utils/pipeline_test.go +++ b/tekton/utils/pipeline_test.go @@ -59,6 +59,20 @@ var _ = Describe("Pipeline", func() { } }) + When("GetRevision method is called", func() { + It("should return the revision if it exists", func() { + revision, err := gitRef.GetRevision() + Expect(revision).To(Equal("my-revision")) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should not return the revision if it does not exist", func() { + revision, err := bundleRef.GetRevision() + Expect(revision).To(BeEmpty()) + Expect(err).To(HaveOccurred()) + }) + }) + When("ToTektonPipelineRef method is called", func() { It("should return Tekton PipelineRef representation of the PipelineRef", func() { ref := clusterRef.ToTektonPipelineRef()