diff --git a/controllers/controllers.go b/controllers/controllers.go
index 8e571bba0..39e15041f 100644
--- a/controllers/controllers.go
+++ b/controllers/controllers.go
@@ -23,6 +23,7 @@ import (
"github.com/redhat-appstudio/integration-service/controllers/integrationpipeline"
"github.com/redhat-appstudio/integration-service/controllers/scenario"
"github.com/redhat-appstudio/integration-service/controllers/snapshot"
+ "github.com/redhat-appstudio/integration-service/controllers/statusreport"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
)
@@ -34,6 +35,7 @@ var setupFunctions = []func(manager.Manager, *logr.Logger) error{
snapshot.SetupController,
scenario.SetupController,
binding.SetupController,
+ statusreport.SetupController,
}
// SetupControllers invoke all SetupController functions defined in setupFunctions, setting all controllers up and
diff --git a/controllers/integrationpipeline/integrationpipeline_adapter_test.go b/controllers/integrationpipeline/integrationpipeline_adapter_test.go
index 0bf35cdcb..954a593e2 100644
--- a/controllers/integrationpipeline/integrationpipeline_adapter_test.go
+++ b/controllers/integrationpipeline/integrationpipeline_adapter_test.go
@@ -60,7 +60,12 @@ func (r *MockStatusReporter) ReportStatus(client.Client, context.Context, *tekto
return r.ReportStatusError
}
-func (a *MockStatusAdapter) GetReporters(pipelineRun *tektonv1beta1.PipelineRun) ([]status.Reporter, error) {
+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
}
diff --git a/controllers/statusreport/statusreport_adapter.go b/controllers/statusreport/statusreport_adapter.go
new file mode 100644
index 000000000..b8d346827
--- /dev/null
+++ b/controllers/statusreport/statusreport_adapter.go
@@ -0,0 +1,73 @@
+/*
+Copyright 2023.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package statusreport
+
+import (
+ "context"
+
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
+ "github.com/redhat-appstudio/integration-service/helpers"
+ "github.com/redhat-appstudio/integration-service/status"
+
+ "github.com/redhat-appstudio/integration-service/loader"
+ "github.com/redhat-appstudio/operator-toolkit/controller"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// Adapter holds the objects needed to reconcile a snapshot's test status report.
+type Adapter struct {
+ snapshot *applicationapiv1alpha1.Snapshot
+ application *applicationapiv1alpha1.Application
+ logger helpers.IntegrationLogger
+ loader loader.ObjectLoader
+ client client.Client
+ context context.Context
+ status status.Status
+}
+
+// NewAdapter creates and returns an Adapter instance.
+func NewAdapter(snapshot *applicationapiv1alpha1.Snapshot, application *applicationapiv1alpha1.Application, logger helpers.IntegrationLogger, loader loader.ObjectLoader, client client.Client,
+ context context.Context) *Adapter {
+ return &Adapter{
+ snapshot: snapshot,
+ application: application,
+ logger: logger,
+ loader: loader,
+ client: client,
+ context: context,
+ status: status.NewAdapter(logger.Logger, client),
+ }
+}
+
+// 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) {
+ reporters, err := a.status.GetReporters(a.snapshot)
+ if err != nil {
+ return controller.RequeueWithError(err)
+ }
+
+ for _, reporter := range reporters {
+ if err := reporter.ReportStatusForSnapshot(a.client, a.context, &a.logger, a.snapshot); err != nil {
+ a.logger.Error(err, "failed to report test status to github for snapshot",
+ "snapshot.Namespace", a.snapshot.Namespace, "snapshot.Name", a.snapshot.Name)
+ return controller.RequeueWithError(err)
+ }
+ }
+
+ return controller.ContinueProcessing()
+}
diff --git a/controllers/statusreport/statusreport_adapter_test.go b/controllers/statusreport/statusreport_adapter_test.go
new file mode 100644
index 000000000..ba84f19d8
--- /dev/null
+++ b/controllers/statusreport/statusreport_adapter_test.go
@@ -0,0 +1,166 @@
+/*
+Copyright 2023.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package statusreport
+
+import (
+ "context"
+ "reflect"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
+ "github.com/redhat-appstudio/integration-service/loader"
+ "github.com/redhat-appstudio/integration-service/status"
+ tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/redhat-appstudio/integration-service/gitops"
+ "github.com/redhat-appstudio/integration-service/helpers"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "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("Snapshot Adapter", Ordered, func() {
+ var (
+ adapter *Adapter
+ logger helpers.IntegrationLogger
+ statusAdapter *MockStatusAdapter
+ statusReporter *MockStatusReporter
+
+ hasApp *applicationapiv1alpha1.Application
+ hasSnapshot *applicationapiv1alpha1.Snapshot
+ )
+ const (
+ SampleRepoLink = "https://github.com/devfile-samples/devfile-sample-java-springboot-basic"
+ sample_image = "quay.io/redhat-appstudio/sample-image"
+ sample_revision = "random-value"
+ )
+
+ BeforeAll(func() {
+ hasApp = &applicationapiv1alpha1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "application-sample",
+ Namespace: "default",
+ },
+ Spec: applicationapiv1alpha1.ApplicationSpec{
+ DisplayName: "application-sample",
+ Description: "This is an example application",
+ },
+ }
+ Expect(k8sClient.Create(ctx, hasApp)).Should(Succeed())
+
+ hasSnapshot = &applicationapiv1alpha1.Snapshot{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "snapshot-sample",
+ Namespace: "default",
+ Labels: map[string]string{
+ gitops.SnapshotTypeLabel: "component",
+ gitops.SnapshotComponentLabel: "component-sample",
+ "build.appstudio.redhat.com/pipeline": "enterprise-contract",
+ gitops.PipelineAsCodeEventTypeLabel: "push",
+ gitops.PipelineAsCodeGitProviderLabel: gitops.PipelineAsCodeGitHubProviderType,
+ },
+ Annotations: map[string]string{
+ gitops.PipelineAsCodeInstallationIDAnnotation: "123",
+ "build.appstudio.redhat.com/commit_sha": "6c65b2fcaea3e1a0a92476c8b5dc89e92a85f025",
+ "appstudio.redhat.com/updateComponentOnSuccess": "false",
+ gitops.SnapshotTestsStatusAnnotation: "[{\"scenario\":\"scenario-1\",\"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 to find deploymentTargetClass with right provisioner for copy of existingEnvironment\"}]",
+ },
+ },
+ Spec: applicationapiv1alpha1.SnapshotSpec{
+ Application: hasApp.Name,
+ Components: []applicationapiv1alpha1.SnapshotComponent{
+ {
+ Name: "component-sample",
+ ContainerImage: sample_image,
+ Source: applicationapiv1alpha1.ComponentSource{
+ ComponentSourceUnion: applicationapiv1alpha1.ComponentSourceUnion{
+ GitSource: &applicationapiv1alpha1.GitSource{
+ Revision: sample_revision,
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ Expect(k8sClient.Create(ctx, hasSnapshot)).Should(Succeed())
+ })
+
+ BeforeEach(func() {
+ adapter = NewAdapter(hasSnapshot, hasApp, logger, loader.NewMockLoader(), k8sClient, ctx)
+ statusReporter = &MockStatusReporter{}
+ statusAdapter = &MockStatusAdapter{Reporter: statusReporter}
+ adapter.status = statusAdapter
+ })
+
+ AfterAll(func() {
+ err := k8sClient.Delete(ctx, hasSnapshot)
+ Expect(err == nil || errors.IsNotFound(err)).To(BeTrue())
+ err = k8sClient.Delete(ctx, hasApp)
+ Expect(err == nil || errors.IsNotFound(err)).To(BeTrue())
+ })
+
+ When("adapter is created", func() {
+ It("can create a new Adapter instance", func() {
+ Expect(reflect.TypeOf(NewAdapter(hasSnapshot, hasApp, logger, loader.NewMockLoader(), k8sClient, ctx))).To(Equal(reflect.TypeOf(&Adapter{})))
+ })
+
+ It("ensures the statusResport is called", func() {
+ adapter = NewAdapter(hasSnapshot, hasApp, logger, loader.NewMockLoader(), k8sClient, ctx)
+ adapter.context = loader.GetMockedContext(ctx, []loader.MockData{
+ {
+ ContextKey: loader.ApplicationContextKey,
+ Resource: hasApp,
+ },
+ {
+ ContextKey: loader.SnapshotContextKey,
+ Resource: hasSnapshot,
+ },
+ })
+ result, err := adapter.EnsureSnapshotTestStatusReported()
+ Expect(!result.CancelRequest && err == nil).To(BeTrue())
+ })
+ })
+
+})
diff --git a/controllers/statusreport/statusreport_controller.go b/controllers/statusreport/statusreport_controller.go
new file mode 100644
index 000000000..7f33f182f
--- /dev/null
+++ b/controllers/statusreport/statusreport_controller.go
@@ -0,0 +1,105 @@
+/*
+Copyright 2023.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions andF
+limitations under the License.
+*/
+
+package statusreport
+
+import (
+ "context"
+
+ "github.com/go-logr/logr"
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
+ "github.com/redhat-appstudio/integration-service/gitops"
+ "github.com/redhat-appstudio/integration-service/helpers"
+ "github.com/redhat-appstudio/integration-service/loader"
+ "github.com/redhat-appstudio/operator-toolkit/controller"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+)
+
+// Reconciler reconciles an Snapshot object
+type Reconciler struct {
+ client.Client
+ Log logr.Logger
+ Scheme *runtime.Scheme
+}
+
+// NewStatusReportReconciler creates and returns a Reconciler.
+func NewStatusReportReconciler(client client.Client, logger *logr.Logger, scheme *runtime.Scheme) *Reconciler {
+ return &Reconciler{
+ Client: client,
+ Log: logger.WithName("statusreport"),
+ Scheme: scheme,
+ }
+}
+
+//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=snapshots,verbs=get;list;watch
+//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=snapshots/status,verbs=get
+//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=applications,verbs=get;list;watch
+//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=applications/status,verbs=get
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ logger := helpers.IntegrationLogger{Logger: r.Log.WithValues("snapshot", req.NamespacedName)}
+ loader := loader.NewLoader()
+
+ logger.Info("start to process snapshot test status since there is change in annotation test.appstudio.openshift.io/status", "snapshot", req.NamespacedName)
+
+ snapshot := &applicationapiv1alpha1.Snapshot{}
+ err := r.Get(ctx, req.NamespacedName, snapshot)
+ if err != nil {
+ logger.Error(err, "Failed to get snapshot for", "req", req.NamespacedName)
+ if errors.IsNotFound(err) {
+ return ctrl.Result{}, nil
+ }
+
+ return ctrl.Result{}, err
+ }
+
+ application, err := loader.GetApplicationFromSnapshot(r.Client, ctx, snapshot)
+ if err != nil {
+ logger.Error(err, "Failed to get Application from the Snapshot")
+ return ctrl.Result{}, err
+ }
+ logger = logger.WithApp(*application)
+
+ adapter := NewAdapter(snapshot, application, logger, loader, r.Client, ctx)
+ return controller.ReconcileHandler([]controller.Operation{
+ adapter.EnsureSnapshotTestStatusReported,
+ })
+}
+
+// AdapterInterface is an interface defining all the operations that should be defined in an Integration adapter.
+type AdapterInterface interface {
+ EnsureSnapshotTestStatusReported() (controller.OperationResult, error)
+}
+
+// SetupController creates a new Integration controller and adds it to the Manager.
+func SetupController(manager ctrl.Manager, log *logr.Logger) error {
+ return setupControllerWithManager(manager, NewStatusReportReconciler(manager.GetClient(), log, manager.GetScheme()))
+}
+
+// setupControllerWithManager sets up the controller with the Manager which monitors new Snapshots
+func setupControllerWithManager(manager ctrl.Manager, controller *Reconciler) error {
+ return ctrl.NewControllerManagedBy(manager).
+ For(&applicationapiv1alpha1.Snapshot{}).
+ WithEventFilter(predicate.Or(
+ gitops.SnapshotTestAnnotationChangePredicate())).
+ Complete(controller)
+}
diff --git a/controllers/statusreport/statusreport_controller_test.go b/controllers/statusreport/statusreport_controller_test.go
new file mode 100644
index 000000000..772c1ac2b
--- /dev/null
+++ b/controllers/statusreport/statusreport_controller_test.go
@@ -0,0 +1,160 @@
+/*
+Copyright 2023.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package statusreport
+
+import (
+ "k8s.io/apimachinery/pkg/api/errors"
+ "reflect"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ ctrl "sigs.k8s.io/controller-runtime"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
+ "github.com/redhat-appstudio/integration-service/gitops"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+ klog "k8s.io/klog/v2"
+)
+
+var _ = Describe("StatusReportController", func() {
+ var (
+ manager ctrl.Manager
+ statusReportReconciler *Reconciler
+ scheme runtime.Scheme
+ req ctrl.Request
+ hasApp *applicationapiv1alpha1.Application
+ hasSnapshot *applicationapiv1alpha1.Snapshot
+ )
+
+ BeforeEach(func() {
+
+ applicationName := "application-sample"
+
+ hasApp = &applicationapiv1alpha1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: applicationName,
+ Namespace: "default",
+ },
+ Spec: applicationapiv1alpha1.ApplicationSpec{
+ DisplayName: "application-sample",
+ Description: "This is an example application",
+ },
+ }
+
+ Expect(k8sClient.Create(ctx, hasApp)).Should(Succeed())
+
+ hasSnapshot = &applicationapiv1alpha1.Snapshot{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "snapshot-sample",
+ Namespace: "default",
+ Labels: map[string]string{
+ gitops.SnapshotTypeLabel: "component",
+ gitops.SnapshotComponentLabel: "component-sample",
+ },
+ },
+ Spec: applicationapiv1alpha1.SnapshotSpec{
+ Application: hasApp.Name,
+ Components: []applicationapiv1alpha1.SnapshotComponent{
+ {
+ Name: "component-sample",
+ ContainerImage: "testimage",
+ },
+ },
+ },
+ }
+ Expect(k8sClient.Create(ctx, hasSnapshot)).Should(Succeed())
+
+ req = ctrl.Request{
+ NamespacedName: types.NamespacedName{
+ Namespace: "default",
+ Name: hasSnapshot.Name,
+ },
+ }
+
+ webhookInstallOptions := &testEnv.WebhookInstallOptions
+
+ klog.Info(webhookInstallOptions.LocalServingHost)
+ klog.Info(webhookInstallOptions.LocalServingPort)
+ klog.Info(webhookInstallOptions.LocalServingCertDir)
+
+ var err error
+ manager, err = ctrl.NewManager(cfg, ctrl.Options{
+ Scheme: clientsetscheme.Scheme,
+ Host: webhookInstallOptions.LocalServingHost,
+ Port: webhookInstallOptions.LocalServingPort,
+ CertDir: webhookInstallOptions.LocalServingCertDir,
+ MetricsBindAddress: "0", // this disables metrics
+ LeaderElection: false,
+ })
+ Expect(err).NotTo(HaveOccurred())
+ Expect(err).To(BeNil())
+
+ statusReportReconciler = NewStatusReportReconciler(k8sClient, &logf.Log, &scheme)
+ })
+ AfterEach(func() {
+ err := k8sClient.Delete(ctx, hasApp)
+ Expect(err == nil || errors.IsNotFound(err)).To(BeTrue())
+ err = k8sClient.Delete(ctx, hasSnapshot)
+ Expect(err == nil || errors.IsNotFound(err)).To(BeTrue())
+ })
+
+ It("can create and return a new Reconciler object", func() {
+ Expect(reflect.TypeOf(statusReportReconciler)).To(Equal(reflect.TypeOf(&Reconciler{})))
+ })
+
+ It("can Reconcile when Reconcile fails to prepare the adapter when snapshot is found", func() {
+ Eventually(func() error {
+ _, err := statusReportReconciler.Reconcile(ctx, req)
+ return err
+ }).Should(BeNil())
+ })
+
+ It("can Reconcile function prepare the adapter and return the result of the reconcile handling operation", func() {
+ req := ctrl.Request{
+ NamespacedName: types.NamespacedName{
+ Name: "non-existent",
+ Namespace: "default",
+ },
+ }
+ result, err := statusReportReconciler.Reconcile(ctx, req)
+ Expect(reflect.TypeOf(result)).To(Equal(reflect.TypeOf(reconcile.Result{})))
+ Expect(err).To(BeNil())
+ })
+
+ It("can setup a new controller manager with the given statusReportReconciler", func() {
+ err := setupControllerWithManager(manager, statusReportReconciler)
+ Expect(err).NotTo(HaveOccurred())
+ })
+
+ It("can setup a new Controller manager and start it", func() {
+ err := SetupController(manager, &ctrl.Log)
+ Expect(err).To(BeNil())
+ go func() {
+ defer GinkgoRecover()
+ err = manager.Start(ctx)
+ Expect(err).NotTo(HaveOccurred())
+ }()
+ })
+
+})
diff --git a/controllers/statusreport/statusreport_suite_test.go b/controllers/statusreport/statusreport_suite_test.go
new file mode 100644
index 000000000..b4940305e
--- /dev/null
+++ b/controllers/statusreport/statusreport_suite_test.go
@@ -0,0 +1,111 @@
+/*
+Copyright 2023.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package statusreport
+
+import (
+ "context"
+ "go/build"
+ "path/filepath"
+ "testing"
+
+ toolkit "github.com/redhat-appstudio/operator-toolkit/test"
+
+ "k8s.io/client-go/rest"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+ ctrl "sigs.k8s.io/controller-runtime"
+
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
+ "github.com/redhat-appstudio/integration-service/api/v1beta1"
+ releasev1alpha1 "github.com/redhat-appstudio/release-service/api/v1alpha1"
+ tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
+ clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
+)
+
+var (
+ cfg *rest.Config
+ k8sClient client.Client
+ testEnv *envtest.Environment
+ ctx context.Context
+ cancel context.CancelFunc
+)
+
+func TestControllerSnapshot(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "StatusReport Controller Test Suite")
+}
+
+var _ = BeforeSuite(func() {
+ logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
+ ctx, cancel = context.WithCancel(context.TODO())
+
+ //adding required CRDs, including tekton for PipelineRun Kind
+ testEnv = &envtest.Environment{
+ CRDDirectoryPaths: []string{
+ filepath.Join("..", "..", "config", "crd", "bases"),
+ filepath.Join(
+ build.Default.GOPATH,
+ "pkg", "mod", toolkit.GetRelativeDependencyPath("tektoncd/pipeline"), "config",
+ ),
+ filepath.Join(
+ build.Default.GOPATH,
+ "pkg", "mod", toolkit.GetRelativeDependencyPath("application-api"),
+ "config", "crd", "bases",
+ ),
+ filepath.Join(
+ build.Default.GOPATH,
+ "pkg", "mod", toolkit.GetRelativeDependencyPath("release-service"), "config", "crd", "bases",
+ ),
+ },
+ ErrorIfCRDPathMissing: true,
+ }
+
+ var err error
+ cfg, err = testEnv.Start()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(cfg).NotTo(BeNil())
+
+ Expect(applicationapiv1alpha1.AddToScheme(clientsetscheme.Scheme)).To(Succeed())
+ Expect(tektonv1beta1.AddToScheme(clientsetscheme.Scheme)).To(Succeed())
+ Expect(releasev1alpha1.AddToScheme(clientsetscheme.Scheme)).To(Succeed())
+ Expect(v1beta1.AddToScheme(clientsetscheme.Scheme)).To(Succeed())
+
+ k8sManager, _ := ctrl.NewManager(cfg, ctrl.Options{
+ Scheme: clientsetscheme.Scheme,
+ MetricsBindAddress: "0", // this disables metrics
+ LeaderElection: false,
+ })
+
+ k8sClient = k8sManager.GetClient()
+ go func() {
+ defer GinkgoRecover()
+ Expect(k8sManager.Start(ctx)).To(Succeed())
+ }()
+})
+
+var _ = AfterSuite(func() {
+ cancel()
+ By("tearing down the test environment")
+ err := testEnv.Stop()
+ Expect(err).NotTo(HaveOccurred())
+})
diff --git a/docs/statusreport-controller.md b/docs/statusreport-controller.md
new file mode 100644
index 000000000..5fc7440e0
--- /dev/null
+++ b/docs/statusreport-controller.md
@@ -0,0 +1,66 @@
+
StatusReport Controller
+
+```mermaid
+%%{init: {'theme':'forest'}}%%
+flowchart TD
+ %% Defining the styles
+ classDef Amber fill:#FFDEAD;
+
+ predicate((PREDICATE:
Snapshot has annotation
test.appstudio.openshift.io/status
changed))
+
+ %%%%%%%%%%%%%%%%%%%%%%% Drawing EnsureSnapshotTestStatusReported() function
+
+ %% Node definitions
+ ensure(Process further if: Snapshot has label
pac.test.appstudio.openshift.io/git-provider:github
defined)
+ get_annotation_value(Get integration test status from annotation
test.appstudio.openshift.io/status
from Snapshot)
+ is_snapshot_from_PR_event{"Is the
Snapshot from
push request event?"}
+ collect_commit_info(Collect commit owner, repo and SHA from Snapshot)
+
+ is_installation_defined{Is annotation
pac.test.appstudio.openshift.io/installation-id
defined?}
+
+ create_appInstallation_token(Create github application installation token)
+ get_all_checkRuns_from_gh(Get all checkruns from github
according to
commit owner, repo and SHA)
+ create_checkRunAdapter(Create checkRun adapter according to
commit owner, repo, SHA
and integration test status)
+ does_checkRun_exist{Does checkRun exist
on github already?}
+ create_new_checkRun_on_gh(Create new checkrun on github)
+ is_checkRun_update_needed{Does existing checkRun
has older CompletedAt?}
+ update_existing_checkRun_on_gh(Update existing checkRun on github)
+
+ set_oAuth_token(Get token from Snapshot and set oAuth token)
+ get_all_commitStatuses_from_gh(Get all commitStatuses from github
according to commit owner, repo and SHA)
+ 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)
+
+ continue_processing(Controller continues processing)
+
+ %% Node connections
+ predicate ----> |"EnsureSnapshotTestStatusReported()"|ensure
+ ensure --> get_annotation_value
+ get_annotation_value --> is_snapshot_from_PR_event
+ is_snapshot_from_PR_event --Yes--> collect_commit_info
+ is_snapshot_from_PR_event --No--> continue_processing
+ collect_commit_info --> is_installation_defined
+ is_installation_defined --Yes--> create_appInstallation_token
+ is_installation_defined --No--> set_oAuth_token
+
+ create_appInstallation_token --> get_all_checkRuns_from_gh
+ get_all_checkRuns_from_gh --> create_checkRunAdapter
+ create_checkRunAdapter --> does_checkRun_exist
+ does_checkRun_exist --Yes--> is_checkRun_update_needed
+ does_checkRun_exist --No--> create_new_checkRun_on_gh
+ create_new_checkRun_on_gh --> continue_processing
+ is_checkRun_update_needed --Yes--> update_existing_checkRun_on_gh
+ is_checkRun_update_needed --No--> continue_processing
+ update_existing_checkRun_on_gh --> continue_processing
+
+ set_oAuth_token --> get_all_commitStatuses_from_gh
+ get_all_commitStatuses_from_gh --> create_commitStatusAdapter
+ 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
+
+ %% Assigning styles to nodes
+ class predicate Amber;
+```
\ No newline at end of file
diff --git a/git/github/github.go b/git/github/github.go
index 407dc2c94..ff735a0c8 100644
--- a/git/github/github.go
+++ b/git/github/github.go
@@ -2,6 +2,7 @@ package github
import (
"context"
+ "fmt"
"net/http"
"time"
@@ -26,6 +27,16 @@ type CheckRunAdapter struct {
CompletionTime time.Time
}
+// CommitStatusAdapter is an abstraction for the github.CommiStatus struct.
+type CommitStatusAdapter struct {
+ Owner string
+ Repository string
+ SHA string
+ State string
+ Description string
+ Context string
+}
+
// GetStatus returns the appropriate status based on conclusion and start time.
func (s *CheckRunAdapter) GetStatus() string {
if s.Conclusion == "success" || s.Conclusion == "failure" {
@@ -56,6 +67,7 @@ type IssuesService interface {
// RepositoriesService defines the methods used in the github Repositories service.
type RepositoriesService interface {
CreateStatus(ctx context.Context, owner string, repo string, ref string, status *ghapi.RepoStatus) (*ghapi.RepoStatus, *ghapi.Response, error)
+ ListStatuses(ctx context.Context, owner, repo, ref string, opts *ghapi.ListOptions) ([]*ghapi.RepoStatus, *ghapi.Response, error)
}
// ClientInterface defines the methods that should be implemented by a GitHub client
@@ -67,6 +79,11 @@ type ClientInterface interface {
GetCheckRunID(ctx context.Context, owner string, repo string, SHA string, externalID string, appID int64) (*int64, error)
CreateComment(ctx context.Context, owner string, repo string, issueNumber int, body string) (int64, error)
CreateCommitStatus(ctx context.Context, owner string, repo string, SHA string, state string, description string, statusContext string) (int64, error)
+ GetAllCheckRunsForRef(ctx context.Context, owner string, repo string, SHA string, appID int64) ([]*ghapi.CheckRun, error)
+ 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)
+ CommitStatusExists(res []*ghapi.RepoStatus, commitStatus *CommitStatusAdapter) (bool, error)
}
// Client is an abstraction around the API client.
@@ -217,7 +234,7 @@ func (c *Client) CreateCheckRun(ctx context.Context, cra *CheckRunAdapter) (*int
cr, _, err := c.GetChecksService().CreateCheckRun(ctx, cra.Owner, cra.Repository, options)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to create check run for for owner/repo/Ref %s/%s/%s: %w", cra.Owner, cra.Repository, cra.SHA, err)
}
c.logger.Info("Created CheckRun",
@@ -284,7 +301,7 @@ func (c *Client) GetCheckRunID(ctx context.Context, owner string, repo string, S
)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to list all checks run for GitHub owner/repo/Ref %s/%s/%s: %w", owner, repo, SHA, err)
}
if *res.Total == 0 {
@@ -302,6 +319,89 @@ func (c *Client) GetCheckRunID(ctx context.Context, owner string, repo string, S
return nil, nil
}
+// GetAllCheckRunsForRef returns all existing GitHub CheckRuns if a match for the Owner, Repo, SHA, and appID.
+func (c *Client) GetAllCheckRunsForRef(ctx context.Context, owner string, repo string, SHA string, appID int64) ([]*ghapi.CheckRun, error) {
+ filter := "all"
+
+ res, _, err := c.GetChecksService().ListCheckRunsForRef(
+ ctx,
+ owner,
+ repo,
+ SHA,
+ &ghapi.ListCheckRunsOptions{
+ AppID: &appID,
+ Filter: &filter,
+ },
+ )
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to get all check runs for GitHub owner/repo/Ref %s/%s/%s: %w", owner, repo, SHA, err)
+ }
+
+ if *res.Total == 0 {
+ c.logger.Info("Found no CheckRuns for the ref", "SHA", SHA)
+ return nil, nil
+ }
+
+ return res.CheckRuns, nil
+}
+
+// GetExistingCheckRun returns existing GitHub CheckRun for the ExternalID in checkRunAdapter.
+func (c *Client) GetExistingCheckRun(checkRuns []*ghapi.CheckRun, newCheckRun *CheckRunAdapter) *ghapi.CheckRun {
+ for _, cr := range checkRuns {
+ if *cr.ExternalID == newCheckRun.ExternalID {
+ c.logger.Info("found CheckRun with a matching ExternalID", "ExternalID", newCheckRun.ExternalID)
+ return cr
+ }
+ }
+ c.logger.Info("found no CheckRuns with a matching ExternalID", "ExternalID", newCheckRun.ExternalID)
+ 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 {
+ if newCheckRun.Text != *existingCheckRun.Output.Text {
+ // We need to update the existing checkrun if their ExternalID and Text are different
+ c.logger.Info("found CheckRun with a matching ExternalID and status, so need to update", "ExternalID", newCheckRun.ExternalID)
+ return true
+ } else {
+ // We don't need to update the existing checkrun if their ExternalID and Text are the same
+ c.logger.Info("found CheckRun with a matching ExternalID and status, so no need to update", "ExternalID", newCheckRun.ExternalID)
+ return false
+ }
+}
+
+// GetAllCommitStatusesForRef returns all existing GitHub CommitStatuses if a match for the Owner, Repo, and SHA.
+func (c *Client) GetAllCommitStatusesForRef(ctx context.Context, owner, repo, sha string) ([]*ghapi.RepoStatus, error) {
+ res, _, err := c.GetRepositoriesService().ListStatuses(ctx, owner, repo, sha, &ghapi.ListOptions{})
+ if err != nil {
+ return nil, fmt.Errorf("failed to get all commit statuses for GitHub owner/repo/Ref %s/%s/%s: %w", owner, repo, sha, err)
+ }
+
+ if len(res) == 0 {
+ c.logger.Info("Found no commitStatus for the ref", "SHA", sha)
+ 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 {
+ if *cs.State == commitStatus.State && *cs.Description == commitStatus.Description && *cs.Context == commitStatus.Context {
+ c.logger.Info("Found CommitStatus with matching conditions", "CommitStatus.State", commitStatus.State, "CommitStatus.Description", commitStatus.Description, "CommitStatus.Context", commitStatus.Context)
+ return true, nil
+ } else {
+ return false, nil
+ }
+ }
+ c.logger.Info("Found no CommitStatus with matching conditions", "CommitStatus.State", commitStatus.State, "CommitStatus.Description", commitStatus.Description, "CommitStatus.Context", commitStatus.Context)
+
+ return false, nil
+}
+
// CreateComment creates a new issue comment via the GitHub API.
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})
@@ -330,7 +430,7 @@ func (c *Client) CreateCommitStatus(ctx context.Context, owner string, repo stri
"Owner", owner,
"Repository", repo,
"SHA", SHA,
- "State", status.State,
+ "State", state,
)
return *status.ID, nil
}
diff --git a/git/github/github_test.go b/git/github/github_test.go
index 866d33295..094e08e51 100644
--- a/git/github/github_test.go
+++ b/git/github/github_test.go
@@ -55,11 +55,27 @@ func (MockChecksService) ListCheckRunsForRef(
) (*ghapi.ListCheckRunsResults, *ghapi.Response, error) {
var id int64 = 20
var externalID string = "example-external-id"
- checkRuns := []*ghapi.CheckRun{{ID: &id, ExternalID: &externalID}}
+ var text string = "example-text-update"
+ var checkRunOutput = ghapi.CheckRunOutput{Text: &text}
+ conclusion := "failure"
+ checkRuns := []*ghapi.CheckRun{{ID: &id, ExternalID: &externalID, Conclusion: &conclusion, Output: &checkRunOutput}}
total := len(checkRuns)
return &ghapi.ListCheckRunsResults{Total: &total, CheckRuns: checkRuns}, nil, nil
}
+// GetAllCheckRunsForRef implements github.ChecksService
+func (MockChecksService) GetAllCheckRunsForRef(
+ ctx context.Context, owner string, repo string, ref string, appID int64,
+) ([]*ghapi.CheckRun, error) {
+ var id int64 = 20
+ var externalID string = "example-external-id"
+ var text string = "example-text-update"
+ var checkRunOutput = ghapi.CheckRunOutput{Text: &text}
+ conclusion := "failure"
+ checkRuns := []*ghapi.CheckRun{{ID: &id, ExternalID: &externalID, Conclusion: &conclusion, Output: &checkRunOutput}}
+ return checkRuns, nil
+}
+
// UpdateCheckRun implements github.ChecksService
func (MockChecksService) UpdateCheckRun(
ctx context.Context, owner string, repo string, checkRunID int64, opts ghapi.UpdateCheckRunOptions,
@@ -85,10 +101,22 @@ func (MockRepositoriesService) CreateStatus(
ctx context.Context, owner string, repo string, ref string, status *ghapi.RepoStatus,
) (*ghapi.RepoStatus, *ghapi.Response, error) {
var id int64 = 50
- var state = "success"
+ var state = "Passed"
return &ghapi.RepoStatus{ID: &id, State: &state}, nil, nil
}
+// ListStatuses implements github.RepositoriesService
+func (MockRepositoriesService) ListStatuses(
+ ctx context.Context, owner string, repo string, ref string, opts *ghapi.ListOptions,
+) ([]*ghapi.RepoStatus, *ghapi.Response, error) {
+ var id int64 = 60
+ var state = "Passed"
+ var description = "example-description"
+ var context = "example-context"
+ repoStatus := &ghapi.RepoStatus{ID: &id, State: &state, Description: &description, Context: &context}
+ return []*ghapi.RepoStatus{repoStatus}, nil, nil
+}
+
var _ = Describe("CheckRunAdapter", func() {
It("can compute status", func() {
adapter := &github.CheckRunAdapter{Conclusion: "success", StartTime: time.Time{}}
@@ -118,7 +146,7 @@ var _ = Describe("Client", func() {
Repository: "example-repo",
SHA: "abcdef1",
ExternalID: "example-external-id",
- Conclusion: "success",
+ Conclusion: "Passed",
Title: "example-title",
Summary: "example-summary",
Text: "example-text",
@@ -126,6 +154,15 @@ var _ = Describe("Client", func() {
CompletionTime: time.Now(),
}
+ var commitStatusAdapter = &github.CommitStatusAdapter{
+ Owner: "example-owner",
+ Repository: "example-repo",
+ SHA: "abcdef1",
+ State: "Passed",
+ Description: "example-description",
+ Context: "example-context",
+ }
+
BeforeEach(func() {
mockAppsSvc = MockAppsService{}
mockChecksSvc = MockChecksService{}
@@ -195,4 +232,65 @@ var _ = Describe("Client", func() {
Expect(err).To(BeNil())
Expect(checkRunID).To(BeNil())
})
+
+ It("can check if check run updated is needed", func() {
+ var checkRunAdapter = &github.CheckRunAdapter{
+ Name: "example-name",
+ Owner: "example-owner",
+ Repository: "example-repo",
+ SHA: "abcdef1",
+ ExternalID: "example-external-id",
+ Conclusion: "success",
+ Title: "example-title",
+ Summary: "example-summary",
+ Text: "example-text",
+ StartTime: time.Now(),
+ CompletionTime: time.Now(),
+ }
+
+ allCheckRuns, err := client.GetAllCheckRunsForRef(context.TODO(), "", "", "", 1)
+ Expect(err).To(BeNil())
+ Expect(len(allCheckRuns) > 0).To(BeTrue())
+
+ existingCheckRun := client.GetExistingCheckRun(allCheckRuns, checkRunAdapter)
+ Expect(existingCheckRun).NotTo(BeNil())
+ Expect(client.IsUpdateNeeded(existingCheckRun, checkRunAdapter)).To(BeTrue())
+
+ checkRunAdapter = &github.CheckRunAdapter{
+ Name: "example-name",
+ Owner: "example-owner",
+ Repository: "example-repo",
+ SHA: "abcdef1",
+ ExternalID: "example-external-id",
+ Conclusion: "failure",
+ Title: "example-title",
+ Summary: "example-summary",
+ Text: "example-text-update",
+ StartTime: time.Now(),
+ CompletionTime: time.Now(),
+ }
+ Expect(client.IsUpdateNeeded(existingCheckRun, checkRunAdapter)).To(BeFalse())
+ })
+
+ It("can check if creating a new commit status is needed", func() {
+ commitStatuses, err := client.GetAllCommitStatusesForRef(context.TODO(), "", "", "")
+ Expect(err).To(BeNil())
+ Expect(len(commitStatuses) > 0).To(BeTrue())
+
+ commitStatusExist, err := client.CommitStatusExists(commitStatuses, commitStatusAdapter)
+ Expect(commitStatusExist).To(BeTrue())
+ Expect(err).To(BeNil())
+
+ commitStatusAdapter = &github.CommitStatusAdapter{
+ Owner: "example-owner",
+ Repository: "example-repo",
+ SHA: "abcdef1",
+ State: "Fail",
+ Description: "example-description",
+ Context: "example-context",
+ }
+ commitStatusExist, err = client.CommitStatusExists(commitStatuses, commitStatusAdapter)
+ Expect(commitStatusExist).To(BeFalse())
+ Expect(err).To(BeNil())
+ })
})
diff --git a/gitops/snapshot.go b/gitops/snapshot.go
index 043b01c66..eba63018b 100644
--- a/gitops/snapshot.go
+++ b/gitops/snapshot.go
@@ -118,6 +118,24 @@ const (
//AppStudioIntegrationStatusFinished is the reason that's set when the AppStudio tests finish.
AppStudioIntegrationStatusFinished = "Finished"
+
+ // the statuses needed to report to GiHub when creating check run or commit status, see doc
+ // https://docs.github.com/en/rest/guides/using-the-rest-api-to-interact-with-checks?apiVersion=2022-11-28
+ // https://docs.github.com/en/free-pro-team@latest/rest/checks/runs?apiVersion=2022-11-28#create-a-check-run
+ //IntegrationTestStatusPendingGithub is the status reported to github when integration test is in a queue
+ IntegrationTestStatusPendingGithub = "pending"
+
+ //IntegrationTestStatusSuccessGithub is the status reported to github when integration test succeed
+ IntegrationTestStatusSuccessGithub = "success"
+
+ //IntegrationTestStatusFailureGithub is the status reported to github when integration test fail
+ IntegrationTestStatusFailureGithub = "failure"
+
+ //IntegrationTestStatusErrorGithub is the status reported to github when integration test experience error
+ IntegrationTestStatusErrorGithub = "error"
+
+ //IntegrationTestStatusInProgressGithub is the status reported to github when integration test is in progress
+ IntegrationTestStatusInProgressGithub = "in_progress"
)
// IntegrationTestScenario test runs status
@@ -415,6 +433,26 @@ func HasSnapshotTestingChangedToFinished(objectOld, objectNew client.Object) boo
return false
}
+// HasSnapshotTestAnnotationChanged returns a boolean indicating whether the Snapshot annotation has
+// changed. If the objects passed to this function are not Snapshots, the function will return false.
+func HasSnapshotTestAnnotationChanged(objectOld, objectNew client.Object) bool {
+ if oldSnapshot, ok := objectOld.(*applicationapiv1alpha1.Snapshot); ok {
+ if newSnapshot, ok := objectNew.(*applicationapiv1alpha1.Snapshot); ok {
+ if !metadata.HasAnnotation(oldSnapshot, SnapshotTestsStatusAnnotation) && metadata.HasAnnotation(newSnapshot, SnapshotTestsStatusAnnotation) {
+ return true
+ }
+ if old_value, ok := oldSnapshot.GetAnnotations()[SnapshotTestsStatusAnnotation]; ok {
+ if new_value, ok := newSnapshot.GetAnnotations()[SnapshotTestsStatusAnnotation]; ok {
+ if old_value != new_value {
+ return true
+ }
+ }
+ }
+ }
+ }
+ return false
+}
+
// PrepareSnapshot prepares the Snapshot for a given application, components and the updated component (if any).
// In case the Snapshot can't be created, an error will be returned.
func PrepareSnapshot(adapterClient client.Client, ctx context.Context, application *applicationapiv1alpha1.Application, applicationComponents *[]applicationapiv1alpha1.Component, component *applicationapiv1alpha1.Component, newContainerImage string, newComponentSource *applicationapiv1alpha1.ComponentSource) (*applicationapiv1alpha1.Snapshot, error) {
diff --git a/gitops/snapshot_predicate.go b/gitops/snapshot_predicate.go
index f0c67ef13..b4a2b9848 100644
--- a/gitops/snapshot_predicate.go
+++ b/gitops/snapshot_predicate.go
@@ -23,3 +23,22 @@ func IntegrationSnapshotChangePredicate() predicate.Predicate {
},
}
}
+
+// SnapshotTestAnnotationChangePredicate returns a predicate which filters out all objects except
+// snapshot annotation "test.appstudio.openshift.io/status" is changed for update events.
+func SnapshotTestAnnotationChangePredicate() predicate.Predicate {
+ return predicate.Funcs{
+ CreateFunc: func(createEvent event.CreateEvent) bool {
+ return false
+ },
+ DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
+ return false
+ },
+ GenericFunc: func(genericEvent event.GenericEvent) bool {
+ return false
+ },
+ UpdateFunc: func(e event.UpdateEvent) bool {
+ return HasSnapshotTestAnnotationChanged(e.ObjectOld, e.ObjectNew)
+ },
+ }
+}
diff --git a/gitops/snapshot_predicate_test.go b/gitops/snapshot_predicate_test.go
index 473147cab..4a3683a41 100644
--- a/gitops/snapshot_predicate_test.go
+++ b/gitops/snapshot_predicate_test.go
@@ -31,16 +31,20 @@ import (
var _ = Describe("Predicates", Ordered, func() {
const (
- namespace = "default"
- applicationName = "test-application"
- componentName = "test-component"
- snapshotOldName = "test-snapshot-old"
- snapshotNewName = "test-snapshot-new"
+ namespace = "default"
+ applicationName = "test-application"
+ componentName = "test-component"
+ snapshotOldName = "test-snapshot-old"
+ snapshotNewName = "test-snapshot-new"
+ snapshotAnnotationOld = "snapshot-annotation-old"
+ snapshotAnnotationNew = "snapshot-annotation-new"
)
var (
hasSnapshotUnknownStatus *applicationapiv1alpha1.Snapshot
hasSnapshotTrueStatus *applicationapiv1alpha1.Snapshot
+ hasSnapshotAnnotationOld *applicationapiv1alpha1.Snapshot
+ hasSnapshotAnnotationNew *applicationapiv1alpha1.Snapshot
sampleImage string
)
@@ -86,10 +90,57 @@ var _ = Describe("Predicates", Ordered, func() {
},
}
+ hasSnapshotAnnotationOld = &applicationapiv1alpha1.Snapshot{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: snapshotAnnotationOld,
+ Namespace: namespace,
+ Labels: map[string]string{
+ gitops.SnapshotTypeLabel: gitops.SnapshotComponentType,
+ gitops.SnapshotComponentLabel: componentName,
+ },
+ Annotations: map[string]string{
+ gitops.SnapshotTestsStatusAnnotation: "[{\"scenario\":\"scenario-1\",\"status\":\"EnvironmentProvisionError\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"sompletionTime\":\"2023-07-26T17:57:49+02:00\",\"details\":\"Failed to find deploymentTargetClass with right provisioner for copy of existingEnvironment\"}]",
+ },
+ },
+ Spec: applicationapiv1alpha1.SnapshotSpec{
+ Application: applicationName,
+ Components: []applicationapiv1alpha1.SnapshotComponent{
+ {
+ Name: componentName,
+ ContainerImage: sampleImage,
+ },
+ },
+ },
+ }
+
+ hasSnapshotAnnotationNew = &applicationapiv1alpha1.Snapshot{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: snapshotAnnotationNew,
+ Namespace: namespace,
+ Labels: map[string]string{
+ gitops.SnapshotTypeLabel: gitops.SnapshotComponentType,
+ gitops.SnapshotComponentLabel: componentName,
+ },
+ Annotations: map[string]string{
+ gitops.SnapshotTestsStatusAnnotation: "[{\"scenario\":\"scenario-1\",\"status\":\"TestPassed\",\"startTime\":\"2023-07-26T16:57:49+02:00\",\"completionTime\":\"2023-07-26T17:57:49+02:00\",\"details\": \"test pass\"}]",
+ },
+ },
+ Spec: applicationapiv1alpha1.SnapshotSpec{
+ Application: applicationName,
+ Components: []applicationapiv1alpha1.SnapshotComponent{
+ {
+ Name: componentName,
+ ContainerImage: sampleImage,
+ },
+ },
+ },
+ }
ctx := context.Background()
Expect(k8sClient.Create(ctx, hasSnapshotUnknownStatus)).Should(Succeed())
Expect(k8sClient.Create(ctx, hasSnapshotTrueStatus)).Should(Succeed())
+ Expect(k8sClient.Create(ctx, hasSnapshotAnnotationOld)).Should(Succeed())
+ Expect(k8sClient.Create(ctx, hasSnapshotAnnotationNew)).Should(Succeed())
// Set the binding statuses after they are created
hasSnapshotUnknownStatus.Status.Conditions = []metav1.Condition{
@@ -137,4 +188,47 @@ var _ = Describe("Predicates", Ordered, func() {
Expect(instance.Delete(contextEvent)).To(BeFalse())
})
})
+
+ Context("when testing IntegrationSnapshotChangePredicate predicate", func() {
+ instance := gitops.SnapshotTestAnnotationChangePredicate()
+
+ It("returns true when the test status annotation of Snapshot changed ", func() {
+ contextEvent := event.UpdateEvent{
+ ObjectOld: hasSnapshotAnnotationOld,
+ ObjectNew: hasSnapshotAnnotationNew,
+ }
+ Expect(instance.Update(contextEvent)).To(BeTrue())
+ })
+
+ It("returns false when the test status annotation of Snapshot is not changed ", func() {
+ contextEvent := event.UpdateEvent{
+ ObjectOld: hasSnapshotAnnotationOld,
+ ObjectNew: hasSnapshotAnnotationOld,
+ }
+ Expect(instance.Update(contextEvent)).To(BeFalse())
+ })
+
+ It("returns true when the test status annotation of old Snapshot doesn't exist but exists in new snapshot ", func() {
+ contextEvent := event.UpdateEvent{
+ ObjectOld: hasSnapshotTrueStatus,
+ ObjectNew: hasSnapshotAnnotationNew,
+ }
+ Expect(instance.Update(contextEvent)).To(BeTrue())
+ })
+
+ It("returns false when the test status annotation doesn't exist in old and new Snapshot", func() {
+ contextEvent := event.UpdateEvent{
+ ObjectOld: hasSnapshotTrueStatus,
+ ObjectNew: hasSnapshotTrueStatus,
+ }
+ Expect(instance.Update(contextEvent)).To(BeFalse())
+ })
+
+ It("returns false when the Snapshot is deleted", func() {
+ contextEvent := event.DeleteEvent{
+ Object: hasSnapshotUnknownStatus,
+ }
+ Expect(instance.Delete(contextEvent)).To(BeFalse())
+ })
+ })
})
diff --git a/status/reporters.go b/status/reporters.go
index 9667cbd08..84246cd54 100644
--- a/status/reporters.go
+++ b/status/reporters.go
@@ -9,6 +9,7 @@ import (
"github.com/go-logr/logr"
pacv1alpha1 "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
"github.com/redhat-appstudio/integration-service/git/github"
"github.com/redhat-appstudio/integration-service/gitops"
"github.com/redhat-appstudio/integration-service/helpers"
@@ -57,12 +58,12 @@ type appCredentials struct {
PrivateKey []byte
}
-func (r *GitHubReporter) getAppCredentials(ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) (*appCredentials, error) {
+func (r *GitHubReporter) getAppCredentials(ctx context.Context, object client.Object) (*appCredentials, error) {
var err error
var found bool
appInfo := appCredentials{}
- appInfo.InstallationID, err = strconv.ParseInt(pipelineRun.GetAnnotations()[gitops.PipelineAsCodeInstallationIDAnnotation], 10, 64)
+ appInfo.InstallationID, err = strconv.ParseInt(object.GetAnnotations()[gitops.PipelineAsCodeInstallationIDAnnotation], 10, 64)
if err != nil {
return nil, err
}
@@ -94,19 +95,19 @@ func (r *GitHubReporter) getAppCredentials(ctx context.Context, pipelineRun *tek
return &appInfo, nil
}
-func (r *GitHubReporter) getToken(ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) (string, error) {
+func (r *GitHubReporter) getToken(ctx context.Context, object client.Object, namespace string) (string, error) {
var err error
- // List all the Repository CRs in the PipelineRun's namespace
+ // List all the Repository CRs in the namespace
repos := pacv1alpha1.RepositoryList{}
- if err = r.k8sClient.List(ctx, &repos, &client.ListOptions{Namespace: pipelineRun.Namespace}); err != nil {
+ if err = r.k8sClient.List(ctx, &repos, &client.ListOptions{Namespace: namespace}); err != nil {
return "", err
}
// Get the full repo URL
- url, found := pipelineRun.GetAnnotations()[gitops.PipelineAsCodeRepoURLAnnotation]
+ url, found := object.GetAnnotations()[gitops.PipelineAsCodeRepoURLAnnotation]
if !found {
- return "", fmt.Errorf("PipelineRun annotation not found %q", gitops.PipelineAsCodeRepoURLAnnotation)
+ return "", fmt.Errorf("object annotation not found %q", gitops.PipelineAsCodeRepoURLAnnotation)
}
// Find a Repository CR with a matching URL and get its secret details
@@ -124,7 +125,7 @@ func (r *GitHubReporter) getToken(ctx context.Context, pipelineRun *tektonv1beta
// Get the pipelines as code secret from the PipelineRun's namespace
pacSecret := v1.Secret{}
- err = r.k8sClient.Get(ctx, types.NamespacedName{Namespace: pipelineRun.Namespace, Name: repoSecret.Name}, &pacSecret)
+ err = r.k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: repoSecret.Name}, &pacSecret)
if err != nil {
return "", err
}
@@ -226,6 +227,116 @@ func (r *GitHubReporter) createCheckRunAdapter(k8sClient client.Client, ctx cont
}, nil
}
+// generateSummary generate a string for the given state, snapshotName and scenarioName
+func generateSummary(state gitops.IntegrationTestStatus, snapshotName, scenarioName string) (string, error) {
+ var title string
+
+ var statusDesc string = "is unknown"
+
+ switch state {
+ case gitops.IntegrationTestStatusPending:
+ statusDesc = "is pending"
+ case gitops.IntegrationTestStatusInProgress:
+ statusDesc = "is in progress"
+ case gitops.IntegrationTestStatusEnvironmentProvisionError:
+ statusDesc = "experienced an error when provisioning environment"
+ case gitops.IntegrationTestStatusDeploymentError:
+ statusDesc = "experienced an error when deploying snapshotEnvironmentBinding"
+ case gitops.IntegrationTestStatusTestPassed:
+ statusDesc = "has passed"
+ case gitops.IntegrationTestStatusTestFail:
+ statusDesc = "has failed"
+ default:
+ return title, fmt.Errorf("unknown status")
+ }
+
+ title = fmt.Sprintf("Integration test for snapshot %s and scenario %s %s", snapshotName, scenarioName, statusDesc)
+
+ return title, 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
+// https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28#create-a-check-run
+func generateCheckRunConclusion(state gitops.IntegrationTestStatus) (string, error) {
+ var conclusion string
+
+ switch state {
+ case gitops.IntegrationTestStatusTestFail, gitops.IntegrationTestStatusEnvironmentProvisionError, gitops.IntegrationTestStatusDeploymentError:
+ conclusion = gitops.IntegrationTestStatusFailureGithub
+ case gitops.IntegrationTestStatusTestPassed:
+ conclusion = gitops.IntegrationTestStatusSuccessGithub
+ case gitops.IntegrationTestStatusPending, gitops.IntegrationTestStatusInProgress:
+ conclusion = ""
+ default:
+ return conclusion, fmt.Errorf("unknown status")
+ }
+
+ return conclusion, nil
+}
+
+// generateCommitState generate state of CommitStatus
+// Can be one of: error, failure, pending, success
+// https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#create-a-commit-status
+func generateCommitState(state gitops.IntegrationTestStatus) (string, error) {
+ var commitState string
+
+ switch state {
+ case gitops.IntegrationTestStatusTestFail:
+ commitState = gitops.IntegrationTestStatusFailureGithub
+ case gitops.IntegrationTestStatusEnvironmentProvisionError, gitops.IntegrationTestStatusDeploymentError:
+ commitState = gitops.IntegrationTestStatusErrorGithub
+ case gitops.IntegrationTestStatusTestPassed:
+ commitState = gitops.IntegrationTestStatusSuccessGithub
+ case gitops.IntegrationTestStatusPending, gitops.IntegrationTestStatusInProgress:
+ commitState = gitops.IntegrationTestStatusPendingGithub
+ default:
+ return commitState, fmt.Errorf("unknown status")
+ }
+
+ return commitState, nil
+}
+
+// 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 gitops.IntegrationTestStatusDetail, owner, repo, sha string) (*github.CheckRunAdapter, error) {
+ snapshotName := snapshot.Name
+ scenarioName := integrationTestStatusDetail.ScenarioName
+
+ conclusion, err := generateCheckRunConclusion(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)
+ }
+
+ cra := &github.CheckRunAdapter{
+ Owner: owner,
+ Repository: repo,
+ Name: NamePrefix + " / " + snapshotName + " / " + scenarioName,
+ 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,
+ }
+
+ if start := integrationTestStatusDetail.StartTime; start != nil {
+ cra.StartTime = *start
+ }
+
+ if complete := integrationTestStatusDetail.CompletionTime; complete != nil {
+ cra.CompletionTime = *complete
+ }
+
+ return cra, nil
+}
+
func (r *GitHubReporter) createCommitStatus(k8sClient client.Client, ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) error {
var (
state string
@@ -289,6 +400,33 @@ func (r *GitHubReporter) createCommitStatus(k8sClient client.Client, ctx context
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 gitops.IntegrationTestStatusDetail, owner, repo, sha string) (*github.CommitStatusAdapter, error) {
+ snapshotName := snapshot.Name
+ scenarioName := integrationTestStatusDetail.ScenarioName
+ statusContext := NamePrefix + " / " + snapshot.Name + " / " + scenarioName
+
+ state, err := generateCommitState(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)
+ }
+
+ description, 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)
+ }
+
+ return &github.CommitStatusAdapter{
+ Owner: owner,
+ Repository: repo,
+ SHA: sha,
+ State: state,
+ Description: description,
+ Context: statusContext,
+ }, nil
+}
+
func (r *GitHubReporter) createComment(k8sClient client.Client, ctx context.Context, pipelineRun *tektonv1beta1.PipelineRun) error {
labels := pipelineRun.GetLabels()
@@ -393,7 +531,7 @@ func (r *GitHubReporter) ReportStatus(k8sClient client.Client, ctx context.Conte
return err
}
} else {
- token, err := r.getToken(ctx, pipelineRun)
+ token, err := r.getToken(ctx, pipelineRun, pipelineRun.Namespace)
if err != nil {
return err
}
@@ -413,3 +551,146 @@ func (r *GitHubReporter) ReportStatus(k8sClient client.Client, ctx context.Conte
return nil
}
+
+// ReportStatusForSnapshot creates CheckRun when using GitHub App integration,
+// creates a a commit status when using GitHub webhook integration
+func (r *GitHubReporter) ReportStatusForSnapshot(k8sClient client.Client, ctx context.Context, logger *helpers.IntegrationLogger, snapshot *applicationapiv1alpha1.Snapshot) error {
+ if !metadata.HasLabelWithValue(snapshot, gitops.PipelineAsCodeEventTypeLabel, gitops.PipelineAsCodePullRequestType) {
+ return nil
+ }
+
+ statuses, err := gitops.NewSnapshotIntegrationTestStatusesFromSnapshot(snapshot)
+ if err != nil {
+ logger.Error(err, "failed to get test status annotations from snapshot",
+ "snapshot.Namespace", snapshot.Namespace, "snapshot.Name", snapshot.Name)
+ return err
+ }
+
+ labels := snapshot.GetLabels()
+
+ owner, found := labels[gitops.PipelineAsCodeURLOrgLabel]
+ if !found {
+ return fmt.Errorf("org label not found %q", gitops.PipelineAsCodeURLOrgLabel)
+ }
+
+ repo, found := labels[gitops.PipelineAsCodeURLRepositoryLabel]
+ if !found {
+ return fmt.Errorf("repository label not found %q", gitops.PipelineAsCodeURLRepositoryLabel)
+ }
+
+ sha, found := labels[gitops.PipelineAsCodeSHALabel]
+ if !found {
+ return fmt.Errorf("sha label not found %q", gitops.PipelineAsCodeSHALabel)
+ }
+ integrationTestStatusDetails := statuses.GetStatuses()
+ // 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(snapshot, gitops.PipelineAsCodeInstallationIDAnnotation) {
+ creds, err := r.getAppCredentials(ctx, snapshot)
+ if err != nil {
+ logger.Error(err, "failed to get app credentials from Snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name)
+ return err
+ }
+
+ token, err := r.client.CreateAppInstallationToken(ctx, creds.AppID, creds.InstallationID, creds.PrivateKey)
+ if err != nil {
+ logger.Error(err, "failed to create app installation token",
+ "creds.AppID", creds.AppID, "creds.InstallationID", creds.InstallationID)
+ return err
+ }
+
+ r.client.SetOAuthToken(ctx, token)
+
+ allCheckRuns, err := r.client.GetAllCheckRunsForRef(ctx, owner, repo, sha, creds.AppID)
+ if err != nil {
+ logger.Error(err, "failed to get all checkruns for ref",
+ "owner", owner, "repo", repo, "creds.AppID", creds.AppID)
+ return err
+ }
+
+ for _, integrationTestStatusDetail := range integrationTestStatusDetails {
+ integrationTestStatusDetail := *integrationTestStatusDetail // G601
+ checkRun, err := r.createCheckRunAdapterForSnapshot(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)
+ return err
+ }
+
+ existingCheckrun := r.client.GetExistingCheckRun(allCheckRuns, checkRun)
+
+ if existingCheckrun == nil {
+ logger.Info("creating checkrun for scenario test status of snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name, "scenarioName", integrationTestStatusDetail.ScenarioName)
+ _, err = r.client.CreateCheckRun(ctx, checkRun)
+ if err != nil {
+ logger.Error(err, "failed to create checkrun",
+ "checkRun", checkRun)
+ return err
+ }
+ } else {
+ logger.Info("found existing checkrun", "existingCheckRun", existingCheckrun)
+ if r.client.IsUpdateNeeded(existingCheckrun, checkRun) {
+ logger.Info("found existing check run with the same ExternalID but different conclusion/status, updating checkrun for scenario test status of snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name, "scenarioName", integrationTestStatusDetail.ScenarioName, "checkrun.ExternalID", checkRun.ExternalID)
+ err = r.client.UpdateCheckRun(ctx, *existingCheckrun.ID, checkRun)
+ if err != nil {
+ logger.Error(err, "failed to update checkrun",
+ "checkRun", checkRun)
+ return err
+ }
+ } else {
+ logger.Info("found existing check run with the same ExternalID and conclusion/status, no need to update checkrun for scenario test status of snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name, "scenarioName", integrationTestStatusDetail.ScenarioName, "checkrun.ExternalID", checkRun.ExternalID)
+ }
+ }
+ }
+ } else {
+ token, err := r.getToken(ctx, snapshot, snapshot.Namespace)
+ if err != nil {
+ logger.Error(err, "failed to get token from snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name)
+ return err
+ }
+
+ r.client.SetOAuthToken(ctx, token)
+
+ allCommitStatuses, err := r.client.GetAllCommitStatusesForRef(ctx, owner, repo, sha)
+ if err != nil {
+ logger.Error(err, "failed to get all commitStatuses for snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name)
+ return err
+ }
+
+ for _, integrationTestStatusDetail := range integrationTestStatusDetails {
+ integrationTestStatusDetail := *integrationTestStatusDetail //G601
+ commitStatus, err := r.createCommitStatusAdapterForSnapshot(snapshot, integrationTestStatusDetail, owner, repo, sha)
+ if err != nil {
+ logger.Error(err, "failed to create CommitStatusAdapter for snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name)
+ return err
+ }
+
+ commitStatusExist, err := r.client.CommitStatusExists(allCommitStatuses, commitStatus)
+ if err != nil {
+ return err
+ }
+
+ if !commitStatusExist {
+ logger.Info("creating commit status for scenario test status of snapshot",
+ "snapshot.NameSpace", snapshot.Namespace, "snapshot.Name", snapshot.Name, "scenarioName", integrationTestStatusDetail.ScenarioName)
+ _, err = r.client.CreateCommitStatus(ctx, commitStatus.Owner, commitStatus.Repository, commitStatus.SHA, commitStatus.State, commitStatus.Description, commitStatus.Context)
+ 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)
+ }
+
+ }
+ }
+
+ return nil
+}
diff --git a/status/reporters_test.go b/status/reporters_test.go
index e2685d2a8..871af24fa 100644
--- a/status/reporters_test.go
+++ b/status/reporters_test.go
@@ -2,6 +2,7 @@ package status_test
import (
"context"
+ "fmt"
"strings"
"time"
@@ -9,10 +10,14 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"github.com/go-logr/logr"
+ ghapi "github.com/google/go-github/v45/github"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
pacv1alpha1 "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
"github.com/redhat-appstudio/integration-service/git/github"
+ "github.com/redhat-appstudio/integration-service/gitops"
+ "github.com/redhat-appstudio/integration-service/helpers"
"github.com/redhat-appstudio/integration-service/status"
tektonv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
v1 "k8s.io/api/core/v1"
@@ -43,6 +48,10 @@ type GetCheckRunIDResult struct {
Error error
}
+type GetCheckRunResult struct {
+ cr *ghapi.CheckRun
+}
+
type CreateCommentResult struct {
ID int64
Error error
@@ -63,6 +72,7 @@ type MockGitHubClient struct {
CreateCheckRunResult
UpdateCheckRunResult
GetCheckRunIDResult
+ GetCheckRunResult
CreateCommentResult
CreateCommitStatusResult
}
@@ -87,6 +97,18 @@ func (c *MockGitHubClient) GetCheckRunID(context.Context, string, string, string
return c.GetCheckRunIDResult.ID, c.GetCheckRunIDResult.Error
}
+func (c *MockGitHubClient) IsUpdateNeeded(existingCheckRun *ghapi.CheckRun, cra *github.CheckRunAdapter) bool {
+ return true
+}
+
+func (c *MockGitHubClient) GetExistingCheckRun(checkRuns []*ghapi.CheckRun, cra *github.CheckRunAdapter) *ghapi.CheckRun {
+ return c.GetCheckRunResult.cr
+}
+
+func (c *MockGitHubClient) CommitStatusExists(res []*ghapi.RepoStatus, commitStatus *github.CommitStatusAdapter) (bool, error) {
+ return false, nil
+}
+
func (c *MockGitHubClient) CreateComment(ctx context.Context, owner string, repo string, issueNumber int, body string) (int64, error) {
c.CreateCommentResult.body = body
c.CreateCommentResult.issueNumber = issueNumber
@@ -100,6 +122,23 @@ func (c *MockGitHubClient) CreateCommitStatus(ctx context.Context, owner string,
return c.CreateCommitStatusResult.ID, c.CreateCommitStatusResult.Error
}
+func (c *MockGitHubClient) GetAllCheckRunsForRef(
+ ctx context.Context, owner string, repo string, ref string, appID int64,
+) ([]*ghapi.CheckRun, error) {
+ var id int64 = 20
+ var externalID string = "example-external-id"
+ checkRuns := []*ghapi.CheckRun{{ID: &id, ExternalID: &externalID}}
+ return checkRuns, nil
+}
+
+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}
+ return []*ghapi.RepoStatus{repoStatus}, nil
+}
+
type MockK8sClient struct {
getInterceptor func(key client.ObjectKey, obj client.Object)
listInterceptor func(list client.ObjectList)
@@ -200,6 +239,8 @@ var _ = Describe("GitHubReporter", func() {
var successfulTaskRun *tektonv1beta1.TaskRun
var failedTaskRun *tektonv1beta1.TaskRun
var skippedTaskRun *tektonv1beta1.TaskRun
+ var hasSnapshot *applicationapiv1alpha1.Snapshot
+ var logger helpers.IntegrationLogger
BeforeEach(func() {
now := time.Now()
@@ -330,6 +371,44 @@ var _ = Describe("GitHubReporter", func() {
},
},
}
+
+ hasSnapshot = &applicationapiv1alpha1.Snapshot{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "snapshot-sample",
+ Namespace: "default",
+ Labels: map[string]string{
+ "test.appstudio.openshift.io/type": "component",
+ "appstudio.openshift.io/component": "component-sample",
+ "build.appstudio.redhat.com/pipeline": "enterprise-contract",
+ "pac.test.appstudio.openshift.io/git-provider": "github",
+ "pac.test.appstudio.openshift.io/url-org": "devfile-sample",
+ "pac.test.appstudio.openshift.io/url-repository": "devfile-sample-go-basic",
+ "pac.test.appstudio.openshift.io/sha": "12a4a35ccd08194595179815e4646c3a6c08bb77",
+ "pac.test.appstudio.openshift.io/event-type": "pull_request",
+ },
+ Annotations: map[string]string{
+ "build.appstudio.redhat.com/commit_sha": "6c65b2fcaea3e1a0a92476c8b5dc89e92a85f025",
+ "appstudio.redhat.com/updateComponentOnSuccess": "false",
+ "pac.test.appstudio.openshift.io/repo-url": "https://github.com/devfile-sample/devfile-sample-go-basic",
+ },
+ },
+ Spec: applicationapiv1alpha1.SnapshotSpec{
+ Application: "application-sample",
+ Components: []applicationapiv1alpha1.SnapshotComponent{
+ {
+ Name: "component-sample",
+ ContainerImage: "sample_image",
+ Source: applicationapiv1alpha1.ComponentSource{
+ ComponentSourceUnion: applicationapiv1alpha1.ComponentSourceUnion{
+ GitSource: &applicationapiv1alpha1.GitSource{
+ Revision: "sample_revision",
+ },
+ },
+ },
+ },
+ },
+ },
+ }
})
Context("when provided GitHub app credentials", func() {
@@ -338,6 +417,7 @@ var _ = Describe("GitHubReporter", func() {
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{
"github-application-id": []byte("456"),
@@ -369,6 +449,8 @@ 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() {
@@ -378,22 +460,33 @@ var _ = Describe("GitHubReporter", func() {
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)
+ 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() {
@@ -426,6 +519,59 @@ var _ = Describe("GitHubReporter", func() {
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\"}]"
+ var id int64 = 1
+ var externalID string = "example-external-id"
+ conclusion := ""
+ mockGitHubClient.GetCheckRunResult.cr = &ghapi.CheckRun{ID: &id, ExternalID: &externalID, Conclusion: &conclusion}
+ 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 is in progress"))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.Conclusion).To(Equal(""))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.StartTime.IsZero()).To(BeFalse())
+
+ // Update existing CheckRun w/failure
+ 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 to find deploymentTargetClass with right provisioner for copy of existingEnvironment\"}]"
+ mockGitHubClient.GetCheckRunResult.cr = &ghapi.CheckRun{ID: &id, ExternalID: &externalID, Conclusion: &conclusion}
+ 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 experienced an error when provisioning environment"))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.Conclusion).To(Equal(gitops.IntegrationTestStatusFailureGithub))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.ExternalID).To(Equal("scenario1"))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.Owner).To(Equal("devfile-sample"))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.Repository).To(Equal("devfile-sample-go-basic"))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.SHA).To(Equal("12a4a35ccd08194595179815e4646c3a6c08bb77"))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.Name).To(Equal("Red Hat Trusted App Test / snapshot-sample / scenario1"))
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.StartTime.IsZero()).To(BeFalse())
+ Expect(mockGitHubClient.UpdateCheckRunResult.cra.CompletionTime.IsZero()).To(BeFalse())
+
+ // Update existing CheckRun w/failure
+ 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\":\"error\"}]"
+ 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 experienced an error when deploying snapshotEnvironmentBinding"))
+ 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\"}]"
+ 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\"}]"
+ 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))
+ })
})
Context("when provided GitHub webhook integration credentials", func() {
@@ -435,6 +581,7 @@ var _ = Describe("GitHubReporter", func() {
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{
Spec: pacv1alpha1.RepositorySpec{
@@ -531,6 +678,35 @@ var _ = Describe("GitHubReporter", func() {
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
+ 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"))
+
+ 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"))
+
+ // 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\"}]"
+ 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"))
+
+ // 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\"}]"
+ 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"))
+ })
+ })
})
diff --git a/status/status.go b/status/status.go
index e02cf97a6..c88791358 100644
--- a/status/status.go
+++ b/status/status.go
@@ -4,7 +4,9 @@ import (
"context"
"github.com/go-logr/logr"
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
"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"
@@ -16,11 +18,12 @@ 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
}
// Status is the interface of the main status Adapter.
type Status interface {
- GetReporters(*tektonv1beta1.PipelineRun) ([]Reporter, error)
+ GetReporters(client.Object) ([]Reporter, error)
}
// Adapter is responsible for discovering supported Reporter implementations.
@@ -57,10 +60,10 @@ func NewAdapter(logger logr.Logger, k8sClient client.Client, opts ...AdapterOpti
// GetReporters returns a list of enabled/supported status reporters for a PipelineRun.
// All potential reporters must be added to this function for them to be utilized.
-func (a *Adapter) GetReporters(pipelineRun *tektonv1beta1.PipelineRun) ([]Reporter, error) {
+func (a *Adapter) GetReporters(object client.Object) ([]Reporter, error) {
var reporters []Reporter
- if metadata.HasLabelWithValue(pipelineRun, gitops.PipelineAsCodeGitProviderLabel, gitops.PipelineAsCodeGitHubProviderType) {
+ if metadata.HasLabelWithValue(object, gitops.PipelineAsCodeGitProviderLabel, gitops.PipelineAsCodeGitHubProviderType) {
reporters = append(reporters, a.githubReporter)
}
diff --git a/status/status_test.go b/status/status_test.go
index d54916575..e0df8767b 100644
--- a/status/status_test.go
+++ b/status/status_test.go
@@ -5,6 +5,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
+ "github.com/redhat-appstudio/integration-service/helpers"
"github.com/redhat-appstudio/integration-service/status"
"github.com/go-logr/logr"
@@ -19,6 +21,10 @@ func (r *MockReporter) ReportStatus(client.Client, context.Context, *tektonv1bet
return nil
}
+func (r *MockReporter) ReportStatusForSnapshot(client.Client, context.Context, *helpers.IntegrationLogger, *applicationapiv1alpha1.Snapshot) error {
+ return nil
+}
+
var _ = Describe("Status Adapter", func() {
var pipelineRun *tektonv1beta1.PipelineRun