pkg/interceptor/edp_interceptor.go (171 lines of code) (raw):
package interceptor
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1beta1"
"github.com/tektoncd/triggers/pkg/interceptors"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/utils/ptr"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
"github.com/epam/edp-tekton/pkg/event_processor"
)
const (
executeTimeOut = 3 * time.Second
)
// EDPInterceptorInterface is an interface for EDPInterceptor.
type EDPInterceptorInterface interface {
Execute(r *http.Request) ([]byte, error)
}
// EDPInterceptor is an interceptor for EDP.
type EDPInterceptor struct {
gitHubProcessor event_processor.Processor
gitLabProcessor event_processor.Processor
gerritProcessor event_processor.Processor
bitbucketProcessor event_processor.Processor
client ctrlClient.Reader
logger *zap.SugaredLogger
}
// NewEDPInterceptor creates a new EDPInterceptor.
func NewEDPInterceptor(
c ctrlClient.Reader,
gitHubProcessor event_processor.Processor,
gitLabProcessor event_processor.Processor,
gerritProcessor event_processor.Processor,
bitbucketProcessor event_processor.Processor,
l *zap.SugaredLogger,
) *EDPInterceptor {
return &EDPInterceptor{
gitHubProcessor: gitHubProcessor,
gitLabProcessor: gitLabProcessor,
gerritProcessor: gerritProcessor,
bitbucketProcessor: bitbucketProcessor,
client: c,
logger: l,
}
}
// Execute executes the interceptor.
func (i *EDPInterceptor) Execute(r *http.Request) ([]byte, error) {
ctx, cancel := context.WithTimeout(r.Context(), executeTimeOut)
defer cancel()
var body bytes.Buffer
defer func() {
if err := r.Body.Close(); err != nil {
i.logger.Errorf("Failed to close body: %s", err)
}
}()
if _, err := io.Copy(&body, r.Body); err != nil {
return nil, internal(fmt.Errorf("failed to read body: %w", err))
}
var ireq triggersv1.InterceptorRequest
if err := json.Unmarshal(body.Bytes(), &ireq); err != nil {
return nil, badRequest(fmt.Errorf("failed to parse body as InterceptorRequest: %w", err))
}
i.logger.Infof("Interceptor request is: %s", body.Bytes())
iresp := i.Process(ctx, &ireq)
respBytes, err := json.Marshal(iresp)
if err != nil {
return nil, internal(err)
}
i.logger.Infof("Interceptor response is: %s", respBytes)
return respBytes, nil
}
// Process processes the interceptor request.
func (i *EDPInterceptor) Process(ctx context.Context, r *triggersv1.InterceptorRequest) *triggersv1.InterceptorResponse {
event, err := i.processEvent(ctx, r)
if err != nil {
return interceptors.Fail(codes.InvalidArgument, err.Error())
}
if event.IsReviewCommentEvent() {
if !event.HasPipelineRecheck {
i.logger.Infof("Pipeline recheck comment is not found, skipping pipeline triggering")
return &triggersv1.InterceptorResponse{
Continue: false,
}
}
i.logger.Infof("Found comment for recheck, triggering pipeline")
}
prepareCodebase(event.Codebase)
codebaseBranchName := convertBranchToCadebaseBranchName(event.TargetBranch, event.Codebase.Name)
trigger := true
ns, _ := triggersv1.ParseTriggerID(r.Context.TriggerID)
codebaseBranch := codebaseApi.CodebaseBranch{}
if err = i.client.Get(ctx, ctrlClient.ObjectKey{Namespace: ns, Name: codebaseBranchName}, &codebaseBranch); err != nil {
if !k8serrors.IsNotFound(err) {
return interceptors.Fail(codes.Internal, fmt.Errorf("failed to get CodebaseBranch: %w", err).Error())
}
trigger = false
i.logger.Infof("Codebasebranch with the name %s is not found, skipping pipeline triggering. "+
"You can ignore this message otherwise add branch %s to codebase %s for the pipeline triggering",
codebaseBranchName,
event.TargetBranch,
event.Codebase.Name,
)
}
return &triggersv1.InterceptorResponse{
Continue: trigger,
Extensions: map[string]interface{}{
"spec": event.Codebase.Spec,
"codebase": event.Codebase.Name,
"codebasebranch": codebaseBranchName,
"targetBranch": event.TargetBranch,
"pullRequest": event.PullRequest,
"pipelines": codebaseBranch.Spec.Pipelines,
},
}
}
// processEvent returns event info from interceptor request.
func (i *EDPInterceptor) processEvent(ctx context.Context, r *triggersv1.InterceptorRequest) (*event_processor.EventInfo, error) {
githubEventType, isGitHubEvent := r.Header["X-Github-Event"]
gitLabEventType, isGitLabEvent := r.Header["X-Gitlab-Event"]
bitbucketEventType, isBitbucketEvent := r.Header["X-Event-Key"]
ns, _ := triggersv1.ParseTriggerID(r.Context.TriggerID)
if isGitLabEvent {
event, err := i.gitLabProcessor.Process(ctx, []byte(r.Body), ns, getEventTypeFromHeader(gitLabEventType))
if err != nil {
return nil, fmt.Errorf("failed to process GitLab event: %w", err)
}
return event, nil
}
if isGitHubEvent {
event, err := i.gitHubProcessor.Process(ctx, []byte(r.Body), ns, getEventTypeFromHeader(githubEventType))
if err != nil {
return nil, fmt.Errorf("failed to process GitHub event: %w", err)
}
return event, nil
}
if isBitbucketEvent {
event, err := i.bitbucketProcessor.Process(ctx, []byte(r.Body), ns, getEventTypeFromHeader(bitbucketEventType))
if err != nil {
return nil, fmt.Errorf("failed to process Bitbucket event: %w", err)
}
return event, nil
}
event, err := i.gerritProcessor.Process(ctx, []byte(r.Body), ns, "")
if err != nil {
return nil, fmt.Errorf("failed to process Gerrit event: %w", err)
}
return event, nil
}
// getEventTypeFromHeader returns event type from header.
func getEventTypeFromHeader(headerData []string) string {
if len(headerData) == 0 {
return ""
}
return headerData[0]
}
// prepareCodebase prepares codebase for interceptor response.
func prepareCodebase(codebase *codebaseApi.Codebase) {
codebase.Spec.Framework = strings.ToLower(codebase.Spec.Framework)
codebase.Spec.BuildTool = strings.ToLower(codebase.Spec.BuildTool)
if codebase.Spec.CommitMessagePattern == nil {
codebase.Spec.CommitMessagePattern = ptr.To("")
}
if codebase.Spec.JiraServer == nil {
codebase.Spec.JiraServer = ptr.To("")
}
}
// convertBranchToCadebaseBranchName converts branch name to CodebaseBranch CR name.
func convertBranchToCadebaseBranchName(branch, codebaseName string) string {
r := strings.NewReplacer("/", "-")
return fmt.Sprintf("%s-%s", codebaseName, r.Replace(branch))
}