controllers/codebase/service/chain/put_webhook.go (160 lines of code) (raw):
package chain
import (
"context"
"errors"
"fmt"
"github.com/go-resty/resty/v2"
routeApi "github.com/openshift/api/route/v1"
coreV1 "k8s.io/api/core/v1"
networkingV1 "k8s.io/api/networking/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1"
"github.com/epam/edp-codebase-operator/v2/controllers/gitserver"
"github.com/epam/edp-codebase-operator/v2/pkg/gitprovider"
"github.com/epam/edp-codebase-operator/v2/pkg/platform"
"github.com/epam/edp-codebase-operator/v2/pkg/util"
)
const (
webhookTokenLength = 20
)
// PutWebHook is a chain element to create webhook.
type PutWebHook struct {
client client.Client
restyClient *resty.Client
}
// NewPutWebHook creates PutWebHook instance.
func NewPutWebHook(k8sClient client.Client, restyClient *resty.Client) *PutWebHook {
return &PutWebHook{client: k8sClient, restyClient: restyClient}
}
// ServeRequest creates webhook.
func (s *PutWebHook) ServeRequest(ctx context.Context, codebase *codebaseApi.Codebase) error {
log := ctrl.LoggerFrom(ctx)
if codebase.Spec.CiTool != util.CITekton {
log.Info("Skip putting webhook for non-Tekton CI tool")
return nil
}
log.Info("Start putting webhook")
gitServer := &codebaseApi.GitServer{}
if err := s.client.Get(ctx, client.ObjectKey{Name: codebase.Spec.GitServer, Namespace: codebase.Namespace}, gitServer); err != nil {
return s.processCodebaseError(
codebase,
fmt.Errorf("failed to get git server %s: %w", codebase.Spec.GitServer, err),
)
}
if gitServer.Spec.GitProvider != codebaseApi.GitProviderGitlab &&
gitServer.Spec.GitProvider != codebaseApi.GitProviderGithub &&
gitServer.Spec.GitProvider != codebaseApi.GitProviderBitbucket {
log.Info(fmt.Sprintf("Unsupported Git provider %s. Skip putting webhook", gitServer.Spec.GitProvider))
return nil
}
secret, err := s.getGitServerSecret(ctx, gitServer.Spec.NameSshKeySecret, gitServer.Namespace)
if err != nil {
return s.processCodebaseError(codebase, err)
}
gitProvider, err := gitprovider.NewProvider(gitServer, s.restyClient, string(secret.Data[util.GitServerSecretTokenField]))
if err != nil {
return s.processCodebaseError(codebase, fmt.Errorf("failed to create git provider: %w", err))
}
projectID := codebase.Spec.GetProjectID()
gitHost := gitprovider.GetGitProviderAPIURL(gitServer)
if codebase.Status.GetWebHookRef() != "" {
_, err = gitProvider.GetWebHook(
ctx,
gitHost,
string(secret.Data[util.GitServerSecretTokenField]),
projectID,
codebase.Status.GetWebHookRef(),
)
if err == nil {
log.Info("Webhook already exists. Skip putting webhook")
return nil
}
if !errors.Is(err, gitprovider.ErrWebHookNotFound) {
return s.processCodebaseError(codebase, fmt.Errorf("failed to get webhook: %w", err))
}
}
webHookURL, err := s.getWebHookUrl(ctx, gitServer)
if err != nil {
return s.processCodebaseError(codebase, err)
}
webHook, err := gitProvider.CreateWebHookIfNotExists(
ctx,
gitHost,
string(secret.Data[util.GitServerSecretTokenField]),
projectID,
string(secret.Data[util.GitServerSecretWebhookSecretField]),
webHookURL,
gitServer.Spec.SkipWebhookSSLVerification,
)
if err != nil {
return s.processCodebaseError(codebase, fmt.Errorf("failed to create web hook: %w", err))
}
codebase.Status.WebHookRef = webHook.ID
if err = setIntermediateSuccessFields(ctx, s.client, codebase, codebaseApi.PutWebHook); err != nil {
return fmt.Errorf("failed to update codebase %s status: %w", codebase.Name, err)
}
log.Info("Webhook has been created successfully")
return nil
}
func (s *PutWebHook) getGitServerSecret(ctx context.Context, secretName, namespace string) (*coreV1.Secret, error) {
secret := &coreV1.Secret{}
if err := s.client.Get(ctx, client.ObjectKey{Name: secretName, Namespace: namespace}, secret); err != nil {
return nil, fmt.Errorf("failed to get %v secret: %w", secretName, err)
}
if _, ok := secret.Data[util.GitServerSecretTokenField]; !ok {
return nil, fmt.Errorf("failed to get %s field from %s secret", util.GitServerSecretTokenField, secretName)
}
if token, ok := secret.Data[util.GitServerSecretWebhookSecretField]; !ok || len(token) == 0 {
token, err := util.GenerateRandomString(webhookTokenLength)
if err != nil {
return nil, fmt.Errorf("failed to generate webhook secret: %w", err)
}
secret.Data[util.GitServerSecretWebhookSecretField] = []byte(token)
if err = s.client.Update(ctx, secret); err != nil {
return nil, fmt.Errorf("failed to update %s secret: %w", secretName, err)
}
}
return secret, nil
}
func (s *PutWebHook) getWebHookUrl(ctx context.Context, gitServer *codebaseApi.GitServer) (string, error) {
if gitServer.Spec.WebhookUrl != "" {
return gitServer.Spec.WebhookUrl, nil
}
if platform.IsK8S() {
return s.getWebhookIngressUrl(ctx, gitServer.Name, gitServer.Namespace)
}
return s.getWebhookRouteUrl(ctx, gitServer.Name, gitServer.Namespace)
}
func (*PutWebHook) processCodebaseError(codebase *codebaseApi.Codebase, err error) error {
setFailedFields(codebase, codebaseApi.PutWebHook, err.Error())
return err
}
func (s *PutWebHook) getWebhookIngressUrl(ctx context.Context, gitServerName, namespace string) (string, error) {
ingress := &networkingV1.Ingress{}
if err := s.client.Get(
ctx,
client.ObjectKey{
Name: gitserver.GenerateIngressName(gitServerName),
Namespace: namespace,
},
ingress,
); err != nil {
return "", fmt.Errorf("failed to get webhook ingress: %w", err)
}
if len(ingress.Spec.Rules) == 0 {
return "", fmt.Errorf("ingress %s doesn't have rules", ingress.Name)
}
return util.GetHostWithProtocol(ingress.Spec.Rules[0].Host), nil
}
func (s *PutWebHook) getWebhookRouteUrl(ctx context.Context, gitServerName, namespace string) (string, error) {
route := &routeApi.Route{}
if err := s.client.Get(
ctx,
client.ObjectKey{
Name: gitserver.GenerateIngressName(gitServerName),
Namespace: namespace,
},
route,
); err != nil {
return "", fmt.Errorf("failed to get webhook route: %w", err)
}
return util.GetHostWithProtocol(route.Status.Ingress[0].Host), nil
}