Skip to content

Commit

Permalink
feat(KONFLUX-5971): set intg test status in git according to PR bld PLR
Browse files Browse the repository at this point in the history
* set integration tests status to pending when build plr is triggered or
  retriggered
* set integration test status to failed/cancelled when build plr fails
* set integration test status to failed/cancelled with failure reason
  when snapshot is not created to show to users on git provider

Signed-off-by: Hongwei Liu<[email protected]>
  • Loading branch information
hongweiliu17 committed Dec 27, 2024
1 parent 11f4a60 commit c75fedd
Show file tree
Hide file tree
Showing 23 changed files with 887 additions and 244 deletions.
8 changes: 8 additions & 0 deletions docs/build_pipeline_controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ new_pipeline_run_without_prgroup{PR group is added to pipelineRun metadata?}
get_pipeline_run{Pipeline updated?}
failed_pipeline_run{Pipeline failed?}
finalizer_exists{Does the finalizer already exist?}
need_to_set_integration_test{Build pipelineRun is newly triggered?<br>Or Build pipelineRun failed?<br> Or Failing to create snapshot?}
retrieve_associated_entity(Retrieve the entity <br> component/application)
determine_snapshot{Does a snapshot exist?}
prep_snapshot(Gather Application components<br> Add new component)
Expand All @@ -28,12 +29,15 @@ continue[Continue processing]
update_metadata(add PR group info to build pipelineRun metadata)
notify_pr_group_failure(annotate Snapshots and in-flight builds in PR group with failure message)
failed_group_pipeline_run{Pipeline failed?}
update_integrationTestStatus_in_git_provider(Create checkRun/commitStatus in<br>git provider)
update_build_plr_annotation(Update build pipelineRun annotation<br>test.appstudio.openshift.io/snapshot-creation-report<br>with the status)
%% Node connections
predicate --> get_pipeline_run
predicate --> new_pipeline_run
predicate --> new_pipeline_run_without_prgroup
predicate --> failed_pipeline_run
predicate --> need_to_set_integration_test
new_pipeline_run --Yes--> finalizer_exists
finalizer_exists --No--> add_finalizer
add_finalizer --> continue
Expand All @@ -55,6 +59,10 @@ prep_snapshot --> check_chains
check_chains --Yes --> annotate_pipelineRun
annotate_pipelineRun --Yes --> remove_finalizer
remove_finalizer --> continue
need_to_set_integration_test --Yes --> update_integrationTestStatus_in_git_provider
need_to_set_integration_test --No --> continue
update_integrationTestStatus_in_git_provider --> update_build_plr_annotation
update_build_plr_annotation --> continue
%% Assigning styles to nodes
class predicate Amber;
Expand Down
129 changes: 106 additions & 23 deletions gitops/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/konflux-ci/integration-service/tekton"
"github.com/konflux-ci/operator-toolkit/metadata"
"github.com/santhosh-tekuri/jsonschema/v5"
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -44,6 +45,9 @@ import (
)

const (
// BuildPipelinesAsCodePrefix contains the prefix applied to labels and annotations Pipelines as Code build pipelineRun.
BuildPipelinesAsCodePrefix = "pipelinesascode.tekton.dev"

// PipelinesAsCodePrefix contains the prefix applied to labels and annotations copied from Pipelines as Code resources.
PipelinesAsCodePrefix = "pac.test.appstudio.openshift.io"

Expand Down Expand Up @@ -77,8 +81,12 @@ const (
// SnapshotGitSourceRepoURLAnnotation contains URL of the git source repository (usually needed for forks)
SnapshotGitSourceRepoURLAnnotation = "test.appstudio.openshift.io/source-repo-url"

SourceURL = "/source-repo-url"
// PipelineAsCodeGitProviderAnnotation contains the git provider attached via PaC (e.g., github, gitlab)
PipelineAsCodeGitSourceURLAnnotation = PipelinesAsCodePrefix + "/source-repo-url"
PipelineAsCodeGitSourceURLAnnotation = PipelinesAsCodePrefix + SourceURL

// BuildPipelineAsCodeGitProviderAnnotation contains the git provider attached via PaC (e.g., github, gitlab)
BuildPipelineAsCodeGitSourceURLAnnotation = BuildPipelinesAsCodePrefix + SourceURL

// SnapshotStatusReportAnnotation contains metadata of tests related to status reporting to git provider
SnapshotStatusReportAnnotation = "test.appstudio.openshift.io/git-reporter-status"
Expand Down Expand Up @@ -122,38 +130,62 @@ const (
// PipelineAsCodeEventTypeLabel is the type of event which triggered the pipelinerun in build service
PipelineAsCodeEventTypeLabel = PipelinesAsCodePrefix + "/event-type"

GitProviderSuffix = "/git-provider"

// PipelineAsCodeGitProviderLabel is the git provider which triggered the pipelinerun in build service.
PipelineAsCodeGitProviderLabel = PipelinesAsCodePrefix + "/git-provider"
PipelineAsCodeGitProviderLabel = PipelinesAsCodePrefix + GitProviderSuffix

// PipelineAsCodeGitProviderAnnotation is the git provider which triggered the pipelinerun in build service.
PipelineAsCodeGitProviderAnnotation = PipelinesAsCodePrefix + "/git-provider"
PipelineAsCodeGitProviderAnnotation = PipelinesAsCodePrefix + GitProviderSuffix

// BuildPipelineAsCodeGitProviderAnnotation is the git provider which triggered the pipelinerun in build service.
BuildPipelineAsCodeGitProviderAnnotation = BuildPipelinesAsCodePrefix + GitProviderSuffix

// BuildPipelineAsCodeGitProviderLabel is the git provider which triggered the pipelinerun in build service.
BuildPipelineAsCodeGitProviderLabel = BuildPipelinesAsCodePrefix + GitProviderSuffix

SHALabelSuffix = "/sha"

// PipelineAsCodeSHALabel is the commit which triggered the pipelinerun in build service.
PipelineAsCodeSHALabel = PipelinesAsCodePrefix + "/sha"
PipelineAsCodeSHALabel = PipelinesAsCodePrefix + SHALabelSuffix

URLOrgLabel = "/url-org"

// PipelineAsCodeURLOrgLabel is the organization for the git repo which triggered the pipelinerun in build service.
PipelineAsCodeURLOrgLabel = PipelinesAsCodePrefix + "/url-org"
PipelineAsCodeURLOrgLabel = PipelinesAsCodePrefix + URLOrgLabel

URLRepositoryLabelSuffix = "/url-repository"

// PipelineAsCodeURLRepositoryLabel is the git repository which triggered the pipelinerun in build service.
PipelineAsCodeURLRepositoryLabel = PipelinesAsCodePrefix + "/url-repository"
PipelineAsCodeURLRepositoryLabel = PipelinesAsCodePrefix + URLRepositoryLabelSuffix

RepoURLAnnotationSuffix = "/repo-url"

// PipelineAsCodeRepoURLAnnotation is the URL to the git repository which triggered the pipelinerun in build service.
PipelineAsCodeRepoURLAnnotation = PipelinesAsCodePrefix + "/repo-url"
PipelineAsCodeRepoURLAnnotation = PipelinesAsCodePrefix + RepoURLAnnotationSuffix

// PipelineAsCodeTargetBranchAnnotation is the SHA of the git revision which triggered the pipelinerun in build service.
PipelineAsCodeTargetBranchAnnotation = PipelinesAsCodePrefix + "/branch"

InstallationIDAnnotationSuffix = "/installation-id"

// PipelineAsCodeInstallationIDAnnotation is the GitHub App installation ID for the git repo which triggered the pipelinerun in build service.
PipelineAsCodeInstallationIDAnnotation = PipelinesAsCodePrefix + "/installation-id"
PipelineAsCodeInstallationIDAnnotation = PipelinesAsCodePrefix + InstallationIDAnnotationSuffix

PullRequestAnnotationSuffix = "/pull-request"

// PipelineAsCodePullRequestAnnotation is the git repository's pull request identifier
PipelineAsCodePullRequestAnnotation = PipelinesAsCodePrefix + "/pull-request"
PipelineAsCodePullRequestAnnotation = PipelinesAsCodePrefix + PullRequestAnnotationSuffix

SourceProjectIDAnnotationSuffix = "/source-project-id"

// PipelineAsCodeSourceProjectIDAnnotation is the source project ID for gitlab
PipelineAsCodeSourceProjectIDAnnotation = PipelinesAsCodePrefix + "/source-project-id"
PipelineAsCodeSourceProjectIDAnnotation = PipelinesAsCodePrefix + SourceProjectIDAnnotationSuffix

TargetProjectIDAnnotationSuffix = "/target-project-id"

// PipelineAsCodeTargetProjectIDAnnotation is the target project ID for gitlab
PipelineAsCodeTargetProjectIDAnnotation = PipelinesAsCodePrefix + "/target-project-id"
PipelineAsCodeTargetProjectIDAnnotation = PipelinesAsCodePrefix + TargetProjectIDAnnotationSuffix

// PipelineAsCodeRepoUrlAnnotation is the target project Repo Url
PipelineAsCodeRepoUrlAnnotation = PipelinesAsCodePrefix + "/repo-url"
Expand Down Expand Up @@ -233,6 +265,9 @@ const (

//IntegrationTestStatusInProgressGithub is the status reported to github when integration test is in progress
IntegrationTestStatusInProgressGithub = "in_progress"

//IntegrationTestStatusCancelledGithub is the status reported to github when integration test is in progress
IntegrationTestStatusCancelledGithub = "cancelled"
)

var (
Expand Down Expand Up @@ -998,29 +1033,58 @@ func IsContextValidForSnapshot(scenarioContextName string, snapshot *application
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 {
// IsContextValidForBuildPLR checks the context and compares it against the snapshot which will be created for build PLR to determine if it applies
func IsContextValidForBuildPLR(scenarioContextName string, plr *tektonv1.PipelineRun) 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" {
return true
} else if strings.HasPrefix(scenarioContextName, "component_") {
componentName := strings.TrimPrefix(scenarioContextName, "component_")
if metadata.HasLabelWithValue(plr, SnapshotComponentLabel, componentName) {
return true
}
} else if scenarioContextName == "pull_request" && !tekton.IsPLRCreatedByPACPushEvent(plr) {
return true
}
return false
}

// IsScenarioApplicableToObjectContext checks the contexts list for a given IntegrationTestScenario and
// compares it against the Snapshot/Build pipelinerun to determine if the scenario applies to it
func IsScenarioApplicableToObjectContext(scenario *v1beta2.IntegrationTestScenario, object metav1.Object) 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
if snapshot, ok := object.(*applicationapiv1alpha1.Snapshot); ok {
for _, scenarioContext := range scenario.Spec.Contexts {
scenarioContext := scenarioContext //G601
if IsContextValidForSnapshot(scenarioContext.Name, snapshot) {
return true
}
}
} else if plr, ok := object.(*tektonv1.PipelineRun); ok {
for _, scenarioContext := range scenario.Spec.Contexts {
scenarioContext := scenarioContext //G601
if IsContextValidForBuildPLR(scenarioContext.Name, plr) {
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 {
// of IntegrationTestScenarios compared against the given Snapshot or build PLR based on individual IntegrationTestScenario contexts
func FilterIntegrationTestScenariosWithContext(scenarios *[]v1beta2.IntegrationTestScenario, object metav1.Object) *[]v1beta2.IntegrationTestScenario {
var filteredScenarioList []v1beta2.IntegrationTestScenario

for _, scenario := range *scenarios {
scenario := scenario //G601
if IsScenarioApplicableToSnapshotsContext(&scenario, snapshot) {
if IsScenarioApplicableToObjectContext(&scenario, object) {
filteredScenarioList = append(filteredScenarioList, scenario)
}
}
Expand Down Expand Up @@ -1147,11 +1211,30 @@ func UnmarshalJSON(b []byte) ([]*ComponentSnapshotInfo, error) {
return componentSnapshotInfos, nil
}

func GetSourceRepoOwnerFromSnapshot(snapshot *applicationapiv1alpha1.Snapshot) string {
sourceRepoUrlAnnotation, found := snapshot.GetAnnotations()[PipelineAsCodeGitSourceURLAnnotation]
func GetSourceRepoOwnerFromObject(object metav1.Object) string {
sourceRepoUrlAnnotation := ""
found := false

if snapshot, ok := object.(*applicationapiv1alpha1.Snapshot); ok {
sourceRepoUrlAnnotation, found = snapshot.GetAnnotations()[PipelineAsCodeGitSourceURLAnnotation]
}
if plr, ok := object.(*tektonv1.PipelineRun); ok {
sourceRepoUrlAnnotation, found = plr.GetAnnotations()[BuildPipelineAsCodeGitSourceURLAnnotation]
}

if found {
arr := strings.Split(sourceRepoUrlAnnotation, "/")
return arr[len(arr)-2]
}
return ""
}

func IsObjectCreatedByPACPushEvent(object metav1.Object) bool {
if snapshot, ok := object.(*applicationapiv1alpha1.Snapshot); ok {
return IsSnapshotCreatedByPACPushEvent(snapshot)
}
if plr, ok := object.(*tektonv1.PipelineRun); ok {
return tekton.IsPLRCreatedByPACPushEvent(plr)
}
return false
}
71 changes: 62 additions & 9 deletions gitops/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import (

applicationapiv1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1"
"github.com/konflux-ci/integration-service/gitops"
"github.com/konflux-ci/integration-service/tekton"
"github.com/konflux-ci/operator-toolkit/metadata"
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -40,13 +42,14 @@ import (
var _ = Describe("Gitops functions for managing Snapshots", Ordered, func() {

var (
hasApp *applicationapiv1alpha1.Application
hasComp *applicationapiv1alpha1.Component
hasSnapshot *applicationapiv1alpha1.Snapshot
hasComSnapshot1 *applicationapiv1alpha1.Snapshot
hasComSnapshot2 *applicationapiv1alpha1.Snapshot
hasComSnapshot3 *applicationapiv1alpha1.Snapshot
sampleImage string
hasApp *applicationapiv1alpha1.Application
hasComp *applicationapiv1alpha1.Component
hasSnapshot *applicationapiv1alpha1.Snapshot
hasComSnapshot1 *applicationapiv1alpha1.Snapshot
hasComSnapshot2 *applicationapiv1alpha1.Snapshot
hasComSnapshot3 *applicationapiv1alpha1.Snapshot
buildPipelineRun *tektonv1.PipelineRun
sampleImage string
)

const (
Expand Down Expand Up @@ -93,6 +96,33 @@ var _ = Describe("Gitops functions for managing Snapshots", Ordered, func() {
},
}
Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed())

buildPipelineRun = &tektonv1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Name: "pipelinerun-build-sample",
Namespace: "default",
Labels: map[string]string{
"pipelines.appstudio.openshift.io/type": "build",
"pipelines.openshift.io/used-by": "build-cloud",
"pipelines.openshift.io/runtime": "nodejs",
"pipelines.openshift.io/strategy": "s2i",
"appstudio.openshift.io/component": "component-sample",
"build.appstudio.redhat.com/target_branch": "main",
"pipelinesascode.tekton.dev/event-type": "pull_request",
"pipelinesascode.tekton.dev/pull-request": "1",
},
Annotations: map[string]string{
"appstudio.redhat.com/updateComponentOnSuccess": "false",
"pipelinesascode.tekton.dev/on-target-branch": "[main,master]",
"build.appstudio.openshift.io/repo": "https://github.com/devfile-samples/devfile-sample-go-basic?rev=c713067b0e65fb3de50d1f7c457eb51c2ab0dbb0",
"foo": "bar",
"chains.tekton.dev/signed": "true",
"pipelinesascode.tekton.dev/source-branch": "sourceBranch",
"pipelinesascode.tekton.dev/url-org": "redhat",
},
},
}
Expect(k8sClient.Create(ctx, buildPipelineRun)).Should(Succeed())
})

BeforeEach(func() {
Expand Down Expand Up @@ -261,6 +291,8 @@ var _ = Describe("Gitops functions for managing Snapshots", Ordered, func() {
Expect(err == nil || errors.IsNotFound(err)).To(BeTrue())
err = k8sClient.Delete(ctx, hasComSnapshot3)
Expect(err == nil || errors.IsNotFound(err)).To(BeTrue())
err = k8sClient.Delete(ctx, buildPipelineRun)
Expect(err == nil || errors.IsNotFound(err)).To(BeTrue())
})

AfterAll(func() {
Expand Down Expand Up @@ -843,6 +875,22 @@ var _ = Describe("Gitops functions for managing Snapshots", Ordered, func() {
Expect(*filteredScenarios).To(HaveLen(3))
})

It("Returns only the scenarios matching the context for a given kind of build PLR", func() {
// A build plr Snapshot for a pull request
filteredScenarios := gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, buildPipelineRun)
Expect(*filteredScenarios).To(HaveLen(6))

// A build plr for pull request event and component
buildPipelineRun.Labels[tekton.ComponentNameLabel] = "component-sample-2"
filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, buildPipelineRun)
Expect(*filteredScenarios).To(HaveLen(6))

// A group Snapshot for pull request event referencing nonexisting component-sample-2
buildPipelineRun.Labels[tekton.ComponentNameLabel] = "component-sample-3"
filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, buildPipelineRun)
Expect(*filteredScenarios).To(HaveLen(5))
})

It("Testing annotating snapshot", func() {
componentSnapshotInfos := []gitops.ComponentSnapshotInfo{
{
Expand Down Expand Up @@ -946,12 +994,17 @@ var _ = Describe("Gitops functions for managing Snapshots", Ordered, func() {
})

It("Can return correct source repo owner for snapshot", func() {
sourceRepoOwner := gitops.GetSourceRepoOwnerFromSnapshot(hasSnapshot)
sourceRepoOwner := gitops.GetSourceRepoOwnerFromObject(hasSnapshot)
Expect(sourceRepoOwner).To(Equal(""))
Expect(metadata.SetAnnotation(hasSnapshot, gitops.PipelineAsCodeGitSourceURLAnnotation, "https://github.com/devfile-sample/devfile-sample-go-basic")).To(Succeed())
sourceRepoOwner = gitops.GetSourceRepoOwnerFromSnapshot(hasSnapshot)
sourceRepoOwner = gitops.GetSourceRepoOwnerFromObject(hasSnapshot)
Expect(sourceRepoOwner).To(Equal("devfile-sample"))
})

It("Can check if object is created by pac push event", func() {
Expect(gitops.IsObjectCreatedByPACPushEvent(buildPipelineRun)).To(BeFalse())
Expect(gitops.IsObjectCreatedByPACPushEvent(hasComSnapshot1)).To(BeFalse())
})
})
})
})
9 changes: 8 additions & 1 deletion helpers/build.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
package helpers

const CreateSnapshotAnnotationName = "test.appstudio.openshift.io/create-snapshot-status"
const (
// CreateSnapshotAnnotationName contains medata of snapshot creation failure or success
CreateSnapshotAnnotationName = "test.appstudio.openshift.io/create-snapshot-status"

// SnapshotCreationReportAnnotation contains metadata of snapshot creation status reporting to git provider
// to initialize integration test
SnapshotCreationReportAnnotation = "test.appstudio.openshift.io/snapshot-creation-report"
)
Loading

0 comments on commit c75fedd

Please sign in to comment.