Skip to content

Commit

Permalink
feat: [TKC-1947] add urls and command variables (#5608)
Browse files Browse the repository at this point in the history
* feat: webhook payload vars

* docs: add new vars to docs

* feat: use dashboard uri

* docs: apply suggestions from code review

Co-authored-by: Julianne Fermi <[email protected]>

---------

Co-authored-by: Julianne Fermi <[email protected]>
  • Loading branch information
vLia and jfermi authored Jun 25, 2024
1 parent 6dd9003 commit 1811baf
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 42 deletions.
5 changes: 2 additions & 3 deletions cmd/api-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,6 @@ func main() {
cfg.GraphqlPort,
artifactStorage,
templatesClient,
cfg.CDEventsTarget,
cfg.TestkubeDashboardURI,
cfg.TestkubeHelmchartVersion,
mode,
Expand All @@ -615,7 +614,6 @@ func main() {
cfg.DisableSecretCreation,
subscriptionChecker,
serviceAccountNames,
cfg.EnableK8sEvents,
)

if mode == common.ModeAgent {
Expand Down Expand Up @@ -646,7 +644,7 @@ func main() {
eventsEmitter.Loader.Register(agentHandle)
}

api.InitEvents()
api.Init(cfg.CDEventsTarget, cfg.EnableK8sEvents)
if !cfg.DisableTestTriggers {
triggerService := triggers.NewService(
sched,
Expand Down Expand Up @@ -880,6 +878,7 @@ func newProContext(cfg *config.Config, grpcClient cloud.TestKubeCloudAPIClient)
OrgID: cfg.TestkubeProOrgID,
Migrate: cfg.TestkubeProMigrate,
ConnectionTimeout: cfg.TestkubeProConnectionTimeout,
DashboardURI: cfg.TestkubeDashboardURI,
}

if grpcClient == nil {
Expand Down
11 changes: 11 additions & 0 deletions docs/docs/articles/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,17 @@ The full TestSuiteExecution data model can be found [here](https://github.com/ku

The full TestWorkflowExecution data model can be found [here](https://github.com/kubeshop/testkube/blob/main/pkg/api/v1/testkube/model_test_workflow_execution.go).

### Additional Top-level Variables:

- `ExecutionCommand` - The CLI command to access the execution (example: `kubectl testkube get execution 6679893e3b11f4e4900e17a5`).
- `ExecutionURL` - The dashboard URL to look at the execution (example: `https://app.testkube.io/organization/tkcorg_9deb42dda2197657/environment/tkcenv_1145999f3c4d1115/dashboard/tests/git-zap-api-test/executions/6679893e3b11f4e4900e17a5`).
- `ArtifactsCommand` - The CLI command to access the artifacts (example: `kubectl testkube get artifacts 6679893e3b11f4e4900e17a5`).
- `ArtifactsURL` - The dashboard URL to look at the artifacts directly (example: `https://app.testkube.io/organization/tkcorg_9deb42dda2197657/environment/tkcenv_1145999f3c4d1115/dashboard/tests/git-zap-api-test/executions/6679893e3b11f4e4900e17a5/artifacts`).
- `LogsCommand` - The CLI command to access the logs (example: `kubectl testkube get execution 6679893e3b11f4e4900e17a5 --logs-only`).
- `LogsURL` - The dashboard URL to look at the logs (example: `https://app.testkube.io/organization/tkcorg_9deb42dda2197657/environment/tkcenv_1145999f3c4d1115/dashboard/tests/git-zap-api-test/executions/6679893e3b11f4e4900e17a5/log-output`).

Make sure that the value `testkube-api.dashboardUri` in the helm-charts is set to `app.testkube.io`, or your remote dashboard URL, so that the variables above can be populated with the correct values.

## Additional Examples

### Microsoft Teams
Expand Down
88 changes: 60 additions & 28 deletions internal/app/api/v1/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ func NewTestkubeAPI(
graphqlPort string,
artifactsStorage storage.ArtifactsStorage,
templatesClient *templatesclientv1.TemplatesClient,
cdeventsTarget string,
dashboardURI string,
helmchartVersion string,
mode string,
Expand All @@ -107,7 +106,6 @@ func NewTestkubeAPI(
disableSecretCreation bool,
subscriptionChecker checktcl.SubscriptionChecker,
serviceAccountNames map[string]string,
enableK8sEvents bool,
) TestkubeAPI {

var httpConfig server.Config
Expand All @@ -123,7 +121,7 @@ func NewTestkubeAPI(
httpConfig.Http.BodyLimit = DefaultHttpBodyLimit
}

s := TestkubeAPI{
return TestkubeAPI{
HTTPServer: server.NewServer(httpConfig),
TestExecutionResults: testSuiteExecutionsResults,
ExecutionResults: testExecutionResults,
Expand Down Expand Up @@ -165,31 +163,6 @@ func NewTestkubeAPI(
LabelSources: common.Ptr(make([]LabelSource, 0)),
ServiceAccountNames: serviceAccountNames,
}

// will be reused in websockets handler
s.WebsocketLoader = ws.NewWebsocketLoader()

s.Events.Loader.Register(webhook.NewWebhookLoader(s.Log, webhookClient, templatesClient, testExecutionResults, testSuiteExecutionsResults, testWorkflowResults, metrics))
s.Events.Loader.Register(s.WebsocketLoader)
s.Events.Loader.Register(s.slackLoader)

if cdeventsTarget != "" {
cdeventLoader, err := cdevent.NewCDEventLoader(cdeventsTarget, clusterId, namespace, dashboardURI, testkube.AllEventTypes)
if err == nil {
s.Events.Loader.Register(cdeventLoader)
} else {
s.Log.Debug("cdevents init error", "error", err.Error())
}
}

if enableK8sEvents {
s.Events.Loader.Register(k8sevent.NewK8sEventLoader(clientset, namespace, testkube.AllEventTypes))
}

s.InitEnvs()
s.InitRoutes()

return s
}

type TestkubeAPI struct {
Expand Down Expand Up @@ -298,6 +271,27 @@ func (s TestkubeAPI) SendTelemetryStartEvent(ctx context.Context, ch chan struct
}()
}

func (s *TestkubeAPI) Init(cdEventsTarget string, enableK8sEvents bool) {
s.InitEventListeners(
s.proContext,
s.WebhooksClient,
s.TemplatesClient,
s.ExecutionResults,
s.TestExecutionResults,
s.TestWorkflowResults,
s.Metrics,
cdEventsTarget,
s.Config.ClusterID,
s.Namespace,
s.dashboardURI,
enableK8sEvents,
s.Clientset,
)
s.InitEnvs()
s.InitRoutes()
s.InitEvents()
}

// InitEnvs initializes api server settings
func (s *TestkubeAPI) InitEnvs() {
if err := envconfig.Process("STORAGE", &s.storageParams); err != nil {
Expand Down Expand Up @@ -590,6 +584,44 @@ func (s *TestkubeAPI) InitRoutes() {
})
}

func (s TestkubeAPI) InitEventListeners(
proContext *config.ProContext,
webhookClient *executorsclientv1.WebhooksClient,
templatesClient *templatesclientv1.TemplatesClient,
testExecutionResults result.Repository,
testSuiteExecutionsResults testresult.Repository,
testWorkflowResults testworkflow.Repository,
metrics metrics.Metrics,
cdeventsTarget string,
clusterId string,
namespace string,
dashboardURI string,
enableK8sEvents bool,
clientset kubernetes.Interface,
) {
// will be reused in websockets handler
s.WebsocketLoader = ws.NewWebsocketLoader()

s.Events.Loader.Register(webhook.NewWebhookLoader(
s.Log, webhookClient, templatesClient, testExecutionResults, testSuiteExecutionsResults,
testWorkflowResults, metrics, s.proContext))
s.Events.Loader.Register(s.WebsocketLoader)
s.Events.Loader.Register(s.slackLoader)

if cdeventsTarget != "" {
cdeventLoader, err := cdevent.NewCDEventLoader(cdeventsTarget, clusterId, namespace, dashboardURI, testkube.AllEventTypes)
if err == nil {
s.Events.Loader.Register(cdeventLoader)
} else {
s.Log.Debug("cdevents init error", "error", err.Error())
}
}

if enableK8sEvents {
s.Events.Loader.Register(k8sevent.NewK8sEventLoader(clientset, namespace, testkube.AllEventTypes))
}
}

func (s TestkubeAPI) StartTelemetryHeartbeats(ctx context.Context, ch chan struct{}) {
go func() {
<-ch
Expand Down
1 change: 1 addition & 0 deletions internal/config/procontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ type ProContext struct {
OrgID string
Migrate string
ConnectionTimeout int
DashboardURI string
}
9 changes: 7 additions & 2 deletions pkg/event/kind/webhook/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"go.uber.org/zap"

v1 "github.com/kubeshop/testkube/internal/app/api/metrics"
"github.com/kubeshop/testkube/internal/config"
"github.com/kubeshop/testkube/pkg/api/v1/testkube"
"github.com/kubeshop/testkube/pkg/event/kind/common"
thttp "github.com/kubeshop/testkube/pkg/http"
Expand All @@ -31,7 +32,9 @@ func NewWebhookListener(name, uri, selector string, events []testkube.EventType,
onStateChange bool,
testExecutionResults result.Repository,
testSuiteExecutionResults testresult.Repository,
testWorkflowExecutionResults testworkflow.Repository, metrics v1.Metrics) *WebhookListener {
testWorkflowExecutionResults testworkflow.Repository,
metrics v1.Metrics,
proContext *config.ProContext) *WebhookListener {
return &WebhookListener{
name: name,
Uri: uri,
Expand All @@ -48,6 +51,7 @@ func NewWebhookListener(name, uri, selector string, events []testkube.EventType,
testSuiteExecutionResults: testSuiteExecutionResults,
testWorkflowExecutionResults: testWorkflowExecutionResults,
metrics: metrics,
proContext: proContext,
}
}

Expand All @@ -67,6 +71,7 @@ type WebhookListener struct {
testSuiteExecutionResults testresult.Repository
testWorkflowExecutionResults testworkflow.Repository
metrics v1.Metrics
proContext *config.ProContext
}

func (l *WebhookListener) Name() string {
Expand Down Expand Up @@ -261,7 +266,7 @@ func (l *WebhookListener) processTemplate(field, body string, event testkube.Eve
}

var buffer bytes.Buffer
if err = tmpl.ExecuteTemplate(&buffer, field, event); err != nil {
if err = tmpl.ExecuteTemplate(&buffer, field, NewTemplateVars(event, l.proContext)); err != nil {
log.Errorw(fmt.Sprintf("executing webhook %s error", field), "error", err)
return nil, err
}
Expand Down
12 changes: 6 additions & 6 deletions pkg/event/kind/webhook/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestWebhookListener_Notify(t *testing.T) {
svr := httptest.NewServer(testHandler)
defer svr.Close()

l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, false, nil, nil, nil, v1.NewMetrics())
l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, false, nil, nil, nil, v1.NewMetrics(), nil)

// when
r := l.Notify(testkube.Event{
Expand All @@ -56,7 +56,7 @@ func TestWebhookListener_Notify(t *testing.T) {
svr := httptest.NewServer(testHandler)
defer svr.Close()

l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, false, nil, nil, nil, v1.NewMetrics())
l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "", nil, false, false, nil, nil, nil, v1.NewMetrics(), nil)

// when
r := l.Notify(testkube.Event{
Expand All @@ -73,7 +73,7 @@ func TestWebhookListener_Notify(t *testing.T) {
t.Parallel()
// given

s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, false, false, nil, nil, nil, v1.NewMetrics())
s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, false, false, nil, nil, nil, v1.NewMetrics(), nil)

// when
r := s.Notify(testkube.Event{
Expand Down Expand Up @@ -106,7 +106,7 @@ func TestWebhookListener_Notify(t *testing.T) {
svr := httptest.NewServer(testHandler)
defer svr.Close()

l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "field", "", nil, false, false, nil, nil, nil, v1.NewMetrics())
l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "field", "", nil, false, false, nil, nil, nil, v1.NewMetrics(), nil)

// when
r := l.Notify(testkube.Event{
Expand All @@ -133,7 +133,7 @@ func TestWebhookListener_Notify(t *testing.T) {
defer svr.Close()

l := NewWebhookListener("l1", svr.URL, "", testEventTypes, "", "{\"id\": \"{{ .Id }}\"}",
map[string]string{"Content-Type": "application/json"}, false, false, nil, nil, nil, v1.NewMetrics())
map[string]string{"Content-Type": "application/json"}, false, false, nil, nil, nil, v1.NewMetrics(), nil)

// when
r := l.Notify(testkube.Event{
Expand All @@ -150,7 +150,7 @@ func TestWebhookListener_Notify(t *testing.T) {
t.Parallel()
// given

s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, true, false, nil, nil, nil, v1.NewMetrics())
s := NewWebhookListener("l1", "http://baduri.badbadbad", "", testEventTypes, "", "", nil, true, false, nil, nil, nil, v1.NewMetrics(), nil)

// when
r := s.Notify(testkube.Event{
Expand Down
8 changes: 6 additions & 2 deletions pkg/event/kind/webhook/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
executorsv1 "github.com/kubeshop/testkube-operator/api/executor/v1"
templatesclientv1 "github.com/kubeshop/testkube-operator/pkg/client/templates/v1"
v1 "github.com/kubeshop/testkube/internal/app/api/metrics"
"github.com/kubeshop/testkube/internal/config"
"github.com/kubeshop/testkube/pkg/api/v1/testkube"
"github.com/kubeshop/testkube/pkg/event/kind/common"
"github.com/kubeshop/testkube/pkg/mapper/webhooks"
Expand All @@ -25,7 +26,7 @@ type WebhooksLister interface {

func NewWebhookLoader(log *zap.SugaredLogger, webhooksClient WebhooksLister, templatesClient templatesclientv1.Interface,
testExecutionResults result.Repository, testSuiteExecutionResults testresult.Repository, testWorkflowExecutionResults testworkflow.Repository,
metrics v1.Metrics,
metrics v1.Metrics, proContext *config.ProContext,
) *WebhooksLoader {
return &WebhooksLoader{
log: log,
Expand All @@ -35,6 +36,7 @@ func NewWebhookLoader(log *zap.SugaredLogger, webhooksClient WebhooksLister, tem
testSuiteExecutionResults: testSuiteExecutionResults,
testWorkflowExecutionResults: testWorkflowExecutionResults,
metrics: metrics,
proContext: proContext,
}
}

Expand All @@ -46,6 +48,7 @@ type WebhooksLoader struct {
testSuiteExecutionResults testresult.Repository
testWorkflowExecutionResults testworkflow.Repository
metrics v1.Metrics
proContext *config.ProContext
}

func (r WebhooksLoader) Kind() string {
Expand Down Expand Up @@ -83,7 +86,8 @@ func (r WebhooksLoader) Load() (listeners common.Listeners, err error) {
name := fmt.Sprintf("%s.%s", webhook.ObjectMeta.Namespace, webhook.ObjectMeta.Name)
listeners = append(listeners, NewWebhookListener(name, webhook.Spec.Uri, webhook.Spec.Selector, types,
webhook.Spec.PayloadObjectField, payloadTemplate, webhook.Spec.Headers, webhook.Spec.Disabled,
webhook.Spec.OnStateChange, r.testExecutionResults, r.testSuiteExecutionResults, r.testWorkflowExecutionResults, r.metrics))
webhook.Spec.OnStateChange, r.testExecutionResults, r.testSuiteExecutionResults, r.testWorkflowExecutionResults,
r.metrics, r.proContext))
}

return listeners, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/event/kind/webhook/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestWebhookLoader(t *testing.T) {
defer mockCtrl.Finish()

mockTemplatesClient := templatesclientv1.NewMockInterface(mockCtrl)
webhooksLoader := NewWebhookLoader(zap.NewNop().Sugar(), &DummyLoader{}, mockTemplatesClient, nil, nil, nil, v1.NewMetrics())
webhooksLoader := NewWebhookLoader(zap.NewNop().Sugar(), &DummyLoader{}, mockTemplatesClient, nil, nil, nil, v1.NewMetrics(), nil)
listeners, err := webhooksLoader.Load()

assert.Equal(t, 1, len(listeners))
Expand Down
56 changes: 56 additions & 0 deletions pkg/event/kind/webhook/templatevars.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package webhook

import (
"fmt"

"github.com/kubeshop/testkube/internal/config"
"github.com/kubeshop/testkube/pkg/api/v1/testkube"
)

type TemplateVars struct {
testkube.Event
ExecutionURL string
ExecutionCommand string
ArtifactURL string
ArtifactCommand string
LogsURL string
LogsCommand string
}

func NewTemplateVars(event testkube.Event, proContext *config.ProContext) TemplateVars {
vars := TemplateVars{
Event: event,
}

switch {
case event.TestExecution != nil:
vars.ExecutionCommand = fmt.Sprintf("kubectl testkube get execution %s", event.TestExecution.Id)
vars.ArtifactCommand = fmt.Sprintf("kubectl testkube get artifacts %s", event.TestExecution.Id)
vars.LogsCommand = fmt.Sprintf("kubectl testkube get execution %s --logs-only", event.TestExecution.Id)
case event.TestSuiteExecution != nil:
vars.ExecutionCommand = fmt.Sprintf("kubectl testkube get testsuiteexecution %s", event.TestSuiteExecution.Id)
case event.TestWorkflowExecution != nil:
vars.ExecutionCommand = fmt.Sprintf("kubectl testkube get testworkflowexecution %s", event.TestWorkflowExecution.Id)
vars.ArtifactCommand = fmt.Sprintf("kubectl testkube get artifacts %s", event.TestWorkflowExecution.Id)
vars.LogsCommand = fmt.Sprintf("kubectl testkube get testworkflowexecution %s", event.TestWorkflowExecution.Id)
}

if proContext == nil || proContext.DashboardURI == "" || proContext.OrgID == "" || proContext.EnvID == "" {
return vars
}

switch {
case event.TestExecution != nil:
vars.ExecutionURL = fmt.Sprintf("https://%s/organization/%s/environment/%s/dashboard/tests/%s/executions/%s", proContext.DashboardURI, proContext.OrgID, proContext.EnvID, event.TestExecution.TestName, event.TestExecution.Id)
vars.ArtifactURL = fmt.Sprintf("%s/artifacts", vars.ExecutionURL)
vars.LogsURL = fmt.Sprintf("%s/log-output", vars.ExecutionURL)
case event.TestSuiteExecution != nil:
vars.ExecutionURL = fmt.Sprintf("https://%s/organization/%s/environment/%s/dashboard/test-suites/%s/executions/%s", proContext.DashboardURI, proContext.OrgID, proContext.EnvID, event.TestSuiteExecution.TestSuite.Name, event.TestSuiteExecution.Id)
case event.TestWorkflowExecution != nil:
vars.ExecutionURL = fmt.Sprintf("https://%s/organization/%s/environment/%s/dashboard/test-workflows/%s/executions/%s", proContext.DashboardURI, proContext.OrgID, proContext.EnvID, event.TestWorkflowExecution.Workflow.Name, event.TestWorkflowExecution.Id)
vars.ArtifactURL = fmt.Sprintf("%s/artifacts", vars.ExecutionURL)
vars.LogsURL = fmt.Sprintf("%s/log-output", vars.ExecutionURL)
}

return vars
}

0 comments on commit 1811baf

Please sign in to comment.