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 4, 2023
1 parent 12da31d commit 88fe5b0
Show file tree
Hide file tree
Showing 14 changed files with 1,096 additions and 14 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
101 changes: 101 additions & 0 deletions controllers/statusreport/statusreport_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
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"
h "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
component *applicationapiv1alpha1.Component
logger h.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, component *applicationapiv1alpha1.Component, logger h.IntegrationLogger, loader loader.ObjectLoader, client client.Client,
context context.Context) *Adapter {
return &Adapter{
snapshot: snapshot,
application: application,
component: component,
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 report integration test scenario test status for snapshot %s/%s", a.snapshot.Namespace, a.snapshot.Name)
return controller.RequeueWithError(err)
}
if integrationTestStatusDetails == nil {
a.logger.Info("no snapshot annotation %s defined for snapshot %s/%s, no need to report integration test status", a.snapshot.Namespace, a.snapshot.Name)
return controller.ContinueProcessing()
}

if err := reporter.ReportStatusForSnapshot(a.client, a.context, a.snapshot, integrationTestStatusDetails); err != nil {
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
}
137 changes: 137 additions & 0 deletions controllers/statusreport/statusreport_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
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/redhat-appstudio/integration-service/cache"

"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=snapshotenvironmentbindings,verbs=get;list;watch
//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=snapshotenvironmentbindings/status,verbs=get
//+kubebuilder:rbac:groups=tekton.dev,resources=pipelineruns,verbs=get;list;watch
//+kubebuilder:rbac:groups=tekton.dev,resources=pipelineruns/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
//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=components,verbs=get;list;watch
//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=components/status,verbs=get
//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=deploymenttargetclasses,verbs=get;list;watch
//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=deploymenttargetclaims,verbs=get;list;watch
//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=environments,verbs=get;list;watch;
//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=environments/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()

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)

component, err := loader.GetComponentFromSnapshot(r.Client, ctx, snapshot)
if err != nil {
logger.Error(err, "Failed to get Component from the Snapshot")
return ctrl.Result{}, err
}

adapter := NewAdapter(snapshot, application, component, 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()))
}

// setupCache indexes fields for each of the resources used in the statusreport adapter in those cases where filtering by
// field is required.
func setupCache(mgr ctrl.Manager) error {
if err := cache.SetupBindingEnvironmentCache(mgr); err != nil {
return err
}

return cache.SetupBindingApplicationCache(mgr)
}

// setupControllerWithManager sets up the controller with the Manager which monitors new Snapshots
func setupControllerWithManager(manager ctrl.Manager, controller *Reconciler) error {
err := setupCache(manager)
if err != nil {
return err
}

return ctrl.NewControllerManagedBy(manager).
For(&applicationapiv1alpha1.Snapshot{}).
WithEventFilter(predicate.Or(
gitops.SnapshotTestAnnotationChangePredicate())).
Complete(controller)
}
Loading

0 comments on commit 88fe5b0

Please sign in to comment.