diff --git a/docs/snapshot-controller.md b/docs/snapshot-controller.md index 5a7e0b411..55bd077b0 100644 --- a/docs/snapshot-controller.md +++ b/docs/snapshot-controller.md @@ -17,7 +17,7 @@ flowchart TD are_there_any_ITS{"Are there any
IntegrationTestScenario
present for the given
Application?"} create_new_test_PLR(Create a new Test PipelineRun for each
of the above ITS, if it doesn't exists already) mark_snapshot_InProgress(Mark Snapshot's Integration-testing
status as 'InProgress') - fetch_all_required_ITS("Fetch all the required
(non-optional) IntegrationTestScenario
for the given Application") + fetch_all_required_ITS("Fetch all the required
(non-optional) IntegrationTestScenario
for the given Application
filtered by ITS context(s)") encountered_error1{Encountered error?} mark_snapshot_Invalid1(Mark the Snapshot as Invalid) is_atleast_1_required_ITS{Is there atleast
1 required ITS?} diff --git a/gitops/snapshot.go b/gitops/snapshot.go index 68da3dfda..50f95a479 100644 --- a/gitops/snapshot.go +++ b/gitops/snapshot.go @@ -20,8 +20,10 @@ import ( "context" "errors" "fmt" + "github.com/konflux-ci/integration-service/api/v1beta2" "reflect" "strconv" + "strings" "time" "github.com/google/go-containerregistry/pkg/name" @@ -92,6 +94,9 @@ const ( // SnapshotOverrideType is the type of Snapshot which was created for override Global Candidate List. SnapshotOverrideType = "override" + // SnapshotGroupType is the type of Snapshot which was created for pull request groups. + SnapshotGroupType = "group" + // PipelineAsCodeEventTypeLabel is the type of event which triggered the pipelinerun in build service PipelineAsCodeEventTypeLabel = PipelinesAsCodePrefix + "/event-type" @@ -869,6 +874,10 @@ func IsComponentSnapshot(snapshot *applicationapiv1alpha1.Snapshot) bool { return metadata.HasLabelWithValue(snapshot, SnapshotTypeLabel, SnapshotComponentType) } +func IsGroupSnapshot(snapshot *applicationapiv1alpha1.Snapshot) bool { + return metadata.HasLabelWithValue(snapshot, SnapshotTypeLabel, SnapshotGroupType) +} + func IsComponentSnapshotCreatedByPACPushEvent(snapshot *applicationapiv1alpha1.Snapshot) bool { return IsComponentSnapshot(snapshot) && IsSnapshotCreatedByPACPushEvent(snapshot) } @@ -885,3 +894,56 @@ func SetOwnerReference(ctx context.Context, adapterClient client.Client, snapsho } return snapshot, nil } + +// IsContextValidForSnapshot checks the context and compares it against the Snapshot to determine if it applies +func IsContextValidForSnapshot(scenarioContextName string, snapshot *applicationapiv1alpha1.Snapshot) bool { + // `application` context is supported for backwards-compatibility and considered the same as `all` + if scenarioContextName == "application" || scenarioContextName == "all" { + return true + } else if scenarioContextName == "component" && IsComponentSnapshot(snapshot) { + return true + } else if strings.HasPrefix(scenarioContextName, "component_") { + componentName := strings.TrimPrefix(scenarioContextName, "component_") + if metadata.HasLabelWithValue(snapshot, SnapshotComponentLabel, componentName) { + return true + } + } else if scenarioContextName == "group" && IsGroupSnapshot(snapshot) { + return true + } else if scenarioContextName == "override" && IsOverrideSnapshot(snapshot) { + return true + } else if scenarioContextName == "push" && IsSnapshotCreatedByPACPushEvent(snapshot) { + return true + } else if scenarioContextName == "pull_request" && !IsSnapshotCreatedByPACPushEvent(snapshot) { + return true + } + return false +} + +// IsScenarioApplicableToSnapshotsContext checks the contexts list for a given IntegrationTestScenario and +// compares it against the Snapshot to determine if the scenario applies to it +func IsScenarioApplicableToSnapshotsContext(scenario *v1beta2.IntegrationTestScenario, snapshot *applicationapiv1alpha1.Snapshot) bool { + // If the contexts list is empty, we assume that the scenario applies to all contexts by default + if len(scenario.Spec.Contexts) == 0 { + return true + } + for _, scenarioContext := range scenario.Spec.Contexts { + scenarioContext := scenarioContext //G601 + if IsContextValidForSnapshot(scenarioContext.Name, snapshot) { + return true + } + } + return false +} + +// FilterIntegrationTestScenariosWithContext returns a filtered list of IntegrationTestScenario from the given list +// of IntegrationTestScenarios compared against the given Snapshot based on individual IntegrationTestScenario contexts +func FilterIntegrationTestScenariosWithContext(scenarios *[]v1beta2.IntegrationTestScenario, snapshot *applicationapiv1alpha1.Snapshot) *[]v1beta2.IntegrationTestScenario { + var filteredScenarioList []v1beta2.IntegrationTestScenario + for _, scenario := range *scenarios { + scenario := scenario //G601 + if IsScenarioApplicableToSnapshotsContext(&scenario, snapshot) { + filteredScenarioList = append(filteredScenarioList, scenario) + } + } + return &filteredScenarioList +} diff --git a/gitops/snapshot_test.go b/gitops/snapshot_test.go index e20253611..1f6c699ad 100644 --- a/gitops/snapshot_test.go +++ b/gitops/snapshot_test.go @@ -17,6 +17,7 @@ limitations under the License. package gitops_test import ( + "github.com/konflux-ci/integration-service/api/v1beta2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" @@ -623,6 +624,92 @@ var _ = Describe("Gitops functions for managing Snapshots", Ordered, func() { Expect(err).ToNot(HaveOccurred()) }) }) + }) + + Context("Filter integration tests for a given Snapshot based on their context", func() { + When("There are a number of integration test scenarios with different contexts", func() { + integrationTestScenario := &v1beta2.IntegrationTestScenario{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-pass", + Namespace: "default", + Labels: map[string]string{ + "test.appstudio.openshift.io/optional": "false", + }, + }, + Spec: v1beta2.IntegrationTestScenarioSpec{ + Application: "application-sample", + ResolverRef: v1beta2.ResolverRef{ + Resolver: "git", + Params: []v1beta2.ResolverParameter{ + { + Name: "url", + Value: "https://github.com/redhat-appstudio/integration-examples.git", + }, + { + Name: "revision", + Value: "main", + }, + { + Name: "pathInRepo", + Value: "pipelineruns/integration_pipelinerun_pass.yaml", + }, + }, + }, + }, + } + applicationScenario := integrationTestScenario.DeepCopy() + applicationScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "application", Description: "Application Testing"}} + componentScenario := integrationTestScenario.DeepCopy() + componentScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "component", Description: "Component"}} + componentSampleScenario := integrationTestScenario.DeepCopy() + componentSampleScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "component_component-sample", Description: "Component component-sample"}} + componentSample2Scenario := integrationTestScenario.DeepCopy() + componentSample2Scenario.Spec.Contexts = []v1beta2.TestContext{{Name: "component_component-sample-2", Description: "Component component-sample-2"}} + pullRequestScenario := integrationTestScenario.DeepCopy() + pullRequestScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "pull_request", Description: "Pull Request"}} + pushScenario := integrationTestScenario.DeepCopy() + pushScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "push", Description: "Component"}} + groupScenario := integrationTestScenario.DeepCopy() + groupScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "group", Description: "PR Group Testing"}} + overrideScenario := integrationTestScenario.DeepCopy() + overrideScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "override", Description: "Override Snapshot testing"}} + componentAndGroupScenario := integrationTestScenario.DeepCopy() + componentAndGroupScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "group"}, {Name: "component"}} + unsupportedScenario := integrationTestScenario.DeepCopy() + unsupportedScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "n/a"}} + + allScenarios := []v1beta2.IntegrationTestScenario{*integrationTestScenario, *applicationScenario, + *componentScenario, *componentSampleScenario, *componentSample2Scenario, *pullRequestScenario, + *pushScenario, *groupScenario, *componentAndGroupScenario, *unsupportedScenario} + + It("Returns only the scenarios matching the context for a given kind of Snapshot", func() { + // A component Snapshot for a push event referencing the component-sample + filteredScenarios := gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot) + Expect(*filteredScenarios).To(HaveLen(6)) + + // A component Snapshot for pull request event referencing the component-sample + hasSnapshot.Labels[gitops.PipelineAsCodeEventTypeLabel] = gitops.PipelineAsCodePullRequestType + filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot) + Expect(*filteredScenarios).To(HaveLen(6)) + + // A group Snapshot for pull request event referencing component-sample-2 + hasSnapshot.Labels[gitops.SnapshotComponentLabel] = "component-sample-2" + filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot) + Expect(*filteredScenarios).To(HaveLen(6)) + + // A group Snapshot for pull request event for a PR group + hasSnapshot.Labels[gitops.SnapshotTypeLabel] = "group" + hasSnapshot.Labels[gitops.SnapshotComponentLabel] = "" + filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot) + Expect(*filteredScenarios).To(HaveLen(5)) + + // An override Snapshot + hasSnapshot.Labels[gitops.SnapshotTypeLabel] = "override" + filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot) + Expect(*filteredScenarios).To(HaveLen(3)) + }) + }) }) + }) diff --git a/internal/controller/snapshot/snapshot_adapter.go b/internal/controller/snapshot/snapshot_adapter.go index 332ba7c4d..beb5b87bf 100644 --- a/internal/controller/snapshot/snapshot_adapter.go +++ b/internal/controller/snapshot/snapshot_adapter.go @@ -163,13 +163,14 @@ func (a *Adapter) EnsureIntegrationPipelineRunsExist() (controller.OperationResu return controller.ContinueProcessing() } - integrationTestScenarios, err := a.loader.GetAllIntegrationTestScenariosForApplication(a.context, a.client, a.application) + allIntegrationTestScenarios, err := a.loader.GetAllIntegrationTestScenariosForApplication(a.context, a.client, a.application) if err != nil { a.logger.Error(err, "Failed to get Integration test scenarios for the following application", "Application.Namespace", a.application.Namespace) } - if integrationTestScenarios != nil { + if allIntegrationTestScenarios != nil { + integrationTestScenarios := gitops.FilterIntegrationTestScenariosWithContext(allIntegrationTestScenarios, a.snapshot) a.logger.Info( fmt.Sprintf("Found %d IntegrationTestScenarios for application", len(*integrationTestScenarios)), "Application.Name", a.application.Name, @@ -254,7 +255,7 @@ func (a *Adapter) EnsureIntegrationPipelineRunsExist() (controller.OperationResu } } - requiredIntegrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application) + allRequiredIntegrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application) if err != nil { a.logger.Error(err, "Failed to get all required IntegrationTestScenarios") patch := client.MergeFrom(a.snapshot.DeepCopy()) @@ -263,6 +264,7 @@ func (a *Adapter) EnsureIntegrationPipelineRunsExist() (controller.OperationResu a.snapshot, h.LogActionUpdate) return controller.RequeueOnErrorOrStop(a.client.Status().Patch(a.context, a.snapshot, patch)) } + requiredIntegrationTestScenarios := gitops.FilterIntegrationTestScenariosWithContext(allRequiredIntegrationTestScenarios, a.snapshot) if len(*requiredIntegrationTestScenarios) == 0 && !gitops.IsSnapshotMarkedAsPassed(a.snapshot) { err := gitops.MarkSnapshotAsPassed(a.context, a.client, a.snapshot, "No required IntegrationTestScenarios found, skipped testing") if err != nil { diff --git a/internal/controller/statusreport/statusreport_adapter.go b/internal/controller/statusreport/statusreport_adapter.go index 33e0061da..65e524471 100644 --- a/internal/controller/statusreport/statusreport_adapter.go +++ b/internal/controller/statusreport/statusreport_adapter.go @@ -121,10 +121,11 @@ func (a *Adapter) EnsureSnapshotTestStatusReportedToGitProvider() (controller.Op func (a *Adapter) EnsureSnapshotFinishedAllTests() (controller.OperationResult, error) { // Get all required integrationTestScenarios for the Application and then use the Snapshot status annotation // to check if all Integration tests were finished for that Snapshot - integrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application) + allRequiredIntegrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application) if err != nil { return controller.RequeueWithError(err) } + integrationTestScenarios := gitops.FilterIntegrationTestScenariosWithContext(allRequiredIntegrationTestScenarios, a.snapshot) a.logger.Info(fmt.Sprintf("Found %d required integration test scenarios", len(*integrationTestScenarios))) testStatuses, err := gitops.NewSnapshotIntegrationTestStatusesFromSnapshot(a.snapshot)