diff --git a/controllers/integrationpipeline/integrationpipeline_adapter.go b/controllers/integrationpipeline/integrationpipeline_adapter.go index 8fcee8169..bed404191 100644 --- a/controllers/integrationpipeline/integrationpipeline_adapter.go +++ b/controllers/integrationpipeline/integrationpipeline_adapter.go @@ -61,24 +61,6 @@ func NewAdapter(pipelineRun *tektonv1beta1.PipelineRun, component *applicationap } } -// EnsureStatusReported will ensure that integration PipelineRun status is reported to the git provider -// which (indirectly) triggered its execution. -func (a *Adapter) EnsureStatusReported() (controller.OperationResult, error) { - reporters, err := a.status.GetReporters(a.pipelineRun) - - if err != nil { - return controller.RequeueWithError(err) - } - - for _, reporter := range reporters { - if err := reporter.ReportStatus(a.client, a.context, a.pipelineRun); err != nil { - return controller.RequeueWithError(err) - } - } - - return controller.ContinueProcessing() -} - // EnsureStatusReportedInSnapshot will ensure that status of the integration test pipelines is reported to snapshot // to be consumed by user func (a *Adapter) EnsureStatusReportedInSnapshot() (controller.OperationResult, error) { diff --git a/controllers/integrationpipeline/integrationpipeline_adapter_test.go b/controllers/integrationpipeline/integrationpipeline_adapter_test.go index f18cc663d..f8379fb51 100644 --- a/controllers/integrationpipeline/integrationpipeline_adapter_test.go +++ b/controllers/integrationpipeline/integrationpipeline_adapter_test.go @@ -18,8 +18,6 @@ package integrationpipeline import ( "bytes" - "context" - "errors" "reflect" "time" @@ -39,44 +37,15 @@ import ( ctrl "sigs.k8s.io/controller-runtime" applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1" - "github.com/redhat-appstudio/integration-service/status" tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tonglil/buflogr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) -type MockStatusAdapter struct { - Reporter *MockStatusReporter - GetReportersError error -} - -type MockStatusReporter struct { - Called bool - ReportStatusError error -} - -func (r *MockStatusReporter) ReportStatus(client.Client, context.Context, *tektonv1beta1.PipelineRun) error { - r.Called = true - return r.ReportStatusError -} - -func (r *MockStatusReporter) ReportStatusForSnapshot(client.Client, context.Context, *helpers.IntegrationLogger, *applicationapiv1alpha1.Snapshot) error { - r.Called = true - return r.ReportStatusError -} - -func (a *MockStatusAdapter) GetReporters(object client.Object) ([]status.Reporter, error) { - return []status.Reporter{a.Reporter}, a.GetReportersError -} - var _ = Describe("Pipeline Adapter", Ordered, func() { var ( - adapter *Adapter - createAdapter func() *Adapter - logger helpers.IntegrationLogger - statusAdapter *MockStatusAdapter - statusReporter *MockStatusReporter + adapter *Adapter + logger helpers.IntegrationLogger successfulTaskRun *tektonv1beta1.TaskRun failedTaskRun *tektonv1beta1.TaskRun @@ -593,98 +562,6 @@ var _ = Describe("Pipeline Adapter", Ordered, func() { }) - When("EnsureStatusReported is called", func() { - It("ensures status is reported for integration PipelineRuns", func() { - adapter = createAdapter() - adapter.context = loader.GetMockedContext(ctx, []loader.MockData{ - { - ContextKey: loader.ApplicationContextKey, - Resource: hasApp, - }, - { - ContextKey: loader.ComponentContextKey, - Resource: hasComp, - }, - { - ContextKey: loader.SnapshotContextKey, - Resource: hasSnapshot, - }, - { - ContextKey: loader.TaskRunContextKey, - Resource: successfulTaskRun, - }, - { - ContextKey: loader.EnvironmentContextKey, - Resource: hasEnv, - }, - { - ContextKey: loader.PipelineRunsContextKey, - Resource: []tektonv1beta1.PipelineRun{*integrationPipelineRunComponent}, - }, - { - ContextKey: loader.RequiredIntegrationTestScenariosContextKey, - Resource: []v1beta1.IntegrationTestScenario{*integrationTestScenario}, - }, - }) - - adapter.pipelineRun = &tektonv1beta1.PipelineRun{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pipelinerun-status-sample", - Namespace: "default", - Labels: map[string]string{ - "appstudio.openshift.io/application": "test-application", - "appstudio.openshift.io/component": "devfile-sample-go-basic", - "appstudio.openshift.io/snapshot": "test-application-s8tnj", - "test.appstudio.openshift.io/scenario": "example-pass", - "pac.test.appstudio.openshift.io/state": "started", - "pac.test.appstudio.openshift.io/sender": "foo", - "pac.test.appstudio.openshift.io/check-run-id": "9058825284", - "pac.test.appstudio.openshift.io/branch": "main", - "pac.test.appstudio.openshift.io/url-org": "devfile-sample", - "pac.test.appstudio.openshift.io/original-prname": "devfile-sample-go-basic-on-pull-request", - "pac.test.appstudio.openshift.io/url-repository": "devfile-sample-go-basic", - "pac.test.appstudio.openshift.io/repository": "devfile-sample-go-basic", - "pac.test.appstudio.openshift.io/sha": "12a4a35ccd08194595179815e4646c3a6c08bb77", - "pac.test.appstudio.openshift.io/git-provider": "github", - "pac.test.appstudio.openshift.io/event-type": "pull_request", - "pipelines.appstudio.openshift.io/type": "test", - }, - Annotations: map[string]string{ - "pac.test.appstudio.openshift.io/on-target-branch": "[main,master]", - "pac.test.appstudio.openshift.io/repo-url": "https://github.com/devfile-samples/devfile-sample-go-basic", - "pac.test.appstudio.openshift.io/sha-title": "Appstudio update devfile-sample-go-basic", - "pac.test.appstudio.openshift.io/git-auth-secret": "pac-gitauth-zjib", - "pac.test.appstudio.openshift.io/pull-request": "16", - "pac.test.appstudio.openshift.io/on-event": "[pull_request]", - "pac.test.appstudio.openshift.io/installation-id": "30353543", - }, - }, - Spec: tektonv1beta1.PipelineRunSpec{ - PipelineRef: &tektonv1beta1.PipelineRef{ - Name: "component-pipeline-pass", - Bundle: "quay.io/kpavic/test-bundle:component-pipeline-pass", - }, - }, - } - - result, err := adapter.EnsureStatusReported() - Expect(!result.CancelRequest && err == nil).To(BeTrue()) - - Expect(statusReporter.Called).To(BeTrue()) - - statusAdapter.GetReportersError = errors.New("GetReportersError") - - result, err = adapter.EnsureStatusReported() - Expect(result.RequeueRequest && err != nil && err.Error() == "GetReportersError").To(BeTrue()) - - statusAdapter.GetReportersError = nil - statusReporter.ReportStatusError = errors.New("ReportStatusError") - - result, err = adapter.EnsureStatusReported() - Expect(result.RequeueRequest && err != nil && err.Error() == "ReportStatusError").To(BeTrue()) - }) - }) - When("EnsureEphemeralEnvironmentsCleanedUp is called", func() { BeforeEach(func() { deploymentTargetClass = &applicationapiv1alpha1.DeploymentTargetClass{ @@ -802,11 +679,4 @@ var _ = Describe("Pipeline Adapter", Ordered, func() { }) }) - createAdapter = func() *Adapter { - adapter = NewAdapter(integrationPipelineRunComponent, hasComp, hasApp, logger, loader.NewMockLoader(), k8sClient, ctx) - statusReporter = &MockStatusReporter{} - statusAdapter = &MockStatusAdapter{Reporter: statusReporter} - adapter.status = statusAdapter - return adapter - } }) diff --git a/controllers/integrationpipeline/integrationpipeline_controller.go b/controllers/integrationpipeline/integrationpipeline_controller.go index a4a10ffd5..535cc4673 100644 --- a/controllers/integrationpipeline/integrationpipeline_controller.go +++ b/controllers/integrationpipeline/integrationpipeline_controller.go @@ -109,14 +109,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return controller.ReconcileHandler([]controller.Operation{ adapter.EnsureStatusReportedInSnapshot, - adapter.EnsureStatusReported, adapter.EnsureEphemeralEnvironmentsCleanedUp, }) } // AdapterInterface is an interface defining all the operations that should be defined in an Integration adapter. type AdapterInterface interface { - EnsureStatusReported() (controller.OperationResult, error) EnsureStatusReportedInSnapshot() (controller.OperationResult, error) EnsureEphemeralEnvironmentsCleanedUp() (controller.OperationResult, error) } diff --git a/controllers/statusreport/statusreport_adapter.go b/controllers/statusreport/statusreport_adapter.go index d29b1463d..7a573db40 100644 --- a/controllers/statusreport/statusreport_adapter.go +++ b/controllers/statusreport/statusreport_adapter.go @@ -23,7 +23,6 @@ import ( "github.com/redhat-appstudio/integration-service/gitops" "github.com/redhat-appstudio/integration-service/metrics" "github.com/redhat-appstudio/operator-toolkit/metadata" - "os" applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1" "github.com/redhat-appstudio/integration-service/helpers" @@ -35,8 +34,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -const FeatureFlagStatusReprotingEnabled = "FEATURE_STATUS_REPORTING_ENABLED" - // Adapter holds the objects needed to reconcile a snapshot's test status report. type Adapter struct { snapshot *applicationapiv1alpha1.Snapshot @@ -65,7 +62,7 @@ func NewAdapter(snapshot *applicationapiv1alpha1.Snapshot, application *applicat // EnsureSnapshotTestStatusReported will ensure that integration test status including env provision and snapshotEnvironmentBinding error is reported to the git provider // which (indirectly) triggered its execution. func (a *Adapter) EnsureSnapshotTestStatusReported() (controller.OperationResult, error) { - if !isFeatureEnabled() || !gitops.IsSnapshotCreatedByPACPullRequestEvent(a.snapshot) { + if !gitops.IsSnapshotCreatedByPACPullRequestEvent(a.snapshot) { return controller.ContinueProcessing() } @@ -85,14 +82,6 @@ func (a *Adapter) EnsureSnapshotTestStatusReported() (controller.OperationResult return controller.ContinueProcessing() } -// isFeatureEnabled returns true when the feature flag FEATURE_STATUS_REPORTING_ENABLED has been defined in env vars -func isFeatureEnabled() bool { - if _, found := os.LookupEnv(FeatureFlagStatusReprotingEnabled); found { - return true - } - return false -} - // EnsureSnapshotFinishedAllTests is an operation that will ensure that a pipeline Snapshot // to the PipelineRun being processed finished and passed all tests for all defined required IntegrationTestScenarios. // If the Snapshot doesn't have the freshest state of components, a composite Snapshot will be created instead diff --git a/controllers/statusreport/statusreport_adapter_test.go b/controllers/statusreport/statusreport_adapter_test.go index e80edeb61..d6538d53f 100644 --- a/controllers/statusreport/statusreport_adapter_test.go +++ b/controllers/statusreport/statusreport_adapter_test.go @@ -23,7 +23,6 @@ import ( "github.com/redhat-appstudio/integration-service/api/v1beta1" "github.com/tonglil/buflogr" "k8s.io/apimachinery/pkg/api/meta" - "os" "reflect" "time" @@ -34,7 +33,6 @@ import ( "github.com/redhat-appstudio/integration-service/loader" intgteststat "github.com/redhat-appstudio/integration-service/pkg/integrationteststatus" "github.com/redhat-appstudio/integration-service/status" - tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -54,11 +52,6 @@ type MockStatusReporter struct { ReportStatusError error } -func (r *MockStatusReporter) ReportStatus(client.Client, context.Context, *tektonv1beta1.PipelineRun) error { - r.Called = true - return r.ReportStatusError -} - func (r *MockStatusReporter) ReportStatusForSnapshot(client.Client, context.Context, *helpers.IntegrationLogger, *applicationapiv1alpha1.Snapshot) error { r.Called = true r.ReportStatusError = nil @@ -260,10 +253,6 @@ var _ = Describe("Snapshot Adapter", Ordered, func() { }, } Expect(k8sClient.Create(ctx, hasSnapshot)).Should(Succeed()) - - // enable feature flag for testing - err := os.Setenv(FeatureFlagStatusReprotingEnabled, "yes") - Expect(err).To(BeNil()) }) AfterEach(func() { diff --git a/docs/statusreport-controller.md b/docs/statusreport-controller.md index fad7b4a80..4a53f5eae 100644 --- a/docs/statusreport-controller.md +++ b/docs/statusreport-controller.md @@ -30,6 +30,9 @@ flowchart TD create_commitStatusAdapter(Create commitStatusAdapter according to
commit owner, repo, SHA
and integration test status) does_commitStatus_exist{Does commitStatus exist
on github already?} create_new_commitStatus_on_gh(Create new commitStatus on github) + does_comment_exist(Does a comment exist for snapshot and scenario?) + update_existing_comment(Update the existing comment for
snapshot and scenario
) + create_new_comment(Create a new comment for
snapshot and scenario
) continue_processing(Controller continues processing) @@ -56,7 +59,11 @@ flowchart TD create_commitStatusAdapter --> does_commitStatus_exist does_commitStatus_exist --Yes--> continue_processing does_commitStatus_exist --No--> create_new_commitStatus_on_gh - create_new_commitStatus_on_gh --> continue_processing + create_new_commitStatus_on_gh --> does_comment_exist + does_comment_exist --Yes--> update_existing_comment + does_comment_exist --No--> create_new_comment + update_existing_comment --> continue_processing + create_new_comment --> continue_processing %%%%%%%%%%%%%%%%%%%%%%% Drawing EnsureSnapshotTestStatusReported() function diff --git a/git/github/github.go b/git/github/github.go index 4dd11d199..21683b44e 100644 --- a/git/github/github.go +++ b/git/github/github.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/http" + "strings" "time" "github.com/bradleyfalzon/ghinstallation/v2" @@ -78,6 +79,8 @@ type ChecksService interface { // IssuesService defines the methods used in the github Issues service. type IssuesService interface { CreateComment(ctx context.Context, owner string, repo string, number int, comment *ghapi.IssueComment) (*ghapi.IssueComment, *ghapi.Response, error) + ListComments(ctx context.Context, owner string, repo string, number int, opts *ghapi.IssueListCommentsOptions) ([]*ghapi.IssueComment, *ghapi.Response, error) + EditComment(ctx context.Context, owner string, repo string, id int64, comment *ghapi.IssueComment) (*ghapi.IssueComment, *ghapi.Response, error) } // RepositoriesService defines the methods used in the github Repositories service. @@ -99,7 +102,10 @@ type ClientInterface interface { IsUpdateNeeded(existingCheckRun *ghapi.CheckRun, newCheckRun *CheckRunAdapter) bool GetExistingCheckRun(checkRuns []*ghapi.CheckRun, newCheckRun *CheckRunAdapter) *ghapi.CheckRun GetAllCommitStatusesForRef(ctx context.Context, owner, repo, sha string) ([]*ghapi.RepoStatus, error) + GetAllCommentsForPR(ctx context.Context, owner string, repo string, pr int) ([]*ghapi.IssueComment, error) CommitStatusExists(res []*ghapi.RepoStatus, commitStatus *CommitStatusAdapter) (bool, error) + GetExistingCommentID(comments []*ghapi.IssueComment, snapshotName, scenarioName string) *int64 + EditComment(ctx context.Context, owner string, repo string, commentID int64, body string) (int64, error) } // Client is an abstraction around the API client. @@ -374,6 +380,18 @@ func (c *Client) GetExistingCheckRun(checkRuns []*ghapi.CheckRun, newCheckRun *C return nil } +// GetExistingComment returns existing GitHub comment for the scenario of ref. +func (c *Client) GetExistingCommentID(comments []*ghapi.IssueComment, snapshotName, scenarioName string) *int64 { + for _, comment := range comments { + if strings.Contains(*comment.Body, snapshotName) && strings.Contains(*comment.Body, scenarioName) { + c.logger.Info("found comment ID with a matching scenarioName", "scenarioName", scenarioName) + return comment.ID + } + } + c.logger.Info("found no comment with a matching scenarioName", "scenarioName", scenarioName) + return nil +} + // IsUpdateNeeded check if check run update is needed // according to the text of existingCheckRun and newCheckRun since the details are different every update func (c *Client) IsUpdateNeeded(existingCheckRun *ghapi.CheckRun, newCheckRun *CheckRunAdapter) bool { @@ -403,6 +421,21 @@ func (c *Client) GetAllCommitStatusesForRef(ctx context.Context, owner, repo, sh return res, nil } +// GetAllCommentsForPR returns all existing comment if a match for the Owner, Repo, and PR. +func (c *Client) GetAllCommentsForPR(ctx context.Context, owner string, repo string, number int) ([]*ghapi.IssueComment, error) { + res, _, err := c.GetIssuesService().ListComments(ctx, owner, repo, number, &ghapi.IssueListCommentsOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get all comments for GitHub owner/repo/PR %s/%s/%d: %w", owner, repo, number, err) + } + + if len(res) == 0 { + c.logger.Info("Found no comments for PR", "PR", number) + return nil, nil + } + + return res, nil +} + // CommitStatusExists returns if a match is found for the SHA, state, context and decription. func (c *Client) CommitStatusExists(res []*ghapi.RepoStatus, commitStatus *CommitStatusAdapter) (bool, error) { for _, cs := range res { @@ -422,7 +455,7 @@ func (c *Client) CommitStatusExists(res []*ghapi.RepoStatus, commitStatus *Commi func (c *Client) CreateComment(ctx context.Context, owner string, repo string, issueNumber int, body string) (int64, error) { comment, _, err := c.GetIssuesService().CreateComment(ctx, owner, repo, issueNumber, &ghapi.IssueComment{Body: &body}) if err != nil { - return 0, err + return 0, fmt.Errorf("failed to create a comment for GitHub owner/repo/PR %s/%s/%d: %w", owner, repo, issueNumber, err) } c.logger.Info("Created comment", @@ -434,11 +467,27 @@ func (c *Client) CreateComment(ctx context.Context, owner string, repo string, i return *comment.ID, nil } +// EditComment edits an existing issue comment via the GitHub API. +func (c *Client) EditComment(ctx context.Context, owner string, repo string, commentID int64, body string) (int64, error) { + comment, _, err := c.GetIssuesService().EditComment(ctx, owner, repo, commentID, &ghapi.IssueComment{Body: &body}) + if err != nil { + return 0, fmt.Errorf("failed to edit an existing comment for GitHub owner/repo/comment %s/%s/%d: %w", owner, repo, commentID, err) + } + + c.logger.Info("Edited comment", + "ID", comment.ID, + "Owner", owner, + "Repository", repo, + "commentID", commentID, + ) + return *comment.ID, nil +} + // CreateCommitStatus creates a repository commit status via the GitHub API. func (c *Client) CreateCommitStatus(ctx context.Context, owner string, repo string, SHA string, state string, description string, statusContext string) (int64, error) { status, _, err := c.GetRepositoriesService().CreateStatus(ctx, owner, repo, SHA, &ghapi.RepoStatus{State: &state, Description: &description, Context: &statusContext}) if err != nil { - return 0, err + return 0, fmt.Errorf("failed to create an existing commitStatus for GitHub owner/repo/ref %s/%s/%s: %w", owner, repo, SHA, err) } c.logger.Info("Created commit status", diff --git a/git/github/github_test.go b/git/github/github_test.go index b47b252f8..bc61e3cd7 100644 --- a/git/github/github_test.go +++ b/git/github/github_test.go @@ -110,6 +110,21 @@ func (MockIssuesService) CreateComment( return &ghapi.IssueComment{ID: &id}, nil, nil } +// ListComments implements github.IssuesService +func (MockIssuesService) ListComments(ctx context.Context, owner string, repo string, + number int, opts *ghapi.IssueListCommentsOptions) ([]*ghapi.IssueComment, *ghapi.Response, error) { + var id int64 = 40 + var body string = "Integration test for snapshot snapshotName and scenario scenarioName" + issueComments := []*ghapi.IssueComment{{ID: &id, Body: &body}} + return issueComments, nil, nil +} + +// EditComment implements github.IssuesService +func (MockIssuesService) EditComment(ctx context.Context, owner string, repo string, number int64, comment *ghapi.IssueComment, +) (*ghapi.IssueComment, *ghapi.Response, error) { + return &ghapi.IssueComment{ID: &number}, nil, nil +} + type MockRepositoriesService struct{} // CreateStatus implements github.RepositoriesService @@ -309,4 +324,19 @@ var _ = Describe("Client", func() { Expect(commitStatusExist).To(BeFalse()) Expect(err).To(BeNil()) }) + + It("can get existing comment id", func() { + comments, err := client.GetAllCommentsForPR(context.TODO(), "", "", 1) + Expect(err).To(BeNil()) + Expect(len(comments) > 0).To(BeTrue()) + + commentID := client.GetExistingCommentID(comments, "snapshotName", "scenarioName") + Expect(*commentID).To(Equal(int64(40))) + }) + + It("can edit comments", func() { + id, err := client.EditComment(context.TODO(), "", "", 1, "example-comment") + Expect(err).To(BeNil()) + Expect(id).To(Equal(int64(1))) + }) }) diff --git a/status/format.go b/status/format.go index 356e741a8..1ec6981d6 100644 --- a/status/format.go +++ b/status/format.go @@ -66,8 +66,8 @@ func FormatSummary(taskRuns []*helpers.TaskRun) (string, error) { return buf.String(), nil } -// FormatComment builds a markdown comment for a list of integration TaskRuns. -func FormatComment(title string, results []*helpers.TaskRun) (string, error) { +// FormatCommentForFinishedPipelineRun builds a markdown comment for a list of finished integration TaskRuns +func FormatCommentForFinishedPipelineRun(title string, results []*helpers.TaskRun) (string, error) { summary, err := FormatSummary(results) if err != nil { return "", err @@ -82,6 +82,17 @@ func FormatComment(title string, results []*helpers.TaskRun) (string, error) { return buf.String(), nil } +// FormatCommentForDetail build a markdown comment for the details of unfinished integrationTest +func FormatCommentForDetail(title, detail string) (string, error) { + buf := bytes.Buffer{} + data := CommentTemplateData{Title: title, Summary: detail} + t := template.Must(template.New("").Parse(commentTemplate)) + if err := t.Execute(&buf, data); err != nil { + return "", err + } + return buf.String(), nil +} + // FormatStatus accepts a TaskRun and returns a Markdown friendly representation of its overall status, if any. func FormatStatus(taskRun *helpers.TaskRun) (string, error) { result, err := taskRun.GetTestResult() diff --git a/status/format_test.go b/status/format_test.go index 47bb370ab..25d8c893a 100644 --- a/status/format_test.go +++ b/status/format_test.go @@ -147,7 +147,7 @@ var _ = Describe("Formatters", func() { }) It("can construct a comment", func() { - comment, err := status.FormatComment("example-title", taskRuns) + comment, err := status.FormatCommentForFinishedPipelineRun("example-title", taskRuns) Expect(err).To(BeNil()) Expect(comment).To(ContainSubstring("### example-title")) Expect(comment).To(ContainSubstring(expectedSummary)) diff --git a/status/reporters.go b/status/reporters.go index 398a6dd65..f1d75fd81 100644 --- a/status/reporters.go +++ b/status/reporters.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "strconv" - "time" "github.com/go-logr/logr" pacv1alpha1 "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1" @@ -35,11 +34,10 @@ import ( tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - "knative.dev/pkg/apis" "sigs.k8s.io/controller-runtime/pkg/client" ) -// GitHubReporter reports status back to GitHub for a PipelineRun. +// GitHubReporter reports status back to GitHub for a Snapshot. type GitHubReporter struct { logger logr.Logger k8sClient client.Client @@ -157,98 +155,10 @@ func (r *GitHubReporter) getToken(ctx context.Context, object client.Object, nam return string(token), nil } -func (r *GitHubReporter) createCheckRunAdapter(k8sClient client.Client, ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) (*github.CheckRunAdapter, error) { - labels := pipelineRun.GetLabels() - - scenario, found := labels[gitops.SnapshotTestScenarioLabel] - if !found { - return nil, fmt.Errorf("PipelineRun label not found %q", gitops.SnapshotTestScenarioLabel) - } - - component, found := labels[gitops.SnapshotComponentLabel] - if !found { - return nil, fmt.Errorf("PipelineRun label not found %q", gitops.SnapshotComponentLabel) - } - - owner, found := labels[gitops.PipelineAsCodeURLOrgLabel] - if !found { - return nil, fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeURLOrgLabel) - } - - repo, found := labels[gitops.PipelineAsCodeURLRepositoryLabel] - if !found { - return nil, fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeURLRepositoryLabel) - } - - SHA, found := labels[gitops.PipelineAsCodeSHALabel] - if !found { - return nil, fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeSHALabel) - } - - var title, conclusion string - succeeded := pipelineRun.Status.GetCondition(apis.ConditionSucceeded) - - if succeeded.IsUnknown() { - title = scenario + " has started" - } else { - outcome, err := helpers.GetIntegrationPipelineRunOutcome(k8sClient, ctx, pipelineRun) - - if err != nil { - return nil, err - } - - if outcome.HasPipelineRunPassedTesting() { - title = scenario + " has succeeded" - conclusion = "success" - } else { - title = scenario + " has failed" - conclusion = "failure" - } - } - - taskRuns, err := helpers.GetAllChildTaskRunsForPipelineRun(r.k8sClient, ctx, pipelineRun) - if err != nil { - return nil, fmt.Errorf("error while getting all child taskRuns from pipelineRun %s: %w", pipelineRun.Name, err) - } - summary, err := FormatSummary(taskRuns) - if err != nil { - return nil, err - } - - startTime := time.Time{} - if start := pipelineRun.Status.StartTime; start != nil { - startTime = start.Time - } - - completionTime := time.Time{} - if complete := pipelineRun.Status.CompletionTime; complete != nil { - completionTime = complete.Time - } - - text := "" - if !succeeded.IsUnknown() { - text = succeeded.Message - } - - return &github.CheckRunAdapter{ - Owner: owner, - Repository: repo, - Name: NamePrefix + " / " + component + " / " + scenario, - SHA: SHA, - ExternalID: pipelineRun.Name, - Conclusion: conclusion, - Title: title, - Summary: summary, - Text: text, - StartTime: startTime, - CompletionTime: completionTime, - }, nil -} - -// generateSummary generate a string for the given state, snapshotName and scenarioName +// generateSummary generate a summary used in checkRun and commitStatus +// for the given state, snapshotName and scenarioName func generateSummary(state intgteststat.IntegrationTestStatus, snapshotName, scenarioName string) (string, error) { - var title string - + var summary string var statusDesc string = "is unknown" switch state { @@ -265,16 +175,68 @@ func generateSummary(state intgteststat.IntegrationTestStatus, snapshotName, sce case intgteststat.IntegrationTestStatusTestFail: statusDesc = "has failed" default: - return title, fmt.Errorf("unknown status") + return summary, fmt.Errorf("unknown status") } - title = fmt.Sprintf("Integration test for snapshot %s and scenario %s %s", snapshotName, scenarioName, statusDesc) + summary = fmt.Sprintf("Integration test for snapshot %s and scenario %s %s", snapshotName, scenarioName, statusDesc) + + return summary, nil +} + +// generateTitle generate a Title of checkRun for the given state +func generateCheckRunTitle(state intgteststat.IntegrationTestStatus) (string, error) { + var title string + + switch state { + case intgteststat.IntegrationTestStatusPending: + title = "Pending" + case intgteststat.IntegrationTestStatusInProgress: + title = "In Progress" + case intgteststat.IntegrationTestStatusEnvironmentProvisionError: + title = "Errored" + case intgteststat.IntegrationTestStatusDeploymentError: + title = "Errored" + case intgteststat.IntegrationTestStatusTestPassed: + title = "Succeeded" + case intgteststat.IntegrationTestStatusTestFail: + title = "Failed" + default: + return title, fmt.Errorf("unknown status") + } return title, nil } +// generateTitle generate a Text of checkRun for the given state +func generateCheckRunText(k8sClient client.Client, ctx context.Context, integrationTestStatusDetail intgteststat.IntegrationTestStatusDetail, namespace string) (string, error) { + if integrationTestStatusDetail.Status == intgteststat.IntegrationTestStatusTestPassed || integrationTestStatusDetail.Status == intgteststat.IntegrationTestStatusTestFail { + pipelineRunName := integrationTestStatusDetail.TestPipelineRunName + pipelineRun := &tektonv1beta1.PipelineRun{} + err := k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: pipelineRunName, + }, pipelineRun) + if err != nil { + return "", fmt.Errorf("error while getting the pipelineRun %s: %w", pipelineRunName, err) + } + + taskRuns, err := helpers.GetAllChildTaskRunsForPipelineRun(k8sClient, ctx, pipelineRun) + if err != nil { + return "", fmt.Errorf("error while getting all child taskRuns from pipelineRun %s: %w", pipelineRunName, err) + } + text, err := FormatSummary(taskRuns) + if err != nil { + return "", err + } + return text, nil + } else { + text := integrationTestStatusDetail.Details + return text, nil + } +} + // generateCheckRunConclusion generate a conclusion as the conclusion of CheckRun -// can be Can be one of: action_required, cancelled, failure, neutral, success, skipped, stale, timed_out +// Can be one of: action_required, cancelled, failure, neutral, success, skipped, stale, timed_out // https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28#create-a-check-run func generateCheckRunConclusion(state intgteststat.IntegrationTestStatus) (string, error) { var conclusion string @@ -317,7 +279,8 @@ func generateCommitState(state intgteststat.IntegrationTestStatus) (string, erro // createCheckRunAdapterForSnapshot create a CheckRunAdapter for given snapshot, integrationTestStatusDetail, owner, repo and sha to create a checkRun // https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28#create-a-check-run -func (r *GitHubReporter) createCheckRunAdapterForSnapshot(snapshot *applicationapiv1alpha1.Snapshot, integrationTestStatusDetail intgteststat.IntegrationTestStatusDetail, owner, repo, sha string) (*github.CheckRunAdapter, error) { +func (r *GitHubReporter) createCheckRunAdapterForSnapshot(ctx context.Context, snapshot *applicationapiv1alpha1.Snapshot, integrationTestStatusDetail intgteststat.IntegrationTestStatusDetail, owner, repo, sha string) (*github.CheckRunAdapter, error) { + var text string snapshotName := snapshot.Name scenarioName := integrationTestStatusDetail.ScenarioName @@ -326,11 +289,21 @@ func (r *GitHubReporter) createCheckRunAdapterForSnapshot(snapshot *applicationa return nil, fmt.Errorf("unknown status %s for integrationTestScenario %s and snapshot %s/%s", integrationTestStatusDetail.Status, scenarioName, snapshot.Namespace, snapshot.Name) } + title, err := generateCheckRunTitle(integrationTestStatusDetail.Status) + if err != nil { + return nil, fmt.Errorf("unknown status %s for integrationTestScenario %s and snapshot %s/%s", integrationTestStatusDetail.Status, scenarioName, snapshot.Namespace, snapshot.Name) + } + summary, err := generateSummary(integrationTestStatusDetail.Status, snapshotName, scenarioName) if err != nil { return nil, fmt.Errorf("unknown status %s for integrationTestScenario %s and snapshot %s/%s", integrationTestStatusDetail.Status, scenarioName, snapshot.Namespace, snapshot.Name) } + text, err = generateCheckRunText(r.k8sClient, ctx, integrationTestStatusDetail, snapshot.Namespace) + if err != nil { + return nil, fmt.Errorf("experienced error when generating text for checkRun: %w", err) + } + cra := &github.CheckRunAdapter{ Owner: owner, Repository: repo, @@ -338,10 +311,9 @@ func (r *GitHubReporter) createCheckRunAdapterForSnapshot(snapshot *applicationa SHA: sha, ExternalID: scenarioName, Conclusion: conclusion, - Title: conclusion, - // This summary will be reworked once PLNSRVCE-1295 is implemented in the future - Summary: summary, - Text: integrationTestStatusDetail.Details, + Title: title, + Summary: summary, + Text: text, } if start := integrationTestStatusDetail.StartTime; start != nil { @@ -355,69 +327,6 @@ func (r *GitHubReporter) createCheckRunAdapterForSnapshot(snapshot *applicationa return cra, nil } -func (r *GitHubReporter) createCommitStatus(k8sClient client.Client, ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) error { - var ( - state string - description string - ) - - labels := pipelineRun.GetLabels() - - scenario, found := labels[gitops.SnapshotTestScenarioLabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.SnapshotTestScenarioLabel) - } - - component, found := labels[gitops.SnapshotComponentLabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.SnapshotComponentLabel) - } - - owner, found := labels[gitops.PipelineAsCodeURLOrgLabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeURLOrgLabel) - } - - repo, found := labels[gitops.PipelineAsCodeURLRepositoryLabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeURLRepositoryLabel) - } - - SHA, found := labels[gitops.PipelineAsCodeSHALabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeSHALabel) - } - - statusContext := NamePrefix + " / " + component + " / " + scenario - - succeeded := pipelineRun.Status.GetCondition(apis.ConditionSucceeded) - - if succeeded.IsUnknown() { - state = "pending" - description = scenario + " has started" - } else { - outcome, err := helpers.GetIntegrationPipelineRunOutcome(k8sClient, ctx, pipelineRun) - if err != nil { - return err - } - - if outcome.HasPipelineRunPassedTesting() { - state = "success" - description = scenario + " has succeeded" - } else { - state = "failure" - description = scenario + " has failed" - } - } - - _, err := r.client.CreateCommitStatus(ctx, owner, repo, SHA, state, description, statusContext) - if err != nil { - return err - } - - return nil -} - // createCommitStatusAdapterForSnapshot create a commitStatusAdapter used to create commitStatus on GitHub // https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#create-a-commit-status func (r *GitHubReporter) createCommitStatusAdapterForSnapshot(snapshot *applicationapiv1alpha1.Snapshot, integrationTestStatusDetail intgteststat.IntegrationTestStatusDetail, owner, repo, sha string) (*github.CommitStatusAdapter, error) { @@ -445,32 +354,11 @@ func (r *GitHubReporter) createCommitStatusAdapterForSnapshot(snapshot *applicat }, nil } -func (r *GitHubReporter) createComment(k8sClient client.Client, ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) error { - labels := pipelineRun.GetLabels() - - succeeded := pipelineRun.Status.GetCondition(apis.ConditionSucceeded) - if succeeded.IsUnknown() { - return nil - } - - scenario, found := labels[gitops.SnapshotTestScenarioLabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.SnapshotTestScenarioLabel) - } - - owner, found := labels[gitops.PipelineAsCodeURLOrgLabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeURLOrgLabel) - } - - repo, found := labels[gitops.PipelineAsCodeURLRepositoryLabel] - if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeURLRepositoryLabel) - } - - issueNumberStr, found := pipelineRun.GetAnnotations()[gitops.PipelineAsCodePullRequestAnnotation] +// UpdateStatusInComment will create/update a comment in PR which creates snapshot +func (r *GitHubReporter) UpdateStatusInComment(k8sClient client.Client, ctx context.Context, snapshot *applicationapiv1alpha1.Snapshot, integrationTestStatusDetail intgteststat.IntegrationTestStatusDetail, repo, owner string) error { + issueNumberStr, found := snapshot.GetAnnotations()[gitops.PipelineAsCodePullRequestAnnotation] if !found { - return fmt.Errorf("PipelineRun label not found %q", gitops.PipelineAsCodeURLRepositoryLabel) + return fmt.Errorf("pull-request annotation not found %q", gitops.PipelineAsCodePullRequestAnnotation) } issueNumber, err := strconv.Atoi(issueNumberStr) @@ -478,100 +366,61 @@ func (r *GitHubReporter) createComment(k8sClient client.Client, ctx context.Cont return err } - outcome, err := helpers.GetIntegrationPipelineRunOutcome(k8sClient, ctx, pipelineRun) - if err != nil { - return err - } - - var title string - if outcome.HasPipelineRunPassedTesting() { - title = scenario + " has succeeded" - } else { - title = scenario + " has failed" - } - - taskRuns, err := helpers.GetAllChildTaskRunsForPipelineRun(r.k8sClient, ctx, pipelineRun) - if err != nil { - return fmt.Errorf("error while getting all child taskRuns from pipelineRun %s: %w", pipelineRun.Name, err) - } - comment, err := FormatComment(title, taskRuns) - if err != nil { - return err - } - - _, err = r.client.CreateComment(ctx, owner, repo, issueNumber, comment) + state := integrationTestStatusDetail.Status + title, err := generateSummary(state, snapshot.Name, integrationTestStatusDetail.ScenarioName) if err != nil { - return err + return fmt.Errorf("unknown status %s for integrationTestScenario %s and snapshot %s/%s", integrationTestStatusDetail.Status, integrationTestStatusDetail.ScenarioName, snapshot.Namespace, snapshot.Name) } - return nil -} - -// ReportStatus creates/updates CheckRuns when using GitHub App integration. -// When using GitHub webhook integration a commit status and, in some cases, a comment is created. -func (r *GitHubReporter) ReportStatus(k8sClient client.Client, ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) error { - if !metadata.HasLabelWithValue(pipelineRun, gitops.PipelineAsCodeEventTypeLabel, gitops.PipelineAsCodePullRequestType) { - return nil - } - - // Existence of the Pipelines as Code installation ID annotation signals configuration using GitHub App integration. - // If it doesn't exist, GitHub webhook integration is configured. - if metadata.HasAnnotation(pipelineRun, gitops.PipelineAsCodeInstallationIDAnnotation) { - creds, err := r.getAppCredentials(ctx, pipelineRun) + var comment string + if state == intgteststat.IntegrationTestStatusTestPassed || state == intgteststat.IntegrationTestStatusTestFail { + pipelineRunName := integrationTestStatusDetail.TestPipelineRunName + pipelineRun := &tektonv1beta1.PipelineRun{} + err := r.k8sClient.Get(ctx, types.NamespacedName{ + Namespace: snapshot.Namespace, + Name: pipelineRunName, + }, pipelineRun) if err != nil { - return err + return fmt.Errorf("error while getting the pipelineRun %s: %w", pipelineRunName, err) } - token, err := r.client.CreateAppInstallationToken(ctx, creds.AppID, creds.InstallationID, creds.PrivateKey) + taskRuns, err := helpers.GetAllChildTaskRunsForPipelineRun(r.k8sClient, ctx, pipelineRun) if err != nil { - return err + return fmt.Errorf("error while getting all child taskRuns from pipelineRun %s: %w", pipelineRunName, err) } - - r.client.SetOAuthToken(ctx, token) - - checkRun, err := r.createCheckRunAdapter(k8sClient, ctx, pipelineRun) + comment, err = FormatCommentForFinishedPipelineRun(title, taskRuns) if err != nil { - return err - } - - checkRunID, err := r.client.GetCheckRunID(ctx, checkRun.Owner, checkRun.Repository, checkRun.SHA, checkRun.ExternalID, creds.AppID) - if err != nil { - return err - } - - if checkRunID == nil { - _, err = r.client.CreateCheckRun(ctx, checkRun) - } else { - err = r.client.UpdateCheckRun(ctx, *checkRunID, checkRun) - } - - if err != nil { - return err + return fmt.Errorf("error while formating all child taskRuns from pipelineRun %s: %w", pipelineRun.Name, err) } } else { - token, err := r.getToken(ctx, pipelineRun, pipelineRun.Namespace) + comment, err = FormatCommentForDetail(title, integrationTestStatusDetail.Details) if err != nil { return err } + } - r.client.SetOAuthToken(ctx, token) - - err = r.createCommitStatus(k8sClient, ctx, pipelineRun) + allComments, err := r.client.GetAllCommentsForPR(ctx, owner, repo, issueNumber) + if err != nil { + return fmt.Errorf("error while getting all comments for pull-request %s: %w", issueNumberStr, err) + } + existingCommentId := r.client.GetExistingCommentID(allComments, snapshot.Name, integrationTestStatusDetail.ScenarioName) + if existingCommentId == nil { + _, err = r.client.CreateComment(ctx, owner, repo, issueNumber, comment) if err != nil { - return err + return fmt.Errorf("error while creating comment for pull-request %s: %w", issueNumberStr, err) } - - err = r.createComment(k8sClient, ctx, pipelineRun) + } else { + _, err = r.client.EditComment(ctx, owner, repo, *existingCommentId, comment) if err != nil { - return err + return fmt.Errorf("error while updating comment for pull-request %s: %w", issueNumberStr, err) } } return nil } -// ReportStatusForSnapshot creates CheckRun when using GitHub App integration, -// creates a a commit status when using GitHub webhook integration +// ReportStatusForSnapshot creates CheckRun when using GitHub App integration. +// When using GitHub webhook integration it creates a commit status and a comment. func (r *GitHubReporter) ReportStatusForSnapshot(k8sClient client.Client, ctx context.Context, logger *helpers.IntegrationLogger, snapshot *applicationapiv1alpha1.Snapshot) error { statuses, err := gitops.NewSnapshotIntegrationTestStatusesFromSnapshot(snapshot) if err != nil { @@ -625,7 +474,7 @@ func (r *GitHubReporter) ReportStatusForSnapshot(k8sClient client.Client, ctx co for _, integrationTestStatusDetail := range integrationTestStatusDetails { integrationTestStatusDetail := *integrationTestStatusDetail // G601 - checkRun, err := r.createCheckRunAdapterForSnapshot(snapshot, integrationTestStatusDetail, owner, repo, sha) + checkRun, err := r.createCheckRunAdapterForSnapshot(ctx, snapshot, integrationTestStatusDetail, owner, repo, sha) if err != nil { logger.Error(err, "failed to create checkRunAdapter for snapshot", "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name) @@ -698,6 +547,13 @@ func (r *GitHubReporter) ReportStatusForSnapshot(k8sClient client.Client, ctx co if err != nil { return err } + // Create a comment when integration test is neither pending nor inprogress since comment for pending/inprogress is less meaningful and there is commitStatus for all statuses + if integrationTestStatusDetail.Status != intgteststat.IntegrationTestStatusPending && integrationTestStatusDetail.Status != intgteststat.IntegrationTestStatusInProgress { + err = r.UpdateStatusInComment(k8sClient, ctx, snapshot, integrationTestStatusDetail, repo, owner) + if err != nil { + return err + } + } } else { logger.Info("found existing commitStatus for scenario test status of snapshot, no need to create new commit status", "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name, "scenarioName", integrationTestStatusDetail.ScenarioName) diff --git a/status/reporters_test.go b/status/reporters_test.go index c8056c85f..ed6b04a89 100644 --- a/status/reporters_test.go +++ b/status/reporters_test.go @@ -17,9 +17,8 @@ limitations under the License. package status_test import ( + "bytes" "context" - "fmt" - "strings" "time" "k8s.io/apimachinery/pkg/api/meta" @@ -36,10 +35,10 @@ import ( "github.com/redhat-appstudio/integration-service/helpers" "github.com/redhat-appstudio/integration-service/status" tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "github.com/tonglil/buflogr" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "knative.dev/pkg/apis" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -75,6 +74,12 @@ type CreateCommentResult struct { issueNumber int } +type EditCommentResult struct { + ID int64 + Error error + body string +} + type CreateCommitStatusResult struct { ID int64 Error error @@ -91,6 +96,7 @@ type MockGitHubClient struct { GetCheckRunResult CreateCommentResult CreateCommitStatusResult + EditCommentResult } func (c *MockGitHubClient) CreateAppInstallationToken(ctx context.Context, appID int64, installationID int64, privateKey []byte) (string, error) { @@ -122,6 +128,13 @@ func (c *MockGitHubClient) GetExistingCheckRun(checkRuns []*ghapi.CheckRun, cra } func (c *MockGitHubClient) CommitStatusExists(res []*ghapi.RepoStatus, commitStatus *github.CommitStatusAdapter) (bool, error) { + for _, cs := range res { + if *cs.State == commitStatus.State && *cs.Description == commitStatus.Description && *cs.Context == commitStatus.Context { + return true, nil + } else { + return false, nil + } + } return false, nil } @@ -131,7 +144,25 @@ func (c *MockGitHubClient) CreateComment(ctx context.Context, owner string, repo return c.CreateCommentResult.ID, c.CreateCommentResult.Error } +func (c *MockGitHubClient) EditComment(ctx context.Context, owner string, repo string, commentID int64, body string) (int64, error) { + c.EditCommentResult.body = body + c.EditCommentResult.ID = commentID + return c.EditCommentResult.ID, c.EditCommentResult.Error +} + +func (c *MockGitHubClient) GetAllCommentsForPR(ctx context.Context, owner string, repo string, pr int) ([]*ghapi.IssueComment, error) { + var id int64 = 20 + comments := []*ghapi.IssueComment{{ID: &id}} + return comments, nil +} + +func (c *MockGitHubClient) GetExistingCommentID(comments []*ghapi.IssueComment, snapshotName, scenarioName string) *int64 { + return nil +} + func (c *MockGitHubClient) CreateCommitStatus(ctx context.Context, owner string, repo string, SHA string, state string, description string, statusContext string) (int64, error) { + var id int64 = 60 + c.CreateCommitStatusResult.ID = id c.CreateCommitStatusResult.state = state c.CreateCommitStatusResult.description = description c.CreateCommitStatusResult.statusContext = statusContext @@ -150,8 +181,10 @@ func (c *MockGitHubClient) GetAllCheckRunsForRef( func (c *MockGitHubClient) GetAllCommitStatusesForRef( ctx context.Context, owner, repo, sha string) ([]*ghapi.RepoStatus, error) { var id int64 = 60 - var state = "success" - repoStatus := &ghapi.RepoStatus{ID: &id, State: &state} + var state = "pending" + var description = "Integration test for snapshot snapshot-sample and scenario scenario2 is pending" + var statusContext = "Red Hat Trusted App Test / snapshot-sample / scenario2" + repoStatus := &ghapi.RepoStatus{ID: &id, State: &state, Context: &statusContext, Description: &description} return []*ghapi.RepoStatus{repoStatus}, nil } @@ -227,25 +260,6 @@ func (c *MockK8sClient) RESTMapper() meta.RESTMapper { panic("implement me") } -func setPipelineRunOutcome(pipelineRun *tektonv1beta1.PipelineRun, taskRun *tektonv1beta1.TaskRun) { - pipelineRun.Status = tektonv1beta1.PipelineRunStatus{ - PipelineRunStatusFields: tektonv1beta1.PipelineRunStatusFields{ - CompletionTime: &metav1.Time{Time: time.Now()}, - ChildReferences: []tektonv1beta1.ChildStatusReference{ - { - Name: taskRun.Name, - PipelineTaskName: "pipeline1-task1", - }, - }, - }, - } - pipelineRun.Status.SetCondition(&apis.Condition{ - Type: apis.ConditionSucceeded, - Message: "sample msg", - Status: "True", - }) -} - var _ = Describe("GitHubReporter", func() { var reporter *status.GitHubReporter @@ -432,7 +446,6 @@ var _ = Describe("GitHubReporter", func() { var secretData map[string][]byte BeforeEach(func() { - pipelineRun.Annotations["pac.test.appstudio.openshift.io/installation-id"] = "123" hasSnapshot.Annotations["pac.test.appstudio.openshift.io/installation-id"] = "123" secretData = map[string][]byte{ @@ -454,6 +467,11 @@ var _ = Describe("GitHubReporter", func() { taskRun.Status = skippedTaskRun.Status } } + if plr, ok := obj.(*tektonv1beta1.PipelineRun); ok { + if key.Name == pipelineRun.Name { + plr.Status = pipelineRun.Status + } + } }, listInterceptor: func(list client.ObjectList) {}, } @@ -463,86 +481,41 @@ var _ = Describe("GitHubReporter", func() { }) It("doesn't report status for non-pull request events", func() { - delete(pipelineRun.Labels, "pac.test.appstudio.openshift.io/event-type") - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) delete(hasSnapshot.Labels, "pac.test.appstudio.openshift.io/event-type") Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) }) It("doesn't report status when the credentials are invalid/missing", func() { // Invalid installation ID value - pipelineRun.Annotations["pac.test.appstudio.openshift.io/installation-id"] = "bad-installation-id" - err := reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun) - Expect(err).ToNot(BeNil()) - pipelineRun.Annotations["pac.test.appstudio.openshift.io/installation-id"] = "123" - hasSnapshot.Annotations["pac.test.appstudio.openshift.io/installation-id"] = "bad-installation-id" - err = reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot) + err := reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot) Expect(err).ToNot(BeNil()) hasSnapshot.Annotations["pac.test.appstudio.openshift.io/installation-id"] = "123" // Invalid app ID value secretData["github-application-id"] = []byte("bad-app-id") - err = reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun) - Expect(err).ToNot(BeNil()) err = reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot) Expect(err).ToNot(BeNil()) secretData["github-application-id"] = []byte("456") // Missing app ID value delete(secretData, "github-application-id") - err = reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun) - Expect(err).ToNot(BeNil()) err = reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot) Expect(err).ToNot(BeNil()) secretData["github-application-id"] = []byte("456") // Missing private key delete(secretData, "github-private-key") - err = reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun) - Expect(err).ToNot(BeNil()) err = reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot) Expect(err).ToNot(BeNil()) }) - It("reports status via CheckRuns", func() { - // Create an in progress CheckRun - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.CreateCheckRunResult.cra.Title).To(Equal("example-pass has started")) - Expect(mockGitHubClient.CreateCheckRunResult.cra.Conclusion).To(Equal("")) - Expect(mockGitHubClient.CreateCheckRunResult.cra.ExternalID).To(Equal(pipelineRun.Name)) - Expect(mockGitHubClient.CreateCheckRunResult.cra.Owner).To(Equal("devfile-sample")) - Expect(mockGitHubClient.CreateCheckRunResult.cra.Repository).To(Equal("devfile-sample-go-basic")) - Expect(mockGitHubClient.CreateCheckRunResult.cra.SHA).To(Equal("12a4a35ccd08194595179815e4646c3a6c08bb77")) - Expect(mockGitHubClient.CreateCheckRunResult.cra.Name).To(Equal("Red Hat Trusted App Test / devfile-sample-go-basic / example-pass")) - Expect(mockGitHubClient.CreateCheckRunResult.cra.StartTime.IsZero()).To(BeFalse()) - Expect(mockGitHubClient.CreateCheckRunResult.cra.CompletionTime.IsZero()).To(BeTrue()) - Expect(mockGitHubClient.CreateCheckRunResult.cra.Text).To(Equal("")) - - // Update existing CheckRun w/success - setPipelineRunOutcome(pipelineRun, successfulTaskRun) - var id int64 = 1 - mockGitHubClient.GetCheckRunIDResult.ID = &id - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.UpdateCheckRunResult.cra.Title).To(Equal("example-pass has succeeded")) - Expect(mockGitHubClient.UpdateCheckRunResult.cra.Conclusion).To(Equal("success")) - Expect(mockGitHubClient.UpdateCheckRunResult.cra.CompletionTime.IsZero()).To(BeFalse()) - Expect(mockGitHubClient.UpdateCheckRunResult.cra.Text).To(Equal("sample msg")) - - // Update existing CheckRun w/failure - setPipelineRunOutcome(pipelineRun, failedTaskRun) - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.UpdateCheckRunResult.cra.Title).To(Equal("example-pass has failed")) - Expect(mockGitHubClient.UpdateCheckRunResult.cra.Conclusion).To(Equal("failure")) - }) - It("reports snapshot tests status via CheckRuns", func() { // Create an pending CheckRun hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"Pending\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"pending\"}]" Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) Expect(mockGitHubClient.CreateCheckRunResult.cra.Summary).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 is pending")) Expect(mockGitHubClient.CreateCheckRunResult.cra.Conclusion).To(Equal("")) - fmt.Fprintf(GinkgoWriter, "-------Time: %v\n", mockGitHubClient.CreateCheckRunResult.cra.StartTime) // Update existing CheckRun w/inprogress hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"InProgress\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"Failed to find deploymentTargetClass with right provisioner for copy of existingEnvironment\"}]" @@ -576,17 +549,18 @@ var _ = Describe("GitHubReporter", func() { Expect(mockGitHubClient.UpdateCheckRunResult.cra.Conclusion).To(Equal(gitops.IntegrationTestStatusFailureGithub)) Expect(mockGitHubClient.UpdateCheckRunResult.cra.CompletionTime.IsZero()).To(BeFalse()) - hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestFail\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"failed\"}]" + hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestFail\",\"testPipelineRunName\":\"test-pipelinerun\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"failed\"}]" Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) Expect(mockGitHubClient.UpdateCheckRunResult.cra.Summary).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 has failed")) Expect(mockGitHubClient.UpdateCheckRunResult.cra.Conclusion).To(Equal(gitops.IntegrationTestStatusFailureGithub)) Expect(mockGitHubClient.UpdateCheckRunResult.cra.CompletionTime.IsZero()).To(BeFalse()) // Update existing CheckRun w/success - hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestPassed\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"failed\"}]" + hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestPassed\",\"testPipelineRunName\":\"test-pipelinerun\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"failed\"}]" Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) Expect(mockGitHubClient.UpdateCheckRunResult.cra.Summary).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 has passed")) Expect(mockGitHubClient.UpdateCheckRunResult.cra.Conclusion).To(Equal(gitops.IntegrationTestStatusSuccessGithub)) + Expect(mockGitHubClient.UpdateCheckRunResult.cra.Title).To(Equal("Succeeded")) }) }) @@ -594,9 +568,10 @@ var _ = Describe("GitHubReporter", func() { var secretData map[string][]byte var repo pacv1alpha1.Repository + var buf bytes.Buffer + log := helpers.IntegrationLogger{Logger: buflogr.NewWithBuffer(&buf)} BeforeEach(func() { - pipelineRun.Annotations["pac.test.appstudio.openshift.io/pull-request"] = "999" hasSnapshot.Annotations["pac.test.appstudio.openshift.io/pull-request"] = "999" repo = pacv1alpha1.Repository{ @@ -625,6 +600,11 @@ var _ = Describe("GitHubReporter", func() { taskRun.Status = skippedTaskRun.Status } } + if plr, ok := obj.(*tektonv1beta1.PipelineRun); ok { + if key.Name == pipelineRun.Name { + plr.Status = pipelineRun.Status + } + } }, listInterceptor: func(list client.ObjectList) { if repoList, ok := list.(*pacv1alpha1.RepositoryList); ok { @@ -641,88 +621,58 @@ var _ = Describe("GitHubReporter", func() { reporter = status.NewGitHubReporter(logr.Discard(), mockK8sClient, status.WithGitHubClient(mockGitHubClient)) }) - It("doesn't report status for non-pull request events", func() { - delete(pipelineRun.Labels, "pac.test.appstudio.openshift.io/event-type") - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - }) - - It("creates a comment for a succeeded PipelineRun", func() { - pipelineRun.Status.SetCondition(&apis.Condition{ - Type: apis.ConditionSucceeded, - Status: "True", - }) - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.CreateCommentResult.body).To(ContainSubstring("# example-pass has succeeded")) - Expect(mockGitHubClient.CreateCommentResult.issueNumber).To(Equal(999)) - }) - - It("creates a comment for a failed PipelineRun", func() { - setPipelineRunOutcome(pipelineRun, failedTaskRun) - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - called := strings.Contains(mockGitHubClient.CreateCommentResult.body, "# example-pass has failed") - Expect(called).To(BeTrue()) - Expect(mockGitHubClient.CreateCommentResult.issueNumber).To(Equal(999)) - }) - - It("doesn't create a comment for non-completed PipelineRuns", func() { - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.CreateCommentResult.body).To(Equal("")) - Expect(mockGitHubClient.CreateCommentResult.issueNumber).To(Equal(0)) - }) - - It("creates a commit status", func() { - // In progress - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal("pending")) - Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("example-pass has started")) - Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / devfile-sample-go-basic / example-pass")) - - // Success - pipelineRun.Status.SetCondition(&apis.Condition{ - Type: apis.ConditionSucceeded, - Status: "True", - }) - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal("success")) - Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("example-pass has succeeded")) - Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / devfile-sample-go-basic / example-pass")) - - // Failure - setPipelineRunOutcome(pipelineRun, failedTaskRun) - Expect(reporter.ReportStatus(mockK8sClient, context.TODO(), pipelineRun)).To(BeNil()) - Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal("failure")) - Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("example-pass has failed")) - Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / devfile-sample-go-basic / example-pass")) - }) - It("creates a commit status for snapshot", func() { - // Error + // EnvironmentProvisionError hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"EnvironmentProvisionError\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"failed\"}]" - Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal(gitops.IntegrationTestStatusErrorGithub)) Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 experienced an error when provisioning environment")) Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / snapshot-sample / scenario1")) + Expect(mockGitHubClient.CreateCommentResult.body).Should(ContainSubstring("experienced an error when provisioning environment")) + // DeploymentError hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"DeploymentError\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"failed\"}]" Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal(gitops.IntegrationTestStatusErrorGithub)) Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 experienced an error when deploying snapshotEnvironmentBinding")) Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / snapshot-sample / scenario1")) + Expect(mockGitHubClient.CreateCommentResult.body).Should(ContainSubstring("experienced an error when deploying snapshotEnvironmentBinding")) // Success - hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestPassed\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"passed\"}]" + hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestPassed\",\"testPipelineRunName\":\"test-pipelinerun\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"passed\"}]" Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal(gitops.IntegrationTestStatusSuccessGithub)) Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 has passed")) Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / snapshot-sample / scenario1")) + Expect(mockGitHubClient.CreateCommentResult.body).Should(ContainSubstring("has passed")) - // Failure - hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestFail\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"passed\"}]" + // TestFail + hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"TestFail\",\"testPipelineRunName\":\"test-pipelinerun\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"failed\"}]" Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal(gitops.IntegrationTestStatusFailureGithub)) Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 has failed")) Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / snapshot-sample / scenario1")) + Expect(mockGitHubClient.CreateCommentResult.body).Should(ContainSubstring("has failed")) + + // InProgress + hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"InProgress\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"In progress\"}]" + Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &logger, hasSnapshot)).To(BeNil()) + Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal(gitops.IntegrationTestStatusPendingGithub)) + Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 is in progress")) + Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / snapshot-sample / scenario1")) + + // Pending + hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario1\",\"status\":\"Pending\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"pending\"}]" + Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &log, hasSnapshot)).To(BeNil()) + Expect(mockGitHubClient.CreateCommitStatusResult.state).To(Equal(gitops.IntegrationTestStatusPendingGithub)) + Expect(mockGitHubClient.CreateCommitStatusResult.description).To(Equal("Integration test for snapshot snapshot-sample and scenario scenario1 is pending")) + Expect(mockGitHubClient.CreateCommitStatusResult.statusContext).To(Equal("Red Hat Trusted App Test / snapshot-sample / scenario1")) + + // One commitStatus exists already + hasSnapshot.Annotations["test.appstudio.openshift.io/status"] = "[{\"scenario\":\"scenario2\",\"status\":\"Pending\",\"lastUpdateTime\":\"2023-08-26T17:57:49+02:00\",\"details\":\"pending\"}]" + Expect(reporter.ReportStatusForSnapshot(mockK8sClient, context.TODO(), &log, hasSnapshot)).To(BeNil()) + expectedLogEntry := "found existing commitStatus for scenario test status of snapshot, no need to create new commit status" + Expect(buf.String()).Should(ContainSubstring(expectedLogEntry)) }) }) }) diff --git a/status/status.go b/status/status.go index 5d62757bd..9b112a154 100644 --- a/status/status.go +++ b/status/status.go @@ -24,7 +24,6 @@ import ( "github.com/redhat-appstudio/integration-service/gitops" "github.com/redhat-appstudio/integration-service/helpers" "github.com/redhat-appstudio/operator-toolkit/metadata" - tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -33,7 +32,6 @@ const NamePrefix = "Red Hat Trusted App Test" // Reporter is a generic interface all status implementations must follow. type Reporter interface { - ReportStatus(client.Client, context.Context, *tektonv1beta1.PipelineRun) error ReportStatusForSnapshot(client.Client, context.Context, *helpers.IntegrationLogger, *applicationapiv1alpha1.Snapshot) error } diff --git a/status/status_test.go b/status/status_test.go index 0e620c320..5a4cea071 100644 --- a/status/status_test.go +++ b/status/status_test.go @@ -33,10 +33,6 @@ import ( type MockReporter struct{} -func (r *MockReporter) ReportStatus(client.Client, context.Context, *tektonv1beta1.PipelineRun) error { - return nil -} - func (r *MockReporter) ReportStatusForSnapshot(client.Client, context.Context, *helpers.IntegrationLogger, *applicationapiv1alpha1.Snapshot) error { return nil }