Skip to content

Commit

Permalink
feat(TKC-1458): add API for managing TestWorkflows (#5041)
Browse files Browse the repository at this point in the history
* feat(TKC-1458): add boilerplate for the TCL-licensed API server endpoints
* feat(TKC-1458): add API for managing TestWorkflows/TestWorkflowTemplates
* feat(TKC-1458): add API Client methods for managing the TestWorkflows/TestWorkflowTemplates
* feat(TKC-1458): check ProContext for detecting Pro functionality in TCL API
* feat(TKC-1458): add OpenAPI definition for TestWorkflow endpoints
* feat(TKC-1458): add endpoints to delete TestWorkflows/TestWorkflowTemplates by labels
* feat(TKC-1458): adjust TestWorkflowTemplate client to reformat input template name
  • Loading branch information
rangoo94 authored Feb 22, 2024
1 parent b9233fc commit a3cb43d
Show file tree
Hide file tree
Showing 19 changed files with 1,859 additions and 46 deletions.
539 changes: 539 additions & 0 deletions api/v1/testkube.yaml

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions cmd/api-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1"
"github.com/kubeshop/testkube/pkg/imageinspector"
apitclv1 "github.com/kubeshop/testkube/pkg/tcl/apitcl/v1"

"go.mongodb.org/mongo-driver/mongo"
"google.golang.org/grpc"
Expand Down Expand Up @@ -523,9 +524,10 @@ func main() {
cfg.DisableSecretCreation,
)

var proContext *config.ProContext
if mode == common.ModeAgent {
log.DefaultLogger.Info("starting agent service")
proContext := config.ProContext{
proContext = &config.ProContext{
APIKey: cfg.TestkubeProAPIKey,
URL: cfg.TestkubeProURL,
LogsPath: cfg.TestkubeProLogsPath,
Expand All @@ -539,7 +541,7 @@ func main() {
ConnectionTimeout: cfg.TestkubeProConnectionTimeout,
}

api.WithProContext(&proContext)
api.WithProContext(proContext)

agentHandle, err := agent.NewAgent(
log.DefaultLogger,
Expand All @@ -550,7 +552,7 @@ func main() {
cfg.TestkubeClusterName,
envs,
features,
proContext,
*proContext,
)
if err != nil {
ui.ExitOnError("Starting agent", err)
Expand All @@ -565,6 +567,9 @@ func main() {
eventsEmitter.Loader.Register(agentHandle)
}

// Apply Pro server enhancements
apitclv1.NewApiTCL(api, proContext, kubeClient).AppendRoutes()

api.InitEvents()

if !cfg.DisableTestTriggers {
Expand Down
156 changes: 132 additions & 24 deletions internal/app/api/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,36 +71,78 @@ var testAbortCount = promauto.NewCounterVec(prometheus.CounterOpts{
Help: "The total number of tests aborted by type events",
}, []string{"type", "result"})

var testWorkflowCreationCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "testkube_testworkflow_creations_count",
Help: "The total number of test workflow created by type events",
}, []string{"result"})

var testWorkflowUpdatesCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "testkube_testworkflow_updates_count",
Help: "The total number of test workflow updated by type events",
}, []string{"result"})

var testWorkflowDeletesCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "testkube_testworkflow_deletes_count",
Help: "The total number of test workflow deleted events",
}, []string{"result"})

var testWorkflowTemplateCreationCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "testkube_testworkflowtemplate_creations_count",
Help: "The total number of test workflow template created by type events",
}, []string{"result"})

var testWorkflowTemplateUpdatesCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "testkube_testworkflowtemplate_updates_count",
Help: "The total number of test workflow template updated by type events",
}, []string{"result"})

var testWorkflowTemplateDeletesCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "testkube_testworkflowtemplate_deletes_count",
Help: "The total number of test workflow template deleted events",
}, []string{"result"})

func NewMetrics() Metrics {
return Metrics{
TestExecutions: testExecutionCount,
TestSuiteExecutions: testSuiteExecutionCount,
TestCreations: testCreationCount,
TestSuiteCreations: testSuiteCreationCount,
TestUpdates: testUpdatesCount,
TestSuiteUpdates: testSuiteUpdatesCount,
TestTriggerCreations: testTriggerCreationCount,
TestTriggerUpdates: testTriggerUpdatesCount,
TestTriggerDeletes: testTriggerDeletesCount,
TestTriggerBulkUpdates: testTriggerBulkUpdatesCount,
TestTriggerBulkDeletes: testTriggerBulkDeletesCount,
TestAbort: testAbortCount,
TestExecutions: testExecutionCount,
TestSuiteExecutions: testSuiteExecutionCount,
TestCreations: testCreationCount,
TestSuiteCreations: testSuiteCreationCount,
TestUpdates: testUpdatesCount,
TestSuiteUpdates: testSuiteUpdatesCount,
TestTriggerCreations: testTriggerCreationCount,
TestTriggerUpdates: testTriggerUpdatesCount,
TestTriggerDeletes: testTriggerDeletesCount,
TestTriggerBulkUpdates: testTriggerBulkUpdatesCount,
TestTriggerBulkDeletes: testTriggerBulkDeletesCount,
TestAbort: testAbortCount,
TestWorkflowCreations: testWorkflowCreationCount,
TestWorkflowUpdates: testWorkflowUpdatesCount,
TestWorkflowDeletes: testWorkflowDeletesCount,
TestWorkflowTemplateCreations: testWorkflowTemplateCreationCount,
TestWorkflowTemplateUpdates: testWorkflowTemplateUpdatesCount,
TestWorkflowTemplateDeletes: testWorkflowTemplateDeletesCount,
}
}

type Metrics struct {
TestExecutions *prometheus.CounterVec
TestSuiteExecutions *prometheus.CounterVec
TestCreations *prometheus.CounterVec
TestSuiteCreations *prometheus.CounterVec
TestUpdates *prometheus.CounterVec
TestSuiteUpdates *prometheus.CounterVec
TestTriggerCreations *prometheus.CounterVec
TestTriggerUpdates *prometheus.CounterVec
TestTriggerDeletes *prometheus.CounterVec
TestTriggerBulkUpdates *prometheus.CounterVec
TestTriggerBulkDeletes *prometheus.CounterVec
TestAbort *prometheus.CounterVec
TestExecutions *prometheus.CounterVec
TestSuiteExecutions *prometheus.CounterVec
TestCreations *prometheus.CounterVec
TestSuiteCreations *prometheus.CounterVec
TestUpdates *prometheus.CounterVec
TestSuiteUpdates *prometheus.CounterVec
TestTriggerCreations *prometheus.CounterVec
TestTriggerUpdates *prometheus.CounterVec
TestTriggerDeletes *prometheus.CounterVec
TestTriggerBulkUpdates *prometheus.CounterVec
TestTriggerBulkDeletes *prometheus.CounterVec
TestAbort *prometheus.CounterVec
TestWorkflowCreations *prometheus.CounterVec
TestWorkflowUpdates *prometheus.CounterVec
TestWorkflowDeletes *prometheus.CounterVec
TestWorkflowTemplateCreations *prometheus.CounterVec
TestWorkflowTemplateUpdates *prometheus.CounterVec
TestWorkflowTemplateDeletes *prometheus.CounterVec
}

func (m Metrics) IncExecuteTest(execution testkube.Execution, dashboardURI string) {
Expand Down Expand Up @@ -266,3 +308,69 @@ func (m Metrics) IncAbortTest(testType string, failed bool) {
"result": result,
}).Inc()
}

func (m Metrics) IncCreateTestWorkflow(err error) {
result := "created"
if err != nil {
result = "error"
}

m.TestWorkflowCreations.With(map[string]string{
"result": result,
}).Inc()
}

func (m Metrics) IncUpdateTestWorkflow(err error) {
result := "updated"
if err != nil {
result = "error"
}

m.TestWorkflowUpdates.With(map[string]string{
"result": result,
}).Inc()
}

func (m Metrics) IncDeleteTestWorkflow(err error) {
result := "deleted"
if err != nil {
result = "error"
}

m.TestWorkflowDeletes.With(map[string]string{
"result": result,
}).Inc()
}

func (m Metrics) IncCreateTestWorkflowTemplate(err error) {
result := "created"
if err != nil {
result = "error"
}

m.TestWorkflowTemplateCreations.With(map[string]string{
"result": result,
}).Inc()
}

func (m Metrics) IncUpdateTestWorkflowTemplate(err error) {
result := "updated"
if err != nil {
result = "error"
}

m.TestWorkflowTemplateUpdates.With(map[string]string{
"result": result,
}).Inc()
}

func (m Metrics) IncDeleteTestWorkflowTemplate(err error) {
result := "deleted"
if err != nil {
result = "error"
}

m.TestWorkflowTemplateDeletes.With(map[string]string{
"result": result,
}).Inc()
}
126 changes: 126 additions & 0 deletions internal/common/crd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package common

import (
"encoding/json"
"reflect"
"regexp"

"gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
)

type SerializeOptions struct {
OmitCreationTimestamp bool
CleanMeta bool
Kind string
GroupVersion *schema.GroupVersion
}

type ObjectWithTypeMeta interface {
SetGroupVersionKind(schema.GroupVersionKind)
}

func AppendTypeMeta(kind string, version schema.GroupVersion, crs ...ObjectWithTypeMeta) {
for _, cr := range crs {
cr.SetGroupVersionKind(schema.GroupVersionKind{
Group: version.Group,
Version: version.Version,
Kind: kind,
})
}
}

func CleanObjectMeta(crs ...metav1.Object) {
for _, cr := range crs {
cr.SetGeneration(0)
cr.SetResourceVersion("")
cr.SetSelfLink("")
cr.SetUID("")
cr.SetFinalizers(nil)
cr.SetOwnerReferences(nil)
cr.SetManagedFields(nil)

annotations := cr.GetAnnotations()
delete(annotations, "kubectl.kubernetes.io/last-applied-configuration")
cr.SetAnnotations(annotations)
}
}

var creationTsNullRegex = regexp.MustCompile(`\n\s+creationTimestamp: null`)
var creationTsRegex = regexp.MustCompile(`\n\s+creationTimestamp:[^\n]*`)

func SerializeCRD(cr interface{}, opts SerializeOptions) ([]byte, error) {
if opts.CleanMeta || (opts.Kind != "" && opts.GroupVersion != nil) {
// For simplicity, support both direct struct (as in *List.Items), as well as the pointer itself
if reflect.ValueOf(cr).Kind() == reflect.Struct {
v := reflect.ValueOf(cr)
p := reflect.New(v.Type())
p.Elem().Set(v)
cr = p.Interface()
}

// Deep copy object, as it will have modifications
switch cr.(type) {
case runtime.Object:
cr = cr.(runtime.Object).DeepCopyObject()
}

// Clean messy metadata
if opts.CleanMeta {
if v, ok := cr.(metav1.Object); ok {
CleanObjectMeta(v)
cr = v
}
}

// Append metadata when expected
if opts.Kind != "" && opts.GroupVersion != nil {
if v, ok := cr.(ObjectWithTypeMeta); ok {
AppendTypeMeta(opts.Kind, *opts.GroupVersion, v)
cr = v
}
}
}

out, err := json.Marshal(cr)
if err != nil {
return nil, err
}
m := yaml.MapSlice{}
_ = yaml.Unmarshal(out, &m)
b, _ := yaml.Marshal(m)
if opts.OmitCreationTimestamp {
b = creationTsRegex.ReplaceAll(b, nil)
} else {
b = creationTsNullRegex.ReplaceAll(b, nil)
}
return b, err
}

var crdSeparator = []byte("---\n")

// SerializeCRDs builds a serialized version of CRD,
// persisting the order of properties from the struct.
func SerializeCRDs[T interface{}](crs []T, opts SerializeOptions) ([]byte, error) {
result := []byte(nil)
for _, cr := range crs {
b, err := SerializeCRD(cr, opts)
if err != nil {
return nil, err
}
if len(result) > 0 {
result = append(append(result, crdSeparator...), b...)
} else {
result = b
}
}
return result, nil
}

func DeserializeCRD(cr runtime.Object, content []byte) error {
_, _, err := scheme.Codecs.UniversalDeserializer().Decode(content, nil, cr)
return err
}
Loading

0 comments on commit a3cb43d

Please sign in to comment.