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
* Add diagram for statusreport controller

Signed-off-by: Hongwei Liu <[email protected]>
  • Loading branch information
hongweiliu17 committed Sep 22, 2023
1 parent a404aca commit 05cd934
Show file tree
Hide file tree
Showing 17 changed files with 1,525 additions and 24 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, *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
}

Expand Down
73 changes: 73 additions & 0 deletions controllers/statusreport/statusreport_adapter.go
Original file line number Diff line number Diff line change
@@ -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()
}
169 changes: 169 additions & 0 deletions controllers/statusreport/statusreport_adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
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"
"fmt"
"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
r.ReportStatusError = nil
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: "pull_request",
"pac.test.appstudio.openshift.io/url-org": "testorg",
"pac.test.appstudio.openshift.io/url-repository": "testrepo",
"pac.test.appstudio.openshift.io/sha": "testsha",
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())
})

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)
statusReporter = &MockStatusReporter{}
statusAdapter = &MockStatusAdapter{Reporter: statusReporter}
adapter.status = statusAdapter
adapter.context = loader.GetMockedContext(ctx, []loader.MockData{
{
ContextKey: loader.ApplicationContextKey,
Resource: hasApp,
},
{
ContextKey: loader.SnapshotContextKey,
Resource: hasSnapshot,
},
})
result, err := adapter.EnsureSnapshotTestStatusReported()
fmt.Fprintf(GinkgoWriter, "-------err: %v\n", err)
fmt.Fprintf(GinkgoWriter, "-------result: %v\n", result)
Expect(!result.CancelRequest && err == nil).To(BeTrue())
})
})

})
105 changes: 105 additions & 0 deletions controllers/statusreport/statusreport_controller.go
Original file line number Diff line number Diff line change
@@ -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())).

Check failure on line 103 in controllers/statusreport/statusreport_controller.go

View workflow job for this annotation

GitHub Actions / Check sources

undefined: gitops.SnapshotTestAnnotationChangePredicate (compile)

Check failure on line 103 in controllers/statusreport/statusreport_controller.go

View workflow job for this annotation

GitHub Actions / Check sources

undefined: gitops.SnapshotTestAnnotationChangePredicate (compile)
Complete(controller)
}
Loading

0 comments on commit 05cd934

Please sign in to comment.