Skip to content

Commit

Permalink
feat(STONEINTG-524): report result of handling ITS to github
Browse files Browse the repository at this point in the history
* Create function to get credential from snapshot
* Create function to report for result of handle ITSes for snapshot

Signed-off-by: Hongwei Liu <[email protected]>
  • Loading branch information
hongweiliu17 committed Sep 5, 2023
1 parent 12da31d commit 26d5739
Show file tree
Hide file tree
Showing 16 changed files with 1,358 additions and 20 deletions.
2 changes: 2 additions & 0 deletions controllers/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, *applicationapiv1alpha1.Snapshot, *[]gitops.IntegrationTestStatusDetail) error {
r.Called = true
return r.ReportStatusError
}

func (a *MockStatusAdapter) GetReporters(object client.Object) ([]status.Reporter, error) {
return []status.Reporter{a.Reporter}, a.GetReportersError
}

Expand Down
100 changes: 100 additions & 0 deletions controllers/statusreport/statusreport_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
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"
"encoding/json"
"fmt"

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/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 {
integrationTestStatusDetails, err := getIntegrationScenarioTestStatusFromAnnotation(a.snapshot)
if err != nil {
a.logger.Error(err, "failed to get test status to snapshot annotations for snapshot %s/%s", "Snapshot.Namespace", a.snapshot.Namespace, "Snapshot.Name", a.snapshot.Name)
return controller.RequeueWithError(err)
}
if integrationTestStatusDetails == nil {
a.logger.Info("no snapshot annotation %s defined for snapshot, no need to report integration test status", "Snapshot.Namespace", a.snapshot.Namespace, "Snapshot.Name", a.snapshot.Name)
return controller.ContinueProcessing()
}

if err := reporter.ReportStatusForSnapshot(a.client, a.context, a.snapshot, integrationTestStatusDetails); 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()
}

func getIntegrationScenarioTestStatusFromAnnotation(snapshot *applicationapiv1alpha1.Snapshot) (*[]gitops.IntegrationTestStatusDetail, error) {
statusAnnotation, ok := snapshot.GetAnnotations()[gitops.SnapshotTestsStatusAnnotation]
if !ok {
return nil, nil
}

integrationTestStatusDetails := &[]gitops.IntegrationTestStatusDetail{}
err := json.Unmarshal([]byte(statusAnnotation), integrationTestStatusDetails)
if err != nil {
return nil, fmt.Errorf("failed to load tests statuses from the scenario annotation: %w", err)
}
return integrationTestStatusDetails, nil
}
150 changes: 150 additions & 0 deletions controllers/statusreport/statusreport_adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
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, *applicationapiv1alpha1.Snapshot, *[]gitops.IntegrationTestStatusDetail) 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: "[{\"ScenarioName\":\"scenario-1\",\"Status\":\"EnvironmentProvisionError\",\"StartTime\":\"2023-07-26T16:57:49+02:00\",\"CompletionTime\":\"2023-07-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())
})
})

})
Loading

0 comments on commit 26d5739

Please sign in to comment.