From d6432aed96a4374442167cdd6b77416f2f6a7909 Mon Sep 17 00:00:00 2001
From: nicufk
Date: Mon, 15 Jan 2024 12:21:06 +0200
Subject: [PATCH 001/234] fix: complete repository empty check (#4889)
* fix: complete repository empty check
* fix: extract method for empty check
---
internal/app/api/v1/tests.go | 13 ++++++++++++-
pkg/api/v1/testkube/model_repository_extended.go | 8 +++++++-
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/internal/app/api/v1/tests.go b/internal/app/api/v1/tests.go
index aa7df7d6beb..6eefb5644d7 100644
--- a/internal/app/api/v1/tests.go
+++ b/internal/app/api/v1/tests.go
@@ -449,7 +449,7 @@ func (s TestkubeAPI) UpdateTestHandler() fiber.Handler {
}
}
- if testSpec.Spec.Content != nil && testSpec.Spec.Content.Repository != nil && testSpec.Spec.Content.Repository.Uri == "" {
+ if isRepositoryEmpty(testSpec.Spec) {
testSpec.Spec.Content.Repository = nil
}
@@ -470,6 +470,17 @@ func (s TestkubeAPI) UpdateTestHandler() fiber.Handler {
}
}
+func isRepositoryEmpty(s testsv3.TestSpec) bool {
+ return s.Content != nil &&
+ s.Content.Repository != nil &&
+ s.Content.Repository.Type_ == "" &&
+ s.Content.Repository.Uri == "" &&
+ s.Content.Repository.Branch == "" &&
+ s.Content.Repository.Path == "" &&
+ s.Content.Repository.Commit == "" &&
+ s.Content.Repository.WorkingDir == ""
+}
+
// DeleteTestHandler is a method for deleting a test with id
func (s TestkubeAPI) DeleteTestHandler() fiber.Handler {
return func(c *fiber.Ctx) error {
diff --git a/pkg/api/v1/testkube/model_repository_extended.go b/pkg/api/v1/testkube/model_repository_extended.go
index 5bfef92f3b3..42891051506 100644
--- a/pkg/api/v1/testkube/model_repository_extended.go
+++ b/pkg/api/v1/testkube/model_repository_extended.go
@@ -41,5 +41,11 @@ func (r *Repository) WithAuthType(authType GitAuthType) *Repository {
// IsEmpty returns true if repository is empty
func (r *Repository) IsEmpty() bool {
- return r == nil || r.Uri == ""
+ return r == nil ||
+ (r.Type_ == "" &&
+ r.Uri == "" &&
+ r.Branch == "" &&
+ r.Path == "" &&
+ r.Commit == "" &&
+ r.WorkingDir == "")
}
From 021a0e8ccd9b8985a1be400981cbafecf3211731 Mon Sep 17 00:00:00 2001
From: Tomasz Konieczny
Date: Mon, 15 Jan 2024 12:44:22 +0100
Subject: [PATCH 002/234] feat: Executor tests - JMeter/JMeterd - extended
other cases, special cases (#4894)
* executor tests - jmeter-executor-smoke-2.jmx
* executor tests - jmeter - other and special cases extended
* end - empty line
* jmeter executor smoke - suite updated
* jmeter executor tests - special cases
* jmeter executor tests - special cases - testsuite
* executor tests - run script updated
* executor tests - branch name updated before merge
* end - empty line
---
test/jmeter/executor-tests/crd/other.yaml | 59 +++++++
test/jmeter/executor-tests/crd/smoke.yaml | 28 ++-
.../executor-tests/crd/special-cases.yaml | 166 ++++++++++++++++++
.../jmeter-executor-smoke-2.jmx | 94 ++++++++++
test/scripts/executor-tests/run.sh | 12 +-
test/suites/executor-jmeter-other-tests.yaml | 8 +-
test/suites/executor-jmeter-smoke-tests.yaml | 3 +
.../special-cases/jmeter-special-cases.yaml | 24 +++
8 files changed, 391 insertions(+), 3 deletions(-)
create mode 100644 test/jmeter/executor-tests/crd/special-cases.yaml
create mode 100644 test/jmeter/executor-tests/jmeter-executor-smoke-2.jmx
create mode 100644 test/suites/special-cases/jmeter-special-cases.yaml
diff --git a/test/jmeter/executor-tests/crd/other.yaml b/test/jmeter/executor-tests/crd/other.yaml
index 845797cdf83..03eaf3ca9fd 100644
--- a/test/jmeter/executor-tests/crd/other.yaml
+++ b/test/jmeter/executor-tests/crd/other.yaml
@@ -151,3 +151,62 @@ spec:
limits:
cpu: 500m
memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-slave-0 # standalone mode
+ labels:
+ core-tests: executors
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests/jmeter-executor-smoke.jmx
+ executionRequest:
+ variables:
+ SLAVES_COUNT:
+ name: SLAVES_COUNT
+ value: "0"
+ type: basic
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-slave-not-set # standalone mode
+ labels:
+ core-tests: executors
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests/jmeter-executor-smoke.jmx
+ executionRequest:
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
diff --git a/test/jmeter/executor-tests/crd/smoke.yaml b/test/jmeter/executor-tests/crd/smoke.yaml
index 67b174a63f4..525a2501328 100644
--- a/test/jmeter/executor-tests/crd/smoke.yaml
+++ b/test/jmeter/executor-tests/crd/smoke.yaml
@@ -114,7 +114,7 @@ spec:
apiVersion: tests.testkube.io/v3
kind: Test
metadata:
- name: jmeterd-executor-smoke-slave-1
+ name: jmeterd-executor-smoke-slave-1 # standalone mode
labels:
core-tests: executors
spec:
@@ -174,3 +174,29 @@ spec:
limits:
cpu: 500m
memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-env-and-property-values
+ labels:
+ core-tests: executors
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests/jmeter-executor-smoke-env-and-property.jmx
+ executionRequest:
+ variables:
+ URL_ENV:
+ name: URL_ENV
+ value: "testkube.io"
+ type: basic
+ args:
+ - "-JURL_PROPERTY=testkube.io"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
diff --git a/test/jmeter/executor-tests/crd/special-cases.yaml b/test/jmeter/executor-tests/crd/special-cases.yaml
new file mode 100644
index 00000000000..a073e6e9c97
--- /dev/null
+++ b/test/jmeter/executor-tests/crd/special-cases.yaml
@@ -0,0 +1,166 @@
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-custom-envs-replication # TODO: validation on the test side
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests/jmeter-executor-smoke.jmx
+ executionRequest:
+ variables:
+ SLAVES_COUNT:
+ name: SLAVES_COUNT
+ value: "2"
+ type: basic
+ CUSTOM_ENV_VARIABLE:
+ name: CUSTOM_ENV_VARIABLE
+ value: CUSTOM_ENV_VARIABLE_value
+ type: basic
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-env-value-in-args
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests
+ executionRequest:
+ variables:
+ JMETER_SCRIPT:
+ name: JMETER_SCRIPT
+ value: jmeter-executor-smoke.jmx
+ type: basic
+ args:
+ - "${JMETER_SCRIPT}"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-directory-1
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests
+ executionRequest:
+ args:
+ - "jmeter-executor-smoke.jmx"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-directory-2
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests
+ executionRequest:
+ args:
+ - "jmeter-executor-smoke-2.jmx"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-slaves-sharedbetweenpods # can be run only at cluster with storageClassName (NFS volume)
+ labels:
+ core-tests: executors
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests/jmeter-executor-smoke.jmx
+ executionRequest:
+ executePostRunScriptBeforeScraping: true
+ postRunScript: "echo \"postrun script\" && echo \"artifact file - contents\" > /data/output/artifact-`uuidgen`.txt"
+ artifactRequest:
+ storageClassName: standard-rwx
+ masks:
+ - .*\.txt
+ sharedBetweenPods: true
+ variables:
+ SLAVES_COUNT:
+ name: SLAVES_COUNT
+ value: "2"
+ type: basic
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ # activeDeadlineSeconds: 180 TODO: increase - too low to create volume
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
diff --git a/test/jmeter/executor-tests/jmeter-executor-smoke-2.jmx b/test/jmeter/executor-tests/jmeter-executor-smoke-2.jmx
new file mode 100644
index 00000000000..6dc469a6d51
--- /dev/null
+++ b/test/jmeter/executor-tests/jmeter-executor-smoke-2.jmx
@@ -0,0 +1,94 @@
+
+
+
+
+
+ false
+ false
+
+
+
+
+
+
+
+ continue
+
+ false
+ 1
+
+ 1
+ 1
+ 1668426657000
+ 1668426657000
+ false
+
+
+
+
+
+
+
+
+ testkube.kubeshop.io
+
+
+
+
+
+
+ GET
+ true
+ false
+ true
+ false
+ false
+
+
+
+
+
+ 200
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+ true
+
+
+
+
+
+
+
+
+
diff --git a/test/scripts/executor-tests/run.sh b/test/scripts/executor-tests/run.sh
index a5919a28e9e..4d471a7e7da 100755
--- a/test/scripts/executor-tests/run.sh
+++ b/test/scripts/executor-tests/run.sh
@@ -329,6 +329,15 @@ special-cases-large-artifacts() {
common_run "$name" "$test_crd_file" "$testsuite_name" "$testsuite_file" "$custom_executor_crd_file"
}
+special-cases-jmeter() {
+ name="Special Cases - JMeter/JMeterd"
+ test_crd_file="test/jmeter/executor-tests/crd/special-cases.yaml"
+ testsuite_name="jmeter-special-cases"
+ testsuite_file="test/suites/special-cases/jmeter-special-cases.yaml"
+
+ common_run "$name" "$test_crd_file" "$testsuite_name" "$testsuite_file"
+}
+
main() {
case $executor_type in
all)
@@ -375,6 +384,7 @@ main() {
special-cases-failures
special-cases-large-logs
special-cases-large-artifacts
+ special-cases-jmeter
;;
*)
$executor_type
@@ -390,4 +400,4 @@ main() {
fi
}
-main
\ No newline at end of file
+main
diff --git a/test/suites/executor-jmeter-other-tests.yaml b/test/suites/executor-jmeter-other-tests.yaml
index 6ed16725314..9d99b59c4f4 100644
--- a/test/suites/executor-jmeter-other-tests.yaml
+++ b/test/suites/executor-jmeter-other-tests.yaml
@@ -5,7 +5,7 @@ metadata:
labels:
core-tests: executors
spec:
- description: "jmeter and jmeterd executor - other tests and edge-cases"
+ description: "jmeter and jmeterd executor - other tests"
steps:
- stopOnFailure: false
execute:
@@ -25,3 +25,9 @@ spec:
- stopOnFailure: false
execute:
- test: jmeterd-executor-smoke-failure-exit-code-0-negative
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-slave-0
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-slave-not-set
diff --git a/test/suites/executor-jmeter-smoke-tests.yaml b/test/suites/executor-jmeter-smoke-tests.yaml
index f71f53907a5..df0a0fad3af 100644
--- a/test/suites/executor-jmeter-smoke-tests.yaml
+++ b/test/suites/executor-jmeter-smoke-tests.yaml
@@ -28,3 +28,6 @@ spec:
- stopOnFailure: false
execute:
- test: jmeterd-executor-smoke-slaves
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-env-and-property-values
diff --git a/test/suites/special-cases/jmeter-special-cases.yaml b/test/suites/special-cases/jmeter-special-cases.yaml
new file mode 100644
index 00000000000..afac7662614
--- /dev/null
+++ b/test/suites/special-cases/jmeter-special-cases.yaml
@@ -0,0 +1,24 @@
+apiVersion: tests.testkube.io/v3
+kind: TestSuite
+metadata:
+ name: jmeter-special-cases
+ labels:
+ core-tests: special-cases
+spec:
+ description: "jmeter and jmeterd executor - special-cases"
+ steps:
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-custom-envs-replication
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-env-value-in-args
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-directory-1
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-directory-2
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-slaves-sharedbetweenpods
From 3d7fdca24db183ad7670476840125462a822c5ea Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Mon, 15 Jan 2024 18:57:11 +0300
Subject: [PATCH 003/234] fix: pvc name
---
config/slave-pod-template.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/slave-pod-template.yml b/config/slave-pod-template.yml
index 0530fd864ac..33d6fd9f8fb 100644
--- a/config/slave-pod-template.yml
+++ b/config/slave-pod-template.yml
@@ -139,7 +139,7 @@ spec:
{{- if and .ArtifactRequest.VolumeMountPath .ArtifactRequest.StorageClassName }}
- name: artifact-volume
persistentVolumeClaim:
- claimName: {{ .Name }}-pvc
+ claimName: {{ .JobName }}-pvc
{{- end }}
{{- end }}
{{- range $configmap := .EnvConfigMaps }}
From f9ec6deea14438bcdf2c5e5a5c9e310ed016726b Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Tue, 16 Jan 2024 10:38:55 +0100
Subject: [PATCH 004/234] =?UTF-8?q?feat:=20refactored=20logs=20stream=20to?=
=?UTF-8?q?=20allow=20to=20be=20passed=20and=20initialized=20la=E2=80=A6?=
=?UTF-8?q?=20(#4892)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: refactored logs stream to allow to be passed and initialized later with id
* feat: added logs for test scheduler errors
* fix: renamed repos
* fix: initialize logs stream only if logs v2 enabled
* Update pkg/logs/events/events.go
Co-authored-by: Lilla Vass
* fix: comments
* fix: added logs
* fix: logs metghod
---------
Co-authored-by: Lilla Vass
---
cmd/api-server/main.go | 11 ++
cmd/sidecar/main.go | 2 +-
pkg/logs/client/interface.go | 26 +++-
pkg/logs/client/mock_client.go | 50 +++++++
.../client/mock_initializedstreamgetter.go | 66 ++++++++++
.../client/mock_initializedstreampusher.go | 79 +++++++++++
pkg/logs/client/mock_stream.go | 124 ++++++++++++++++++
pkg/logs/client/stream.go | 63 +++++----
pkg/logs/client/stream_test.go | 17 ++-
pkg/logs/events/events.go | 43 ++++--
pkg/logs/events_test.go | 28 ++--
pkg/logs/sidecar/proxy.go | 13 +-
pkg/scheduler/service.go | 12 +-
pkg/scheduler/test_scheduler.go | 52 +++++---
pkg/scheduler/testsuite_scheduler.go | 16 +--
pkg/triggers/executor_test.go | 4 +
pkg/triggers/service_test.go | 19 +--
pkg/triggers/watcher.go | 3 +-
18 files changed, 509 insertions(+), 119 deletions(-)
create mode 100644 pkg/logs/client/mock_client.go
create mode 100644 pkg/logs/client/mock_initializedstreamgetter.go
create mode 100644 pkg/logs/client/mock_initializedstreampusher.go
create mode 100644 pkg/logs/client/mock_stream.go
diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go
index f25259182b8..86cd6ff8eb1 100644
--- a/cmd/api-server/main.go
+++ b/cmd/api-server/main.go
@@ -58,6 +58,7 @@ import (
kubeexecutor "github.com/kubeshop/testkube/pkg/executor"
"github.com/kubeshop/testkube/pkg/executor/client"
"github.com/kubeshop/testkube/pkg/executor/containerexecutor"
+ logsclient "github.com/kubeshop/testkube/pkg/logs/client"
"github.com/kubeshop/testkube/pkg/scheduler"
testkubeclientset "github.com/kubeshop/testkube-operator/pkg/clientset/versioned"
@@ -348,6 +349,15 @@ func main() {
eventBus := bus.NewNATSBus(nc)
eventsEmitter := event.NewEmitter(eventBus, cfg.TestkubeClusterName, envs)
+ var logsStream logsclient.Stream
+
+ if ff.LogsV2 {
+ logsStream, err = logsclient.NewNatsLogStream(nc.Conn)
+ if err != nil {
+ ui.ExitOnError("Creating logs streaming client", err)
+ }
+ }
+
metrics := metrics.NewMetrics()
defaultExecutors, err := parseDefaultExecutors(cfg)
@@ -439,6 +449,7 @@ func main() {
eventBus,
cfg.TestkubeDashboardURI,
ff,
+ logsStream,
)
slackLoader, err := newSlackLoader(cfg, envs)
diff --git a/cmd/sidecar/main.go b/cmd/sidecar/main.go
index d2a54bb19e7..fab7ec5f0ff 100644
--- a/cmd/sidecar/main.go
+++ b/cmd/sidecar/main.go
@@ -39,7 +39,7 @@ func main() {
podsClient := clientset.CoreV1().Pods(cfg.Namespace)
- logsStream, err := client.NewNatsLogStream(nc, cfg.ExecutionId)
+ logsStream, err := client.NewNatsLogStream(nc)
if err != nil {
ui.ExitOnError("error creating logs stream", err)
return
diff --git a/pkg/logs/client/interface.go b/pkg/logs/client/interface.go
index 6f85f45fb68..595dffa93db 100644
--- a/pkg/logs/client/interface.go
+++ b/pkg/logs/client/interface.go
@@ -13,10 +13,12 @@ const (
StopSubject = "events.logs.stop"
)
+//go:generate mockgen -destination=./mock_client.go -package=client "github.com/kubeshop/testkube/pkg/logs/client" Client
type Client interface {
Get(ctx context.Context, id string) chan events.LogResponse
}
+//go:generate mockgen -destination=./mock_stream.go -package=client "github.com/kubeshop/testkube/pkg/logs/client" Stream
type Stream interface {
StreamInitializer
StreamPusher
@@ -24,26 +26,38 @@ type Stream interface {
StreamGetter
}
+//go:generate mockgen -destination=./mock_initializedstreampusher.go -package=client "github.com/kubeshop/testkube/pkg/logs/client" InitializedStreamPusher
+type InitializedStreamPusher interface {
+ StreamInitializer
+ StreamPusher
+}
+
+//go:generate mockgen -destination=./mock_initializedstreamgetter.go -package=client "github.com/kubeshop/testkube/pkg/logs/client" InitializedStreamGetter
+type InitializedStreamGetter interface {
+ StreamInitializer
+ StreamGetter
+}
+
type StreamMetadata struct {
Name string
}
type StreamInitializer interface {
// Init creates or updates stream on demand
- Init(ctx context.Context) (meta StreamMetadata, err error)
+ Init(ctx context.Context, id string) (meta StreamMetadata, err error)
}
type StreamPusher interface {
// Push sends logs to log stream
- Push(ctx context.Context, chunk events.Log) error
+ Push(ctx context.Context, id string, chunk events.Log) error
// PushBytes sends RAW bytes to log stream, developer is responsible for marshaling valid data
- PushBytes(ctx context.Context, chunk []byte) error
+ PushBytes(ctx context.Context, id string, chunk []byte) error
}
// LogStream is a single log stream chunk with possible errors
type StreamGetter interface {
// Init creates or updates stream on demand
- Get(ctx context.Context) (chan events.LogResponse, error)
+ Get(ctx context.Context, id string) (chan events.LogResponse, error)
}
type StreamConfigurer interface {
@@ -63,7 +77,7 @@ type StreamResponse struct {
type StreamTrigger interface {
// Trigger start event
- Start(ctx context.Context) (StreamResponse, error)
+ Start(ctx context.Context, id string) (StreamResponse, error)
// Trigger stop event
- Stop(ctx context.Context) (StreamResponse, error)
+ Stop(ctx context.Context, id string) (StreamResponse, error)
}
diff --git a/pkg/logs/client/mock_client.go b/pkg/logs/client/mock_client.go
new file mode 100644
index 00000000000..a87031ea1af
--- /dev/null
+++ b/pkg/logs/client/mock_client.go
@@ -0,0 +1,50 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/kubeshop/testkube/pkg/logs/client (interfaces: Client)
+
+// Package client is a generated GoMock package.
+package client
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ events "github.com/kubeshop/testkube/pkg/logs/events"
+)
+
+// MockClient is a mock of Client interface.
+type MockClient struct {
+ ctrl *gomock.Controller
+ recorder *MockClientMockRecorder
+}
+
+// MockClientMockRecorder is the mock recorder for MockClient.
+type MockClientMockRecorder struct {
+ mock *MockClient
+}
+
+// NewMockClient creates a new mock instance.
+func NewMockClient(ctrl *gomock.Controller) *MockClient {
+ mock := &MockClient{ctrl: ctrl}
+ mock.recorder = &MockClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockClient) EXPECT() *MockClientMockRecorder {
+ return m.recorder
+}
+
+// Get mocks base method.
+func (m *MockClient) Get(arg0 context.Context, arg1 string) chan events.LogResponse {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Get", arg0, arg1)
+ ret0, _ := ret[0].(chan events.LogResponse)
+ return ret0
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockClientMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), arg0, arg1)
+}
diff --git a/pkg/logs/client/mock_initializedstreamgetter.go b/pkg/logs/client/mock_initializedstreamgetter.go
new file mode 100644
index 00000000000..911f3f3f5cd
--- /dev/null
+++ b/pkg/logs/client/mock_initializedstreamgetter.go
@@ -0,0 +1,66 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/kubeshop/testkube/pkg/logs/client (interfaces: InitializedStreamGetter)
+
+// Package client is a generated GoMock package.
+package client
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ events "github.com/kubeshop/testkube/pkg/logs/events"
+)
+
+// MockInitializedStreamGetter is a mock of InitializedStreamGetter interface.
+type MockInitializedStreamGetter struct {
+ ctrl *gomock.Controller
+ recorder *MockInitializedStreamGetterMockRecorder
+}
+
+// MockInitializedStreamGetterMockRecorder is the mock recorder for MockInitializedStreamGetter.
+type MockInitializedStreamGetterMockRecorder struct {
+ mock *MockInitializedStreamGetter
+}
+
+// NewMockInitializedStreamGetter creates a new mock instance.
+func NewMockInitializedStreamGetter(ctrl *gomock.Controller) *MockInitializedStreamGetter {
+ mock := &MockInitializedStreamGetter{ctrl: ctrl}
+ mock.recorder = &MockInitializedStreamGetterMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockInitializedStreamGetter) EXPECT() *MockInitializedStreamGetterMockRecorder {
+ return m.recorder
+}
+
+// Get mocks base method.
+func (m *MockInitializedStreamGetter) Get(arg0 context.Context, arg1 string) (chan events.LogResponse, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Get", arg0, arg1)
+ ret0, _ := ret[0].(chan events.LogResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockInitializedStreamGetterMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInitializedStreamGetter)(nil).Get), arg0, arg1)
+}
+
+// Init mocks base method.
+func (m *MockInitializedStreamGetter) Init(arg0 context.Context, arg1 string) (StreamMetadata, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Init", arg0, arg1)
+ ret0, _ := ret[0].(StreamMetadata)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Init indicates an expected call of Init.
+func (mr *MockInitializedStreamGetterMockRecorder) Init(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockInitializedStreamGetter)(nil).Init), arg0, arg1)
+}
diff --git a/pkg/logs/client/mock_initializedstreampusher.go b/pkg/logs/client/mock_initializedstreampusher.go
new file mode 100644
index 00000000000..9892c9b9ef2
--- /dev/null
+++ b/pkg/logs/client/mock_initializedstreampusher.go
@@ -0,0 +1,79 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/kubeshop/testkube/pkg/logs/client (interfaces: InitializedStreamPusher)
+
+// Package client is a generated GoMock package.
+package client
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ events "github.com/kubeshop/testkube/pkg/logs/events"
+)
+
+// MockInitializedStreamPusher is a mock of InitializedStreamPusher interface.
+type MockInitializedStreamPusher struct {
+ ctrl *gomock.Controller
+ recorder *MockInitializedStreamPusherMockRecorder
+}
+
+// MockInitializedStreamPusherMockRecorder is the mock recorder for MockInitializedStreamPusher.
+type MockInitializedStreamPusherMockRecorder struct {
+ mock *MockInitializedStreamPusher
+}
+
+// NewMockInitializedStreamPusher creates a new mock instance.
+func NewMockInitializedStreamPusher(ctrl *gomock.Controller) *MockInitializedStreamPusher {
+ mock := &MockInitializedStreamPusher{ctrl: ctrl}
+ mock.recorder = &MockInitializedStreamPusherMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockInitializedStreamPusher) EXPECT() *MockInitializedStreamPusherMockRecorder {
+ return m.recorder
+}
+
+// Init mocks base method.
+func (m *MockInitializedStreamPusher) Init(arg0 context.Context, arg1 string) (StreamMetadata, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Init", arg0, arg1)
+ ret0, _ := ret[0].(StreamMetadata)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Init indicates an expected call of Init.
+func (mr *MockInitializedStreamPusherMockRecorder) Init(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockInitializedStreamPusher)(nil).Init), arg0, arg1)
+}
+
+// Push mocks base method.
+func (m *MockInitializedStreamPusher) Push(arg0 context.Context, arg1 string, arg2 events.Log) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Push", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Push indicates an expected call of Push.
+func (mr *MockInitializedStreamPusherMockRecorder) Push(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockInitializedStreamPusher)(nil).Push), arg0, arg1, arg2)
+}
+
+// PushBytes mocks base method.
+func (m *MockInitializedStreamPusher) PushBytes(arg0 context.Context, arg1 string, arg2 []byte) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "PushBytes", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// PushBytes indicates an expected call of PushBytes.
+func (mr *MockInitializedStreamPusherMockRecorder) PushBytes(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushBytes", reflect.TypeOf((*MockInitializedStreamPusher)(nil).PushBytes), arg0, arg1, arg2)
+}
diff --git a/pkg/logs/client/mock_stream.go b/pkg/logs/client/mock_stream.go
new file mode 100644
index 00000000000..4b605daacc9
--- /dev/null
+++ b/pkg/logs/client/mock_stream.go
@@ -0,0 +1,124 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/kubeshop/testkube/pkg/logs/client (interfaces: Stream)
+
+// Package client is a generated GoMock package.
+package client
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ events "github.com/kubeshop/testkube/pkg/logs/events"
+)
+
+// MockStream is a mock of Stream interface.
+type MockStream struct {
+ ctrl *gomock.Controller
+ recorder *MockStreamMockRecorder
+}
+
+// MockStreamMockRecorder is the mock recorder for MockStream.
+type MockStreamMockRecorder struct {
+ mock *MockStream
+}
+
+// NewMockStream creates a new mock instance.
+func NewMockStream(ctrl *gomock.Controller) *MockStream {
+ mock := &MockStream{ctrl: ctrl}
+ mock.recorder = &MockStreamMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockStream) EXPECT() *MockStreamMockRecorder {
+ return m.recorder
+}
+
+// Get mocks base method.
+func (m *MockStream) Get(arg0 context.Context, arg1 string) (chan events.LogResponse, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Get", arg0, arg1)
+ ret0, _ := ret[0].(chan events.LogResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockStreamMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStream)(nil).Get), arg0, arg1)
+}
+
+// Init mocks base method.
+func (m *MockStream) Init(arg0 context.Context, arg1 string) (StreamMetadata, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Init", arg0, arg1)
+ ret0, _ := ret[0].(StreamMetadata)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Init indicates an expected call of Init.
+func (mr *MockStreamMockRecorder) Init(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockStream)(nil).Init), arg0, arg1)
+}
+
+// Push mocks base method.
+func (m *MockStream) Push(arg0 context.Context, arg1 string, arg2 events.Log) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Push", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Push indicates an expected call of Push.
+func (mr *MockStreamMockRecorder) Push(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockStream)(nil).Push), arg0, arg1, arg2)
+}
+
+// PushBytes mocks base method.
+func (m *MockStream) PushBytes(arg0 context.Context, arg1 string, arg2 []byte) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "PushBytes", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// PushBytes indicates an expected call of PushBytes.
+func (mr *MockStreamMockRecorder) PushBytes(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushBytes", reflect.TypeOf((*MockStream)(nil).PushBytes), arg0, arg1, arg2)
+}
+
+// Start mocks base method.
+func (m *MockStream) Start(arg0 context.Context, arg1 string) (StreamResponse, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Start", arg0, arg1)
+ ret0, _ := ret[0].(StreamResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Start indicates an expected call of Start.
+func (mr *MockStreamMockRecorder) Start(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockStream)(nil).Start), arg0, arg1)
+}
+
+// Stop mocks base method.
+func (m *MockStream) Stop(arg0 context.Context, arg1 string) (StreamResponse, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Stop", arg0, arg1)
+ ret0, _ := ret[0].(StreamResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Stop indicates an expected call of Stop.
+func (mr *MockStreamMockRecorder) Stop(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockStream)(nil).Stop), arg0, arg1)
+}
diff --git a/pkg/logs/client/stream.go b/pkg/logs/client/stream.go
index 46cbd89ab74..e8bccf966d6 100644
--- a/pkg/logs/client/stream.go
+++ b/pkg/logs/client/stream.go
@@ -15,32 +15,30 @@ import (
"github.com/kubeshop/testkube/pkg/utils"
)
-func NewNatsLogStream(nc *nats.Conn, id string) (Stream, error) {
+const ConsumerPrefix = "lc"
+
+func NewNatsLogStream(nc *nats.Conn) (s Stream, err error) {
js, err := jetstream.New(nc)
if err != nil {
- return &NatsLogStream{}, err
+ return s, err
}
return &NatsLogStream{
- nc: nc,
- js: js,
- log: log.DefaultLogger,
- id: id,
- streamName: StreamPrefix + id,
+ nc: nc,
+ js: js,
+ log: log.DefaultLogger,
}, nil
}
type NatsLogStream struct {
- nc *nats.Conn
- js jetstream.JetStream
- log *zap.SugaredLogger
- streamName string
- id string
+ nc *nats.Conn
+ js jetstream.JetStream
+ log *zap.SugaredLogger
}
-func (c NatsLogStream) Init(ctx context.Context) (StreamMetadata, error) {
+func (c NatsLogStream) Init(ctx context.Context, id string) (StreamMetadata, error) {
s, err := c.js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
- Name: c.streamName,
+ Name: c.streamName(id),
Storage: jetstream.FileStorage, // durable stream
})
@@ -48,42 +46,42 @@ func (c NatsLogStream) Init(ctx context.Context) (StreamMetadata, error) {
c.log.Debugw("stream upserted", "info", s.CachedInfo())
}
- return StreamMetadata{Name: c.streamName}, err
+ return StreamMetadata{Name: c.streamName(id)}, err
}
// Push log chunk to NATS stream
-func (c NatsLogStream) Push(ctx context.Context, chunk events.Log) error {
+func (c NatsLogStream) Push(ctx context.Context, id string, chunk events.Log) error {
b, err := json.Marshal(chunk)
if err != nil {
return err
}
- return c.PushBytes(ctx, b)
+ return c.PushBytes(ctx, id, b)
}
// Push log chunk to NATS stream
// TODO handle message repeat with backoff strategy on error
-func (c NatsLogStream) PushBytes(ctx context.Context, chunk []byte) error {
- _, err := c.js.Publish(ctx, c.streamName, chunk)
+func (c NatsLogStream) PushBytes(ctx context.Context, id string, chunk []byte) error {
+ _, err := c.js.Publish(ctx, c.streamName(id), chunk)
return err
}
// Start emits start event to the stream - logs service will handle start and create new stream
-func (c NatsLogStream) Start(ctx context.Context) (resp StreamResponse, err error) {
- return c.syncCall(ctx, StartSubject)
+func (c NatsLogStream) Start(ctx context.Context, id string) (resp StreamResponse, err error) {
+ return c.syncCall(ctx, StartSubject, id)
}
// Stop emits stop event to the stream and waits for given stream to be stopped fully - logs service will handle stop and close stream and all subscribers
-func (c NatsLogStream) Stop(ctx context.Context) (resp StreamResponse, err error) {
- return c.syncCall(ctx, StopSubject)
+func (c NatsLogStream) Stop(ctx context.Context, id string) (resp StreamResponse, err error) {
+ return c.syncCall(ctx, StopSubject, id)
}
// Get returns channel with log stream chunks for given execution id connects through GRPC to log service
-func (c NatsLogStream) Get(ctx context.Context) (chan events.LogResponse, error) {
+func (c NatsLogStream) Get(ctx context.Context, id string) (chan events.LogResponse, error) {
ch := make(chan events.LogResponse)
- name := fmt.Sprintf("lc%s%s", c.id, utils.RandAlphanum(6))
- cons, err := c.js.CreateOrUpdateConsumer(ctx, c.streamName, jetstream.ConsumerConfig{
+ name := fmt.Sprintf("%s%s%s", ConsumerPrefix, id, utils.RandAlphanum(6))
+ cons, err := c.js.CreateOrUpdateConsumer(ctx, c.streamName(id), jetstream.ConsumerConfig{
Name: name,
Durable: name,
DeliverPolicy: jetstream.DeliverAllPolicy,
@@ -93,7 +91,7 @@ func (c NatsLogStream) Get(ctx context.Context) (chan events.LogResponse, error)
return ch, err
}
- log := c.log.With("id", c.id)
+ log := c.log.With("id", id)
cons.Consume(func(msg jetstream.Msg) {
log.Debugw("got message", "data", string(msg.Data()))
@@ -123,8 +121,11 @@ func (c NatsLogStream) Get(ctx context.Context) (chan events.LogResponse, error)
}
// syncCall sends request to given subject and waits for response
-func (c NatsLogStream) syncCall(ctx context.Context, subject string) (resp StreamResponse, err error) {
- b, _ := json.Marshal(events.Trigger{Id: c.id})
+func (c NatsLogStream) syncCall(ctx context.Context, subject, id string) (resp StreamResponse, err error) {
+ b, err := json.Marshal(events.Trigger{Id: id})
+ if err != nil {
+ return resp, err
+ }
m, err := c.nc.Request(subject, b, time.Minute)
if err != nil {
return resp, err
@@ -132,3 +133,7 @@ func (c NatsLogStream) syncCall(ctx context.Context, subject string) (resp Strea
return StreamResponse{Message: m.Data}, nil
}
+
+func (c NatsLogStream) streamName(id string) string {
+ return StreamPrefix + id
+}
diff --git a/pkg/logs/client/stream_test.go b/pkg/logs/client/stream_test.go
index e5390da6b58..7d3ed1a8845 100644
--- a/pkg/logs/client/stream_test.go
+++ b/pkg/logs/client/stream_test.go
@@ -2,7 +2,6 @@ package client
import (
"context"
- "fmt"
"testing"
"github.com/nats-io/nats.go"
@@ -15,39 +14,39 @@ func TestStream_StartStop(t *testing.T) {
ns, nc := bus.TestServerWithConnection()
defer ns.Shutdown()
+ id := "111"
+
ctx := context.Background()
- client, err := NewNatsLogStream(nc, "111")
+ client, err := NewNatsLogStream(nc)
assert.NoError(t, err)
- meta, err := client.Init(ctx)
+ meta, err := client.Init(ctx, id)
assert.NoError(t, err)
- assert.Equal(t, StreamPrefix+"111", meta.Name)
+ assert.Equal(t, StreamPrefix+id, meta.Name)
- err = client.PushBytes(ctx, []byte(`{"content":"hello 1"}`))
+ err = client.PushBytes(ctx, id, []byte(`{"content":"hello 1"}`))
assert.NoError(t, err)
var startReceived, stopReceived bool
_, err = nc.Subscribe(StartSubject, func(m *nats.Msg) {
- fmt.Printf("%s\n", m.Data)
m.Respond([]byte("ok"))
startReceived = true
})
assert.NoError(t, err)
_, err = nc.Subscribe(StopSubject, func(m *nats.Msg) {
- fmt.Printf("%s\n", m.Data)
m.Respond([]byte("ok"))
stopReceived = true
})
assert.NoError(t, err)
- d, err := client.Start(ctx)
+ d, err := client.Start(ctx, id)
assert.NoError(t, err)
assert.Equal(t, "ok", string(d.Message))
- d, err = client.Stop(ctx)
+ d, err = client.Stop(ctx, id)
assert.NoError(t, err)
assert.Equal(t, "ok", string(d.Message))
diff --git a/pkg/logs/events/events.go b/pkg/logs/events/events.go
index f2548ba369c..032299405d2 100644
--- a/pkg/logs/events/events.go
+++ b/pkg/logs/events/events.go
@@ -39,13 +39,21 @@ type Log struct {
Error bool `json:"error,omitempty"`
Version LogVersion `json:"version,omitempty"`
- // Old output - for backwards compatibility - will be removed
+ // Old output - for backwards compatibility - will be removed for non-structured logs
V1 *LogOutputV1 `json:"v1,omitempty"`
}
type LogOutputV1 struct {
Result *testkube.ExecutionResult
}
+func NewLog(content string) *Log {
+ return &Log{
+ Time: time.Now(),
+ Content: string(content),
+ Metadata: map[string]string{},
+ }
+}
+
func NewLogResponse(ts time.Time, content []byte) Log {
return Log{
Time: ts,
@@ -54,23 +62,32 @@ func NewLogResponse(ts time.Time, content []byte) Log {
}
}
-// log line/chunk data
-func (c *Log) WithMetadataEntry(key, value string) *Log {
- if c.Metadata == nil {
- c.Metadata = map[string]string{}
+func (l *Log) WithMetadataEntry(key, value string) *Log {
+ if l.Metadata == nil {
+ l.Metadata = map[string]string{}
}
- c.Metadata[key] = value
- return c
+ l.Metadata[key] = value
+ return l
+}
+
+func (l *Log) WithType(t string) *Log {
+ l.Type = t
+ return l
+}
+
+func (l *Log) WithSource(s string) *Log {
+ l.Source = s
+ return l
}
-func (c *Log) WithVersion(version LogVersion) *Log {
- c.Version = version
- return c
+func (l *Log) WithVersion(version LogVersion) *Log {
+ l.Version = version
+ return l
}
-func (c *Log) WithV1Result(result *testkube.ExecutionResult) *Log {
- c.V1.Result = result
- return c
+func (l *Log) WithV1Result(result *testkube.ExecutionResult) *Log {
+ l.V1.Result = result
+ return l
}
var timestampRegexp = regexp.MustCompile("^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*")
diff --git a/pkg/logs/events_test.go b/pkg/logs/events_test.go
index 288aea12788..879dcb06c87 100644
--- a/pkg/logs/events_test.go
+++ b/pkg/logs/events_test.go
@@ -62,23 +62,23 @@ func TestLogs_EventsFlow(t *testing.T) {
<-log.Ready
// and logs stream client
- stream, err := client.NewNatsLogStream(nc, "stop-test")
+ stream, err := client.NewNatsLogStream(nc)
assert.NoError(t, err)
// and initialized log stream for given ID
- meta, err := stream.Init(ctx)
+ meta, err := stream.Init(ctx, "stop-test")
assert.NotEmpty(t, meta.Name)
assert.NoError(t, err)
// when start event triggered
- _, err = stream.Start(ctx)
+ _, err = stream.Start(ctx, "stop-test")
assert.NoError(t, err)
// and when data pushed to the log stream
- stream.Push(ctx, events.NewLogResponse(time.Now(), []byte("hello 1")))
+ stream.Push(ctx, "stop-test", events.NewLogResponse(time.Now(), []byte("hello 1")))
// and stop event triggered
- _, err = stream.Stop(ctx)
+ _, err = stream.Stop(ctx, "stop-test")
assert.NoError(t, err)
// then all adapters should be gracefully stopped
@@ -130,26 +130,26 @@ func TestLogs_EventsFlow(t *testing.T) {
<-log.Ready
// and stream client
- stream, err := client.NewNatsLogStream(nc, "messages-test")
+ stream, err := client.NewNatsLogStream(nc)
assert.NoError(t, err)
// and initialized log stream for given ID
- meta, err := stream.Init(ctx)
+ meta, err := stream.Init(ctx, "messages-test")
assert.NotEmpty(t, meta.Name)
assert.NoError(t, err)
// when start event triggered
- _, err = stream.Start(ctx)
+ _, err = stream.Start(ctx, "messages-test")
assert.NoError(t, err)
for i := 0; i < messagesCount; i++ {
// and when data pushed to the log stream
- err = stream.Push(ctx, events.NewLogResponse(time.Now(), []byte("hello")))
+ err = stream.Push(ctx, "messages-test", events.NewLogResponse(time.Now(), []byte("hello")))
assert.NoError(t, err)
}
// and wait for message to be propagated
- _, err = stream.Stop(ctx)
+ _, err = stream.Stop(ctx, "messages-test")
assert.NoError(t, err)
assertMessagesCount(t, a, 4*messagesCount)
@@ -198,16 +198,16 @@ func TestLogs_EventsFlow(t *testing.T) {
<-log.Ready
// and logs stream client
- stream, err := client.NewNatsLogStream(nc, "stop-test")
+ stream, err := client.NewNatsLogStream(nc)
assert.NoError(t, err)
// and initialized log stream for given ID
- meta, err := stream.Init(ctx)
+ meta, err := stream.Init(ctx, "consumer-stats")
assert.NotEmpty(t, meta.Name)
assert.NoError(t, err)
// when start event triggered
- _, err = stream.Start(ctx)
+ _, err = stream.Start(ctx, "consumer-stats")
assert.NoError(t, err)
// then we should have 2 consumers
@@ -215,7 +215,7 @@ func TestLogs_EventsFlow(t *testing.T) {
assert.Equal(t, 2, stats.Count)
// when stop event triggered
- _, err = stream.Stop(ctx)
+ _, err = stream.Stop(ctx, "consumer-stats")
assert.NoError(t, err)
// then all adapters should be gracefully stopped
diff --git a/pkg/logs/sidecar/proxy.go b/pkg/logs/sidecar/proxy.go
index ad99a2388c9..d7c9aad161d 100644
--- a/pkg/logs/sidecar/proxy.go
+++ b/pkg/logs/sidecar/proxy.go
@@ -38,7 +38,7 @@ const (
func NewProxy(clientset kubernetes.Interface, podsClient tcorev1.PodInterface, logsStream client.Stream, js jetstream.JetStream, log *zap.SugaredLogger, namespace, executionId string) *Proxy {
return &Proxy{
- log: log.With("namespace", namespace, "executionId", executionId),
+ log: log.With("service", "logs-proxy", "namespace", namespace, "executionId", executionId),
js: js,
clientset: clientset,
namespace: namespace,
@@ -55,7 +55,7 @@ type Proxy struct {
namespace string
executionId string
podsClient tcorev1.PodInterface
- logsStream client.Stream
+ logsStream client.InitializedStreamPusher
}
func (p *Proxy) Run(ctx context.Context) error {
@@ -66,8 +66,7 @@ func (p *Proxy) Run(ctx context.Context) error {
logs := make(chan events.Log, logsBuffer)
// create stream for incoming logs
-
- _, err := p.logsStream.Init(ctx)
+ _, err := p.logsStream.Init(ctx, p.executionId)
if err != nil {
return err
}
@@ -76,7 +75,7 @@ func (p *Proxy) Run(ctx context.Context) error {
p.log.Debugw("logs proxy stream started")
err := p.streamLogs(ctx, logs)
if err != nil {
- p.handleError(err, "proxy stream logs error")
+ p.handleError(err, "logs proxy stream error")
}
}()
@@ -89,7 +88,7 @@ func (p *Proxy) Run(ctx context.Context) error {
p.log.Warn("logs proxy context cancelled, exiting")
return nil
default:
- err = p.logsStream.Push(ctx, l)
+ err = p.logsStream.Push(ctx, p.executionId, l)
if err != nil {
p.handleError(err, "error pushing logs to stream")
return err
@@ -248,7 +247,7 @@ func (p *Proxy) handleError(err error, title string) {
p.log.Errorw(title, "error", err)
if err == nil {
- p.logsStream.Push(context.Background(), ch)
+ p.logsStream.Push(context.Background(), p.executionId, ch)
} else {
p.log.Errorw("error pushing error to stream", "title", title, "error", err)
}
diff --git a/pkg/scheduler/service.go b/pkg/scheduler/service.go
index 6e1a6656ed6..349c1d8c688 100644
--- a/pkg/scheduler/service.go
+++ b/pkg/scheduler/service.go
@@ -16,6 +16,7 @@ import (
"github.com/kubeshop/testkube/pkg/configmap"
"github.com/kubeshop/testkube/pkg/event"
"github.com/kubeshop/testkube/pkg/executor/client"
+ logsclient "github.com/kubeshop/testkube/pkg/logs/client"
"github.com/kubeshop/testkube/pkg/repository/result"
"github.com/kubeshop/testkube/pkg/repository/testresult"
"github.com/kubeshop/testkube/pkg/secret"
@@ -25,8 +26,8 @@ type Scheduler struct {
metrics v1.Metrics
executor client.Executor
containerExecutor client.Executor
- executionResults result.Repository
- testExecutionResults testresult.Repository
+ testResults result.Repository
+ testsuiteResults testresult.Repository
executorsClient executorsv1.Interface
testsClient testsv3.Interface
testSuitesClient testsuitesv3.Interface
@@ -40,6 +41,7 @@ type Scheduler struct {
eventsBus bus.Bus
dashboardURI string
featureFlags featureflags.FeatureFlags
+ logsStream logsclient.InitializedStreamPusher
}
func NewScheduler(
@@ -61,14 +63,15 @@ func NewScheduler(
eventsBus bus.Bus,
dashboardURI string,
featureFlags featureflags.FeatureFlags,
+ logsStream logsclient.InitializedStreamPusher,
) *Scheduler {
return &Scheduler{
metrics: metrics,
executor: executor,
containerExecutor: containerExecutor,
secretClient: secretClient,
- executionResults: executionResults,
- testExecutionResults: testExecutionResults,
+ testResults: executionResults,
+ testsuiteResults: testExecutionResults,
executorsClient: executorsClient,
testsClient: testsClient,
testSuitesClient: testSuitesClient,
@@ -81,5 +84,6 @@ func NewScheduler(
eventsBus: eventsBus,
dashboardURI: dashboardURI,
featureFlags: featureFlags,
+ logsStream: logsStream,
}
}
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index 4bfbcd97b92..11aaa82bbf8 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -14,6 +14,7 @@ import (
"github.com/kubeshop/testkube/pkg/api/v1/testkube"
"github.com/kubeshop/testkube/pkg/executor"
"github.com/kubeshop/testkube/pkg/executor/client"
+ "github.com/kubeshop/testkube/pkg/logs/events"
testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests"
"github.com/kubeshop/testkube/pkg/workerpool"
)
@@ -53,22 +54,25 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
request.Name = fmt.Sprintf("%s-%d", request.Name, request.Number)
}
+ s.events.Notify(testkube.NewEventStartTest(&execution))
+
// test name + test execution name should be unique
- execution, _ = s.executionResults.GetByNameAndTest(ctx, request.Name, test.Name)
+ execution, _ = s.testResults.GetByNameAndTest(ctx, request.Name, test.Name)
if execution.Name == request.Name {
- return execution.Err(errors.Errorf("test execution with name %s already exists", request.Name)), nil
+ err := errors.Errorf("test execution with name %s already exists", request.Name)
+ return s.handleExecutionError(ctx, execution, "duplicate execution: %w", err)
}
secretUUID, err := s.testsClient.GetCurrentSecretUUID(test.Name)
if err != nil {
- return execution.Errw(request.Id, "can't get current secret uuid: %w", err), nil
+ return s.handleExecutionError(ctx, execution, "can't get current secret uuid: %w", err)
}
request.TestSecretUUID = secretUUID
// merge available data into execution options test spec, executor spec, request, test id
options, err := s.getExecuteOptions(test.Namespace, test.Name, request)
if err != nil {
- return execution.Errw(request.Id, "can't create valid execution options: %w", err), nil
+ return s.handleExecutionError(ctx, execution, "can't get current secret uuid: %w", err)
}
// store execution in storage, can be fetched from API now
@@ -76,25 +80,22 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
options.ID = execution.Id
if err := s.createSecretsReferences(&execution); err != nil {
- return execution.Errw(execution.Id, "can't create secret variables `Secret` references: %w", err), nil
+ return s.handleExecutionError(ctx, execution, "can't create secret variables `Secret` references: %w", err)
}
- err = s.executionResults.Insert(ctx, execution)
+ err = s.testResults.Insert(ctx, execution)
if err != nil {
- return execution.Errw(execution.Id, "can't create new test execution, can't insert into storage: %w", err), nil
+ return s.handleExecutionError(ctx, execution, "can't create new test execution, can't insert into storage: %w", err)
}
s.logger.Infow("calling executor with options", "options", options.Request)
execution.Start()
- s.events.Notify(testkube.NewEventStartTest(&execution))
-
// update storage with current execution status
- err = s.executionResults.StartExecution(ctx, execution.Id, execution.StartTime)
+ err = s.testResults.StartExecution(ctx, execution.Id, execution.StartTime)
if err != nil {
- s.events.Notify(testkube.NewEventEndTestFailed(&execution))
- return execution.Errw(execution.Id, "can't execute test, can't insert into storage error: %w", err), nil
+ return s.handleExecutionError(ctx, execution, "can't execute test, can't insert into storage error: %w", err)
}
// sync/async test execution
@@ -104,14 +105,12 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
execution.ExecutionResult = result
// update storage with current execution status
- if uerr := s.executionResults.UpdateResult(ctx, execution.Id, execution); uerr != nil {
- s.events.Notify(testkube.NewEventEndTestFailed(&execution))
- return execution.Errw(execution.Id, "update execution error: %w", uerr), nil
+ if uerr := s.testResults.UpdateResult(ctx, execution.Id, execution); uerr != nil {
+ return s.handleExecutionError(ctx, execution, "update execution error: %w", err)
}
if err != nil {
- s.events.Notify(testkube.NewEventEndTestFailed(&execution))
- return execution.Errw(execution.Id, "test execution failed: %w", err), nil
+ return s.handleExecutionError(ctx, execution, "test execution failed: %w", err)
}
s.logger.Infow("test started", "executionId", execution.Id, "status", execution.ExecutionResult.Status)
@@ -119,6 +118,23 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
return execution, nil
}
+func (s *Scheduler) handleExecutionError(ctx context.Context, execution testkube.Execution, msgTpl string, err error) (testkube.Execution, error) {
+ // push error log to the log stream if logs v2 enabled
+ if s.featureFlags.LogsV2 {
+ l := events.NewLog(fmt.Sprintf(msgTpl, err)).
+ WithType("error").
+ WithVersion(events.LogVersionV2).
+ WithSource("test-scheduler")
+
+ s.logsStream.Push(ctx, execution.Id, *l)
+ }
+
+ // notify events that execution failed
+ s.events.Notify(testkube.NewEventEndTestFailed(&execution))
+
+ return execution.Errw(execution.Id, msgTpl, err), nil
+}
+
func (s *Scheduler) startTestExecution(ctx context.Context, options client.ExecuteOptions, execution *testkube.Execution) (result *testkube.ExecutionResult, err error) {
executor := s.getExecutor(options.TestName)
return executor.Execute(ctx, execution, options)
@@ -146,7 +162,7 @@ func (s *Scheduler) getExecutor(testName string) client.Executor {
}
func (s *Scheduler) getNextExecutionNumber(testName string) int32 {
- number, err := s.executionResults.GetNextExecutionNumber(context.Background(), testName)
+ number, err := s.testResults.GetNextExecutionNumber(context.Background(), testName)
if err != nil {
s.logger.Errorw("retrieving latest execution", "error", err)
return number
diff --git a/pkg/scheduler/testsuite_scheduler.go b/pkg/scheduler/testsuite_scheduler.go
index ba564919051..d8aa33189ea 100644
--- a/pkg/scheduler/testsuite_scheduler.go
+++ b/pkg/scheduler/testsuite_scheduler.go
@@ -117,7 +117,7 @@ func (s *Scheduler) executeTestSuite(ctx context.Context, testSuite testkube.Tes
}
testsuiteExecution = testkube.NewStartedTestSuiteExecution(testSuite, request)
- err = s.testExecutionResults.Insert(ctx, testsuiteExecution)
+ err = s.testsuiteResults.Insert(ctx, testsuiteExecution)
if err != nil {
s.logger.Infow("Inserting test execution", "error", err)
}
@@ -208,7 +208,7 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE
}
}
- err := s.testExecutionResults.Update(ctx, *testsuiteExecution)
+ err := s.testsuiteResults.Update(ctx, *testsuiteExecution)
if err != nil {
s.logger.Infow("Updating test execution", "error", err)
}
@@ -224,7 +224,7 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE
s.logger.Debugw("Batch step execution result", "step", batchStepResult.Execute, "results", results)
- err = s.testExecutionResults.Update(ctx, *testsuiteExecution)
+ err = s.testsuiteResults.Update(ctx, *testsuiteExecution)
if err != nil {
s.logger.Errorw("saving test suite execution results error", "error", err)
@@ -262,7 +262,7 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE
s.metrics.IncExecuteTestSuite(*testsuiteExecution, s.dashboardURI)
- err = s.testExecutionResults.Update(ctx, *testsuiteExecution)
+ err = s.testsuiteResults.Update(ctx, *testsuiteExecution)
if err != nil {
s.logger.Errorw("saving final test suite execution result error", "error", err)
}
@@ -272,7 +272,7 @@ func (s *Scheduler) runSteps(ctx context.Context, wg *sync.WaitGroup, testsuiteE
func (s *Scheduler) runAfterEachStep(ctx context.Context, execution *testkube.TestSuiteExecution, wg *sync.WaitGroup) {
execution.Stop()
- err := s.testExecutionResults.EndExecution(ctx, *execution)
+ err := s.testsuiteResults.EndExecution(ctx, *execution)
if err != nil {
s.logger.Errorw("error setting end time", "error", err.Error())
}
@@ -518,7 +518,7 @@ func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution test
}
result.Start()
- if err := s.testExecutionResults.Update(ctx, testsuiteExecution); err != nil {
+ if err := s.testsuiteResults.Update(ctx, testsuiteExecution); err != nil {
s.logger.Errorw("saving test suite execution start time error", "error", err)
}
@@ -543,7 +543,7 @@ func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution test
if result.Execute[i].Execution.Id == r.Result.Id {
result.Execute[i].Execution = &value
- if err := s.testExecutionResults.Update(ctx, testsuiteExecution); err != nil {
+ if err := s.testsuiteResults.Update(ctx, testsuiteExecution); err != nil {
s.logger.Errorw("saving test suite execution results error", "error", err)
}
}
@@ -552,7 +552,7 @@ func (s *Scheduler) executeTestStep(ctx context.Context, testsuiteExecution test
}
result.Stop()
- if err := s.testExecutionResults.Update(ctx, testsuiteExecution); err != nil {
+ if err := s.testsuiteResults.Update(ctx, testsuiteExecution); err != nil {
s.logger.Errorw("saving test suite execution end time error", "error", err)
}
}
diff --git a/pkg/triggers/executor_test.go b/pkg/triggers/executor_test.go
index 371981fdcd7..dce098cbd69 100644
--- a/pkg/triggers/executor_test.go
+++ b/pkg/triggers/executor_test.go
@@ -24,6 +24,7 @@ import (
"github.com/kubeshop/testkube/pkg/event/bus"
"github.com/kubeshop/testkube/pkg/executor/client"
"github.com/kubeshop/testkube/pkg/log"
+ logsclient "github.com/kubeshop/testkube/pkg/logs/client"
"github.com/kubeshop/testkube/pkg/repository/config"
"github.com/kubeshop/testkube/pkg/repository/result"
"github.com/kubeshop/testkube/pkg/repository/testresult"
@@ -103,6 +104,8 @@ func TestExecute(t *testing.T) {
mockExecutor.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any()).Return(&mockExecutionResult, nil)
mockResultRepository.EXPECT().UpdateResult(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
+ mockLogsStream := logsclient.NewMockInitializedStreamPusher(mockCtrl)
+
sched := scheduler.NewScheduler(
metricsHandle,
mockExecutor,
@@ -122,6 +125,7 @@ func TestExecute(t *testing.T) {
mockBus,
"",
featureflags.FeatureFlags{},
+ mockLogsStream,
)
s := &Service{
triggerStatus: make(map[statusKey]*triggerStatus),
diff --git a/pkg/triggers/service_test.go b/pkg/triggers/service_test.go
index e2cf5a43e10..7c847f32e4d 100644
--- a/pkg/triggers/service_test.go
+++ b/pkg/triggers/service_test.go
@@ -14,7 +14,6 @@ import (
executorv1 "github.com/kubeshop/testkube-operator/api/executor/v1"
testsv3 "github.com/kubeshop/testkube-operator/api/tests/v3"
testtriggersv1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1"
- v1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1"
executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1"
testsclientv3 "github.com/kubeshop/testkube-operator/pkg/client/tests/v3"
testsourcesv1 "github.com/kubeshop/testkube-operator/pkg/client/testsources/v1"
@@ -29,6 +28,7 @@ import (
"github.com/kubeshop/testkube/pkg/event/bus"
"github.com/kubeshop/testkube/pkg/executor/client"
"github.com/kubeshop/testkube/pkg/log"
+ logsclient "github.com/kubeshop/testkube/pkg/logs/client"
"github.com/kubeshop/testkube/pkg/repository/config"
"github.com/kubeshop/testkube/pkg/repository/result"
"github.com/kubeshop/testkube/pkg/repository/testresult"
@@ -117,6 +117,8 @@ func TestService_Run(t *testing.T) {
testLogger := log.DefaultLogger
+ mockLogsStream := logsclient.NewMockInitializedStreamPusher(mockCtrl)
+
sched := scheduler.NewScheduler(
testMetrics,
mockExecutor,
@@ -136,6 +138,7 @@ func TestService_Run(t *testing.T) {
mockBus,
"",
featureflags.FeatureFlags{},
+ mockLogsStream,
)
mockLeaseBackend := NewMockLeaseBackend(mockCtrl)
@@ -207,7 +210,7 @@ func TestService_addTrigger(t *testing.T) {
s := Service{triggerStatus: make(map[statusKey]*triggerStatus)}
- testTrigger := v1.TestTrigger{
+ testTrigger := testtriggersv1.TestTrigger{
ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-1", Namespace: "testkube"},
}
s.addTrigger(&testTrigger)
@@ -222,10 +225,10 @@ func TestService_removeTrigger(t *testing.T) {
s := Service{triggerStatus: make(map[statusKey]*triggerStatus)}
- testTrigger1 := v1.TestTrigger{
+ testTrigger1 := testtriggersv1.TestTrigger{
ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-1", Namespace: "testkube"},
}
- testTrigger2 := v1.TestTrigger{
+ testTrigger2 := testtriggersv1.TestTrigger{
ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-2", Namespace: "testkube"},
}
s.addTrigger(&testTrigger1)
@@ -247,15 +250,15 @@ func TestService_updateTrigger(t *testing.T) {
s := Service{triggerStatus: make(map[statusKey]*triggerStatus)}
- oldTestTrigger := v1.TestTrigger{
+ oldTestTrigger := testtriggersv1.TestTrigger{
ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
- Spec: v1.TestTriggerSpec{Event: "created"},
+ Spec: testtriggersv1.TestTriggerSpec{Event: "created"},
}
s.addTrigger(&oldTestTrigger)
- newTestTrigger := v1.TestTrigger{
+ newTestTrigger := testtriggersv1.TestTrigger{
ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
- Spec: v1.TestTriggerSpec{Event: "modified"},
+ Spec: testtriggersv1.TestTriggerSpec{Event: "modified"},
}
s.updateTrigger(&newTestTrigger)
diff --git a/pkg/triggers/watcher.go b/pkg/triggers/watcher.go
index 7413671c264..cb85ee7b37b 100644
--- a/pkg/triggers/watcher.go
+++ b/pkg/triggers/watcher.go
@@ -9,7 +9,6 @@ import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
appsinformerv1 "k8s.io/client-go/informers/apps/v1"
coreinformerv1 "k8s.io/client-go/informers/core/v1"
@@ -57,7 +56,7 @@ func newK8sInformers(clientset kubernetes.Interface, testKubeClientset versioned
testkubeNamespace string, watcherNamespaces []string) *k8sInformers {
var k8sInformers k8sInformers
if len(watcherNamespaces) == 0 {
- watcherNamespaces = append(watcherNamespaces, v1.NamespaceAll)
+ watcherNamespaces = append(watcherNamespaces, metav1.NamespaceAll)
}
for _, namespace := range watcherNamespaces {
From 5e416091cb34bb1c86b05811fe8265ce63e67bce Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Tue, 16 Jan 2024 20:47:13 +0300
Subject: [PATCH 005/234] fix: remove duplicates
---
contrib/executor/jmeterd/pkg/runner/runner.go | 34 +++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner.go b/contrib/executor/jmeterd/pkg/runner/runner.go
index ea0c96d4004..1472e89ce44 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner.go
@@ -132,7 +132,7 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
reportPath := filepath.Join(outputDir, "report")
jmeterLogPath := filepath.Join(outputDir, "jmeter.log")
args := execution.Args
- hasJunit, hasReport := replacePlaceholderArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
+ hasJunit, hasReport := prepareArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
if mode == jmeterModeDistributed {
clientSet, err := k8sclient.ConnectToK8s()
@@ -227,7 +227,37 @@ func initSlaves(
}
-func replacePlaceholderArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool) {
+func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool) {
+ duplicates := make(map[string]int)
+ removals := make(map[string]string)
+ for _, arg := range args {
+ duplicates[arg] += 1
+ if duplicates[arg] > 1 {
+ switch arg {
+ case "-t":
+ removals[""] = arg
+ case "-l":
+ removals[""] = arg
+ case "-o":
+ removals[""] = arg
+ case "-j":
+ removals[""] = arg
+ }
+ }
+ }
+
+ for i := len(args) - 1; i >= 0; i-- {
+ if arg, ok := removals[args[i]]; ok {
+ args = append(args[:i], args[i+1:]...)
+ if i > 0 {
+ i--
+ if args[i] == arg {
+ args = append(args[:i], args[i+1:]...)
+ }
+ }
+ }
+ }
+
for i, arg := range args {
switch arg {
case "":
From ea557920c610fd90db0842bcfec5937ed6405772 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Tue, 16 Jan 2024 20:55:25 +0300
Subject: [PATCH 006/234] fix: unit tests
---
contrib/executor/jmeterd/pkg/runner/runner_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index ee75d8d0f30..e9aa1b92604 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -57,7 +57,7 @@ func TestReplaceArgs(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- hasJunit, hasReport := replacePlaceholderArgs(tt.args, tt.path, tt.jtlPath, tt.reportPath, tt.jmeterLogPath)
+ hasJunit, hasReport := prepareArgs(tt.args, tt.path, tt.jtlPath, tt.reportPath, tt.jmeterLogPath)
for i, arg := range tt.args {
assert.Equal(t, tt.expectedArgs[i], arg)
From 2d1c7d703d89254d5e29b1148d40a1f08c45c89c Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Tue, 16 Jan 2024 21:03:13 +0300
Subject: [PATCH 007/234] fix: add OUTPUT_DIR env var
---
contrib/executor/jmeterd/pkg/runner/runner.go | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner.go b/contrib/executor/jmeterd/pkg/runner/runner.go
index 1472e89ce44..a9ae104b235 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner.go
@@ -122,7 +122,14 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
if workingDir != "" {
runPath = workingDir
}
+
outputDir := filepath.Join(runPath, "output")
+ err = os.Setenv("OUTPUT_DIR", outputDir)
+ if err != nil {
+ output.PrintLogf("%s Failed to set output directory %s", ui.IconWarning, outputDir)
+ }
+ slavesEnvVariables["OUTPUT_DIR"] = testkube.NewBasicVariable("OUTPUT_DIR", outputDir)
+
// recreate output directory with wide permissions so JMeter can create report files
if err = os.Mkdir(outputDir, 0777); err != nil {
return *result.Err(errors.Wrapf(err, "error creating directory %s", outputDir)), nil
From 64ffb5f3c9a31ae5379a2f85534db65ff2100ada Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Tue, 16 Jan 2024 23:14:03 +0300
Subject: [PATCH 008/234] fix: add unit tests
---
contrib/executor/jmeterd/pkg/runner/runner.go | 24 +++++-----
.../jmeterd/pkg/runner/runner_test.go | 48 +++++++++++++++++--
2 files changed, 57 insertions(+), 15 deletions(-)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner.go b/contrib/executor/jmeterd/pkg/runner/runner.go
index a9ae104b235..b1dfa26438a 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner.go
@@ -139,7 +139,7 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
reportPath := filepath.Join(outputDir, "report")
jmeterLogPath := filepath.Join(outputDir, "jmeter.log")
args := execution.Args
- hasJunit, hasReport := prepareArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
+ hasJunit, hasReport, args := prepareArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
if mode == jmeterModeDistributed {
clientSet, err := k8sclient.ConnectToK8s()
@@ -234,27 +234,27 @@ func initSlaves(
}
-func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool) {
- duplicates := make(map[string]int)
- removals := make(map[string]string)
+func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool, result []string) {
+ counters := make(map[string]int)
+ duplicates := make(map[string]string)
for _, arg := range args {
- duplicates[arg] += 1
- if duplicates[arg] > 1 {
+ counters[arg] += 1
+ if counters[arg] > 1 {
switch arg {
case "-t":
- removals[""] = arg
+ duplicates[""] = arg
case "-l":
- removals[""] = arg
+ duplicates[""] = arg
case "-o":
- removals[""] = arg
+ duplicates[""] = arg
case "-j":
- removals[""] = arg
+ duplicates[""] = arg
}
}
}
for i := len(args) - 1; i >= 0; i-- {
- if arg, ok := removals[args[i]]; ok {
+ if arg, ok := duplicates[args[i]]; ok {
args = append(args[:i], args[i+1:]...)
if i > 0 {
i--
@@ -280,7 +280,7 @@ func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string)
hasJunit = true
}
}
- return
+ return hasJunit, hasReport, args
}
func getEntryPoint() (entrypoint string) {
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index e9aa1b92604..6575ab9a5a3 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -14,7 +14,7 @@ import (
"github.com/kubeshop/testkube/pkg/envs"
)
-func TestReplaceArgs(t *testing.T) {
+func TestPrepareArgsReplacements(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -57,9 +57,51 @@ func TestReplaceArgs(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- hasJunit, hasReport := prepareArgs(tt.args, tt.path, tt.jtlPath, tt.reportPath, tt.jmeterLogPath)
+ hasJunit, hasReport, args := prepareArgs(tt.args, tt.path, tt.jtlPath, tt.reportPath, tt.jmeterLogPath)
- for i, arg := range tt.args {
+ for i, arg := range args {
+ assert.Equal(t, tt.expectedArgs[i], arg)
+ }
+ assert.Equal(t, tt.expectedJunit, hasJunit)
+ assert.Equal(t, tt.expectedReport, hasReport)
+ })
+ }
+}
+
+func TestPrepareArgsDuplication(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ args []string
+ expectedArgs []string
+ expectedJunit bool
+ expectedReport bool
+ }{
+ {
+ name: "Duplicated args",
+ args: []string{"-t", "", "-t", "path", "-l"},
+ expectedArgs: []string{"-t", "path", "-l"},
+ expectedJunit: true,
+ expectedReport: false,
+ },
+ {
+ name: "Non duplicated args",
+ args: []string{"-t", "path", "-l"},
+ expectedArgs: []string{"-t", "path", "-l"},
+ expectedJunit: true,
+ expectedReport: false,
+ },
+ }
+
+ for i := range tests {
+ tt := tests[i]
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ hasJunit, hasReport, args := prepareArgs(tt.args, "", "", "", "")
+
+ for i, arg := range args {
assert.Equal(t, tt.expectedArgs[i], arg)
}
assert.Equal(t, tt.expectedJunit, hasJunit)
From 1e90ad7d03eaa8229c57df096e20e3a16dd5bc18 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Wed, 17 Jan 2024 13:35:22 +0300
Subject: [PATCH 009/234] fix: add more test cases
---
.../jmeterd/pkg/runner/runner_test.go | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index 6575ab9a5a3..b9195ecbc98 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -85,6 +85,13 @@ func TestPrepareArgsDuplication(t *testing.T) {
expectedJunit: true,
expectedReport: false,
},
+ {
+ name: "Multiple duplicated args",
+ args: []string{"-t", "", "-o", "", "-t", "path", "-o", "output", "-l"},
+ expectedArgs: []string{"-t", "path", "-o", "output", "-l"},
+ expectedJunit: true,
+ expectedReport: false,
+ },
{
name: "Non duplicated args",
args: []string{"-t", "path", "-l"},
@@ -92,6 +99,27 @@ func TestPrepareArgsDuplication(t *testing.T) {
expectedJunit: true,
expectedReport: false,
},
+ {
+ name: "Wrong arg order",
+ args: []string{"", "-t", "-t", "path", "-l"},
+ expectedArgs: []string{"-t", "-t", "path", "-l"},
+ expectedJunit: true,
+ expectedReport: false,
+ },
+ {
+ name: "Missed template arg",
+ args: []string{"-t", "-t", "path", "-l"},
+ expectedArgs: []string{"-t", "-t", "path", "-l"},
+ expectedJunit: true,
+ expectedReport: false,
+ },
+ {
+ name: "Wrong arg before template",
+ args: []string{"-d", "-o", "", "-t", "-t", "path", "-l"},
+ expectedArgs: []string{"-d", "-o", "-t", "-t", "path", "-l"},
+ expectedJunit: true,
+ expectedReport: false,
+ },
}
for i := range tests {
From 87655eb3958c750d23f5275a82cb9e100555ff48 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Wed, 17 Jan 2024 13:38:57 +0300
Subject: [PATCH 010/234] fix: one more test case
---
contrib/executor/jmeterd/pkg/runner/runner_test.go | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index b9195ecbc98..dd3755dd61b 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -120,6 +120,13 @@ func TestPrepareArgsDuplication(t *testing.T) {
expectedJunit: true,
expectedReport: false,
},
+ {
+ name: "Duplicated not template args",
+ args: []string{"-t", "first", "-t", "second", "-l"},
+ expectedArgs: []string{"-t", "first", "-t", "second", "-l"},
+ expectedJunit: true,
+ expectedReport: false,
+ },
}
for i := range tests {
From 110290b2b8155649ffb4a5da2f40338b6c10a43c Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Wed, 17 Jan 2024 12:42:10 +0100
Subject: [PATCH 011/234] fix: slack token must be set to initialize Slack
Listener (#4902)
---
pkg/slack/slack.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/slack/slack.go b/pkg/slack/slack.go
index b703ceaae0b..c6ab0f2734d 100644
--- a/pkg/slack/slack.go
+++ b/pkg/slack/slack.go
@@ -47,7 +47,7 @@ func NewNotifier(template, clusterName, dashboardURI string, config []Notificati
notifier := Notifier{messageTemplate: template, clusterName: clusterName, dashboardURI: dashboardURI,
config: NewConfig(config), envs: envs}
notifier.timestamps = make(map[string]string)
- if token, ok := os.LookupEnv("SLACK_TOKEN"); ok {
+ if token, ok := os.LookupEnv("SLACK_TOKEN"); ok && token != "" {
log.DefaultLogger.Infow("initializing slack client", "SLACK_TOKEN", text.Obfuscate(token))
notifier.client = slack.New(token, slack.OptionDebug(true))
notifier.Ready = true
From d39a8aef917b68ea8e6097674081c34cad125ffd Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 17 Jan 2024 17:20:22 +0200
Subject: [PATCH 012/234] test cli image
---
.builds-linux.goreleaser.yml | 10 ----------
.github/workflows/release-dev.yaml | 19 ++++++++++++++++---
.github/workflows/release.yaml | 26 ++++++++++++++++----------
3 files changed, 32 insertions(+), 23 deletions(-)
diff --git a/.builds-linux.goreleaser.yml b/.builds-linux.goreleaser.yml
index 512281a9b20..f733a63b880 100644
--- a/.builds-linux.goreleaser.yml
+++ b/.builds-linux.goreleaser.yml
@@ -70,16 +70,6 @@ dockers:
- "--cache-from={{ .Env.DOCKER_BUILDX_CACHE_FROM }}"
- "--build-arg=ALPINE_IMAGE={{ .Env.ALPINE_IMAGE }}"
-docker_manifests:
- - name_template: kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}
- image_templates:
- - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-amd64
- - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8
- - name_template: kubeshop/testkube-cli:latest
- image_templates:
- - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-amd64
- - kubeshop/testkube-cli:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8
-
docker_signs:
- cmd: cosign
artifacts: all
diff --git a/.github/workflows/release-dev.yaml b/.github/workflows/release-dev.yaml
index 656fec54f57..be9e463ce71 100644
--- a/.github/workflows/release-dev.yaml
+++ b/.github/workflows/release-dev.yaml
@@ -66,6 +66,12 @@ jobs:
id: github_sha
run: echo "::set-output name=sha_short::${GITHUB_SHA::7}"
+ - name: Get tag
+ id: tag
+ uses: dawidd6/action-get-tag@v1
+ with:
+ strip_v: true
+
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
@@ -82,13 +88,20 @@ jobs:
DOCKER_BUILDX_CACHE_FROM: "type=gha"
DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max"
ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }}
- DOCKER_IMAGE_TAG: ${{ steps.github_sha.outputs.sha_short }}
+ DOCKER_IMAGE_TAG: ${{steps.tag.outputs.tag}}
- name: Push Docker images
if: matrix.name == 'linux'
run: |
- docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-amd64
+ docker push kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-arm64v8
+ docker push kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-amd64
+
+ # adding the docker manifest for the latest image tag
+ docker manifest create kubeshop/testkube-cli:latest --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-amd64 --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-arm64v8
+ docker manifest push -p kubeshop/testkube-cli:latest
+
+ docker manifest create kubeshop/testkube-cli:${{steps.tag.outputs.tag}} --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-amd64 --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-arm64v8
+ docker manifest push -p kubeshop/testkube-cli:${{steps.tag.outputs.tag}}
- name: Push README to Dockerhub
if: matrix.name == 'linux'
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index f0e0cd5d7d5..3ecb2129c75 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -67,8 +67,14 @@ jobs:
id: github_sha
run: echo "::set-output name=sha_short::${GITHUB_SHA::7}"
+ - name: Get tag
+ id: tag
+ uses: dawidd6/action-get-tag@v1
+ with:
+ strip_v: true
+
- name: Run GoReleaser
- uses: goreleaser/goreleaser-action@v2
+ uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser-pro
version: latest
@@ -83,20 +89,20 @@ jobs:
DOCKER_BUILDX_CACHE_FROM: "type=gha"
DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max"
ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }}
- DOCKER_IMAGE_TAG: ${{ steps.github_sha.outputs.sha_short }}
+ DOCKER_IMAGE_TAG: ${{steps.tag.outputs.tag}}
- name: Push Docker images
if: matrix.name == 'linux'
run: |
- docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker push kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-amd64
+ docker push kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-arm64v8
+ docker push kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-amd64
+
# adding the docker manifest for the latest image tag
- docker manifest create kubeshop/testkube-cli:latest \
- kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-amd64 \
- kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker manifest annotate kubeshop/testkube-cli:latest kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-amd64 --arch amd64
- docker manifest annotate kubeshop/testkube-cli:latest kubeshop/testkube-cli:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --arch arm64 --variant v8
- docker manifest push kubeshop/testkube-cli:latest
+ docker manifest create kubeshop/testkube-cli:latest --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-amd64 --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-arm64v8
+ docker manifest push -p kubeshop/testkube-cli:latest
+
+ docker manifest create kubeshop/testkube-cli:${{steps.tag.outputs.tag}} --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-amd64 --amend kubeshop/testkube-cli:${{steps.tag.outputs.tag}}-arm64v8
+ docker manifest push -p kubeshop/testkube-cli:${{steps.tag.outputs.tag}}
- name: Upload Artifacts
uses: actions/upload-artifact@master
From 9fd606d13b38f2a68f3c1d77b63f5ed72f4f608d Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 17 Jan 2024 17:27:48 +0200
Subject: [PATCH 013/234] delete sha output
---
.github/workflows/release-dev.yaml | 4 ----
.github/workflows/release.yaml | 4 ----
2 files changed, 8 deletions(-)
diff --git a/.github/workflows/release-dev.yaml b/.github/workflows/release-dev.yaml
index be9e463ce71..c2311de10d3 100644
--- a/.github/workflows/release-dev.yaml
+++ b/.github/workflows/release-dev.yaml
@@ -62,10 +62,6 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Get github sha
- id: github_sha
- run: echo "::set-output name=sha_short::${GITHUB_SHA::7}"
-
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 3ecb2129c75..43e4cb75938 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -63,10 +63,6 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Get github sha
- id: github_sha
- run: echo "::set-output name=sha_short::${GITHUB_SHA::7}"
-
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
From 51caafa01f045be900b65a0e759db2cb193877d5 Mon Sep 17 00:00:00 2001
From: Povilas Versockas
Date: Thu, 18 Jan 2024 10:43:48 +0200
Subject: [PATCH 014/234] feat: add status field to artifacts (#4908)
---
api/v1/testkube.yaml | 8 +++++++-
pkg/api/v1/testkube/model_artifact.go | 1 +
pkg/executor/scraper/extractor.go | 3 +++
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml
index ba7b5601660..caad2352fae 100644
--- a/api/v1/testkube.yaml
+++ b/api/v1/testkube.yaml
@@ -4306,6 +4306,12 @@ components:
type: string
description: execution name that produced the artifact
example: "test-1"
+ status:
+ type: string
+ enum:
+ - ready
+ - processing
+ - failed
ExecutionsResult:
description: the result for a page of executions
@@ -6133,4 +6139,4 @@ components:
- execution
filePath:
type: string
- example: folder/file.txt
\ No newline at end of file
+ example: folder/file.txt
diff --git a/pkg/api/v1/testkube/model_artifact.go b/pkg/api/v1/testkube/model_artifact.go
index 41111f66d2f..062b341e8a3 100644
--- a/pkg/api/v1/testkube/model_artifact.go
+++ b/pkg/api/v1/testkube/model_artifact.go
@@ -17,4 +17,5 @@ type Artifact struct {
Size int32 `json:"size,omitempty"`
// execution name that produced the artifact
ExecutionName string `json:"executionName,omitempty"`
+ Status string `json:"status,omitempty"`
}
diff --git a/pkg/executor/scraper/extractor.go b/pkg/executor/scraper/extractor.go
index 63ce3b5ed04..2acf4e81fa3 100644
--- a/pkg/executor/scraper/extractor.go
+++ b/pkg/executor/scraper/extractor.go
@@ -41,4 +41,7 @@ type FilesMeta struct {
type FileStat struct {
Name string `json:"name"`
Size int64 `json:"size"`
+ // Status shows if file is ready to be downloaded
+ // One of: ready, processing, error
+ Status string `json:"status,omitempty"`
}
From 0a390e70152595f111213db3b103caafbbd6f8cc Mon Sep 17 00:00:00 2001
From: nicufk
Date: Thu, 18 Jan 2024 13:26:41 +0200
Subject: [PATCH 015/234] feat: add minio log consumer with opts (#4867)
* feat: add minio log consumer with opts
* fix: tests
* fix: skip test
* fix: improve code and error handling
* fix: handle chunk too big
* fix: execute but return errors
* fix: minor improvements
* fix: contexts
---
internal/app/api/v1/handlers.go | 3 +-
pkg/logs/adapter/minio.go | 236 +++++++++++++++++++++++++++
pkg/logs/adapter/minio_test.go | 184 +++++++++++++++++++++
pkg/storage/minio/minio.go | 182 +++++----------------
pkg/storage/minio/minio_connecter.go | 147 +++++++++++++++++
5 files changed, 606 insertions(+), 146 deletions(-)
create mode 100644 pkg/logs/adapter/minio.go
create mode 100644 pkg/logs/adapter/minio_test.go
create mode 100644 pkg/storage/minio/minio_connecter.go
diff --git a/internal/app/api/v1/handlers.go b/internal/app/api/v1/handlers.go
index 3db757e5d1c..ee3394b0018 100644
--- a/internal/app/api/v1/handlers.go
+++ b/internal/app/api/v1/handlers.go
@@ -6,13 +6,12 @@ import (
"os"
"strings"
- "github.com/kubeshop/testkube/pkg/version"
-
"github.com/gofiber/fiber/v2"
"github.com/kubeshop/testkube/pkg/api/v1/testkube"
"github.com/kubeshop/testkube/pkg/k8sclient"
"github.com/kubeshop/testkube/pkg/oauth"
+ "github.com/kubeshop/testkube/pkg/version"
)
const (
diff --git a/pkg/logs/adapter/minio.go b/pkg/logs/adapter/minio.go
new file mode 100644
index 00000000000..25d8f15a3f2
--- /dev/null
+++ b/pkg/logs/adapter/minio.go
@@ -0,0 +1,236 @@
+package adapter
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "sync"
+
+ "github.com/minio/minio-go/v7"
+ "go.uber.org/zap"
+
+ "github.com/kubeshop/testkube/pkg/log"
+ "github.com/kubeshop/testkube/pkg/logs/events"
+ minioconnecter "github.com/kubeshop/testkube/pkg/storage/minio"
+)
+
+const (
+ defaultBufferSize = 1024 * 100 // 100KB
+ defaultWriteSize = 1024 * 80 // 80KB
+)
+
+var _ Adapter = &MinioConsumer{}
+
+type ErrMinioConsumerDisconnected struct {
+}
+
+func (e ErrMinioConsumerDisconnected) Error() string {
+ return "minio consumer disconnected"
+}
+
+type ErrIdNotFound struct {
+ Id string
+}
+
+func (e ErrIdNotFound) Error() string {
+ return fmt.Sprintf("id %s not found", e.Id)
+}
+
+type ErrChucnkTooBig struct {
+ Length int
+}
+
+func (e ErrChucnkTooBig) Error() string {
+ return fmt.Sprintf("chunk too big: %d", e.Length)
+}
+
+type BufferInfo struct {
+ Buffer *bytes.Buffer
+ Part int
+}
+
+// MinioConsumer creates new MinioSubscriber which will send data to local MinIO bucket
+func NewMinioConsumer(endpoint, accessKeyID, secretAccessKey, region, token, bucket string, opts ...minioconnecter.Option) (*MinioConsumer, error) {
+ ctx := context.TODO()
+ c := &MinioConsumer{
+ minioConnecter: minioconnecter.NewConnecter(endpoint, accessKeyID, secretAccessKey, region, token, bucket, log.DefaultLogger, opts...),
+ Log: log.DefaultLogger,
+ bucket: bucket,
+ region: region,
+ disconnected: false,
+ buffInfos: make(map[string]BufferInfo),
+ }
+ minioClient, err := c.minioConnecter.GetClient()
+ if err != nil {
+ c.Log.Errorw("error connecting to minio", "err", err)
+ return c, err
+ }
+
+ c.minioClient = minioClient
+ exists, err := c.minioClient.BucketExists(ctx, c.bucket)
+ if err != nil {
+ c.Log.Errorw("error checking if bucket exists", "err", err)
+ return c, err
+ }
+
+ if !exists {
+ err = c.minioClient.MakeBucket(ctx, c.bucket,
+ minio.MakeBucketOptions{Region: c.region})
+ if err != nil {
+ c.Log.Errorw("error creating bucket", "err", err)
+ return c, err
+ }
+ }
+ return c, nil
+}
+
+type MinioConsumer struct {
+ minioConnecter *minioconnecter.Connecter
+ minioClient *minio.Client
+ bucket string
+ region string
+ Log *zap.SugaredLogger
+ disconnected bool
+ buffInfos map[string]BufferInfo
+ mapLock sync.RWMutex
+}
+
+func (s *MinioConsumer) Notify(id string, e events.Log) error {
+ if s.disconnected {
+ s.Log.Debugw("minio consumer disconnected", "id", id)
+ return ErrMinioConsumerDisconnected{}
+ }
+
+ buffInfo, ok := s.GetBuffInfo(id)
+ if !ok {
+ buffInfo = BufferInfo{Buffer: bytes.NewBuffer(make([]byte, 0, defaultBufferSize)), Part: 0}
+ s.UpdateBuffInfo(id, buffInfo)
+ }
+
+ chunckToAdd, err := json.Marshal(e)
+ if err != nil {
+ return err
+ }
+
+ if len(chunckToAdd) > defaultWriteSize {
+ s.Log.Warnw("chunck too big", "length", len(chunckToAdd))
+ return ErrChucnkTooBig{len(chunckToAdd)}
+ }
+
+ chunckToAdd = append(chunckToAdd, []byte("\n")...)
+
+ writer := buffInfo.Buffer
+ _, err = writer.Write(chunckToAdd)
+ if err != nil {
+ return err
+ }
+
+ if writer.Len() > defaultWriteSize {
+ buffInfo.Buffer = bytes.NewBuffer(make([]byte, 0, defaultBufferSize))
+ name := id + "-" + strconv.Itoa(buffInfo.Part)
+ buffInfo.Part++
+ s.UpdateBuffInfo(id, buffInfo)
+ go s.putData(context.TODO(), name, writer)
+ }
+
+ return nil
+}
+
+func (s *MinioConsumer) putData(ctx context.Context, name string, buffer *bytes.Buffer) {
+ if buffer != nil && buffer.Len() != 0 {
+ _, err := s.minioClient.PutObject(ctx, s.bucket, name, buffer, int64(buffer.Len()), minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err != nil {
+ s.Log.Errorw("error putting object", "err", err)
+ }
+ } else {
+ s.Log.Warn("empty buffer for name: ", name)
+ }
+
+}
+
+func (s *MinioConsumer) combineData(ctxt context.Context, minioClient *minio.Client, id string, parts int, deleteIntermediaryData bool) error {
+ var returnedError []error
+ returnedError = nil
+ buffer := bytes.NewBuffer(make([]byte, 0, parts*defaultBufferSize))
+ for i := 0; i < parts; i++ {
+ objectName := fmt.Sprintf("%s-%d", id, i)
+ if s.objectExists(objectName) {
+ objInfo, err := minioClient.GetObject(ctxt, s.bucket, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ s.Log.Errorw("error getting object", "err", err)
+ returnedError = append(returnedError, err)
+ }
+ _, err = buffer.ReadFrom(objInfo)
+ if err != nil {
+ s.Log.Errorw("error reading object", "err", err)
+ returnedError = append(returnedError, err)
+ }
+ }
+ }
+ _, err := minioClient.PutObject(ctxt, s.bucket, id, buffer, int64(buffer.Len()), minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err != nil {
+ s.Log.Errorw("error putting object", "err", err)
+ return err
+ }
+
+ if deleteIntermediaryData {
+ for i := 0; i < parts; i++ {
+ objectName := fmt.Sprintf("%s-%d", id, i)
+ if s.objectExists(objectName) {
+ err = minioClient.RemoveObject(ctxt, s.bucket, objectName, minio.RemoveObjectOptions{})
+ if err != nil {
+ s.Log.Errorw("error removing object", "err", err)
+ returnedError = append(returnedError, err)
+ }
+ }
+ }
+ }
+ buffer.Reset()
+ if len(returnedError) == 0 {
+ return nil
+ }
+ return fmt.Errorf("executed with errors: %v", returnedError)
+}
+
+func (s *MinioConsumer) objectExists(objectName string) bool {
+ _, err := s.minioClient.StatObject(context.Background(), s.bucket, objectName, minio.StatObjectOptions{})
+ return err == nil
+}
+
+func (s *MinioConsumer) Stop(id string) error {
+ ctx := context.TODO()
+ buffInfo, ok := s.GetBuffInfo(id)
+ if !ok {
+ return ErrIdNotFound{id}
+ }
+ name := id + "-" + strconv.Itoa(buffInfo.Part)
+ s.putData(ctx, name, buffInfo.Buffer)
+ parts := buffInfo.Part + 1
+ s.DeleteBuffInfo(id)
+ return s.combineData(ctx, s.minioClient, id, parts, true)
+}
+
+func (s *MinioConsumer) Name() string {
+ return "minio"
+}
+
+func (s *MinioConsumer) GetBuffInfo(id string) (BufferInfo, bool) {
+ s.mapLock.RLock()
+ defer s.mapLock.RUnlock()
+ buffInfo, ok := s.buffInfos[id]
+ return buffInfo, ok
+}
+
+func (s *MinioConsumer) UpdateBuffInfo(id string, buffInfo BufferInfo) {
+ s.mapLock.Lock()
+ defer s.mapLock.Unlock()
+ s.buffInfos[id] = buffInfo
+}
+
+func (s *MinioConsumer) DeleteBuffInfo(id string) {
+ s.mapLock.Lock()
+ defer s.mapLock.Unlock()
+ delete(s.buffInfos, id)
+}
diff --git a/pkg/logs/adapter/minio_test.go b/pkg/logs/adapter/minio_test.go
new file mode 100644
index 00000000000..307e590b5f7
--- /dev/null
+++ b/pkg/logs/adapter/minio_test.go
@@ -0,0 +1,184 @@
+package adapter
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "math/rand"
+ "strconv"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/minio/minio-go/v7"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/kubeshop/testkube/pkg/logs/events"
+ minioconnecter "github.com/kubeshop/testkube/pkg/storage/minio"
+ "github.com/kubeshop/testkube/pkg/utils"
+)
+
+const hugeString = "82vbUcyQ0chpR665zbXY2mySOk7DGDFQCF1iLFjDNUYtNV8oQNaX3IYgJR30zBVhmDVjoZDJXO479tGSirHilZWEbzhjKJOdUwGb2HWOSOOjGh5r5wH0EHxRiOp8mBJv2rwdB2SoKF7JTBFgRt9M8F0JKp2Zx5kqh8eOGB1DGj64NLmwIpfuevJSv0wbDLrls5kEL5hHkszXPsuufVjJBsjNrxCoafuk93L2jE3ivVrMlkmLd9XAWKdop0oo0yRMJ9Vs1T5SZTkM6KXJB5hY3c14NsoPiG9Ay4EZmXrGpzGWI3RLAU6snXL8kV9sVLCG5DuRDnW047VR8eb78fpVj8YY3o9xpZd7xYPAhsmK0SwznHfrb0etAqdjQO6LFS9Blwre3G94DG5scVFH8RfteVNgKJXa8lTp8kKjtQLKNNA9mqyWfJ7uy8yjnVKwl7rodKqdtU6wjH2hf597MXA3roIS2xVhFpsCAVDybo9TVvZpoGfE9povhApoUR6Rmae9zvXPRoDbClOrvDElFkfgkJFzuoY2rPoV3dKuiTNwhYgPm36WPRk3SeFf2NiBQnWJBvjbRMIk5DsGfxcEiXQBfDvY4hgFctjwZ3USvWGriqT1cPsJ90LMLxbp38TRD1KVJ8ZgpqdvKTTi8dBqgEtob7okhdrkOahHJ3EKPtqV4PmaHvXSaIJvDG9c8jza64wxYBwMkHGt22i3HhCcIi8KmmfVo1ruqQLqKvINJg8eD5rKGV1mX9IipQcnrqADYnAj1wls7NSxsL0VZZm2pxRaGN494o2LCicHGEcOYkVLHufXY4Gv3friOIZSrT1r3NUgDBufpXWiG2b02TrRyFhgwRSS1a2OyMjHkT9tALmlIwFGF5HdaZphN6Mo5TFGdJyp65YU1scnlSGAVXzVdhsoD0RDZPSetdK2fzJC20kncaujAujHtSKnXrJNIhObnOjgMhCkx5E4z0oIH26DlfrbxS7k5SBQb1Zo3papQOk4uTNIdMBW4cE3V7AB8r6v4en3"
+
+func init() {
+ rand.New(rand.NewSource(time.Now().UnixNano()))
+}
+
+var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+func RandString(n int) string {
+ b := make([]rune, n)
+ for i := range b {
+ b[i] = letterRunes[rand.Intn(len(letterRunes))]
+ }
+ return string(b)
+}
+
+func TestLogs(t *testing.T) {
+ t.Skip("skipping test")
+ consumer, _ := NewMinioConsumer("localhost:9000", "minio", "minio123", "", "", "test-1", minioconnecter.Insecure())
+ id := "test-bla"
+ for i := 0; i < 1000; i++ {
+ fmt.Println("sending", i)
+ consumer.Notify(id, events.Log{Time: time.Now(),
+ Content: fmt.Sprintf("Test %d: %s", i, hugeString),
+ Type: "test", Source: strconv.Itoa(i)})
+ time.Sleep(100 * time.Millisecond)
+ }
+ err := consumer.Stop(id)
+ assert.NoError(t, err)
+}
+
+func BenchmarkLogs(b *testing.B) {
+ randomString := RandString(5)
+ bucket := "test-bench"
+ consumer, _ := NewMinioConsumer("localhost:9000", "minio", "minio123", "", "", bucket, minioconnecter.Insecure())
+ id := "test-bench" + "-" + randomString + "-" + strconv.Itoa(b.N)
+ totalSize := 0
+ for i := 0; i < b.N; i++ {
+ consumer.Notify(id, events.Log{Time: time.Now(),
+ Content: fmt.Sprintf("Test %d: %s", i, hugeString),
+ Type: "test", Source: strconv.Itoa(i)})
+ totalSize += len(hugeString)
+ }
+ sizeInMB := float64(totalSize) / 1024 / 1024
+ err := consumer.Stop(id)
+ assert.NoError(b, err)
+ b.Logf("Total size for %s logs is %f MB", id, sizeInMB)
+}
+
+func BenchmarkLogs2(b *testing.B) {
+ bucket := "test-bench"
+ consumer, _ := NewMinioConsumer("localhost:9000", "minio", "minio123", "", "", bucket, minioconnecter.Insecure())
+ idChan := make(chan string, 100)
+ go verifyConsumer(idChan, bucket, consumer.minioClient)
+ var counter atomic.Int32
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ randomString := strconv.Itoa(int(counter.Add(1)))
+ id := "test-bench" + "-" + randomString
+ testOneConsumer(consumer, id)
+ idChan <- id
+ }()
+ }
+ wg.Wait()
+}
+
+func testOneConsumer(consumer *MinioConsumer, id string) {
+ fmt.Println("#####starting", id)
+ totalSize := 0
+ numberOFLogs := rand.Intn(100000)
+ for i := 0; i < numberOFLogs; i++ {
+ consumer.Notify(id, events.Log{Time: time.Now(),
+ Content: fmt.Sprintf("Test %d: %s", i, hugeString),
+ Type: "test", Source: strconv.Itoa(i)})
+ totalSize += len(hugeString)
+ time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
+ }
+ sizeInMB := float64(totalSize) / 1024 / 1024
+ err := consumer.Stop(id)
+ if err != nil {
+ fmt.Println("#####error stopping", err)
+ }
+ fmt.Printf("#####Total size for %s logs is %f MB\n\n\n", id, sizeInMB)
+}
+
+func verifyConsumer(idChan chan string, bucket string, minioClient *minio.Client) {
+ okSlice := make([]string, 0)
+ notOkSlice := make([]string, 0)
+ for id := range idChan {
+ reader, err := minioClient.GetObject(context.Background(), bucket, id, minio.GetObjectOptions{})
+ if err != nil {
+ fmt.Println("######error getting object", err)
+ }
+ count := 0
+
+ r := bufio.NewReader(reader)
+ isOk := true
+ for {
+ line, err := utils.ReadLongLine(r)
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ break
+ }
+ var LogChunk events.Log
+ err = json.Unmarshal(line, &LogChunk)
+ if err != nil {
+ fmt.Printf("for id %s error %v unmarshalling %s\n\n\n", id, err, string(line))
+ isOk = false
+ break
+ }
+ if LogChunk.Source == "" || LogChunk.Source != strconv.Itoa(count) {
+ fmt.Printf("for id %s not equal for count %d line %s \n logChunk %+v\n\n\n", id, count, string(line), LogChunk)
+ isOk = false
+ break
+ }
+ count++
+ }
+ if isOk {
+ okSlice = append(okSlice, id)
+ } else {
+ notOkSlice = append(notOkSlice, id)
+ }
+ }
+ fmt.Println("##### number of ok", len(okSlice))
+ fmt.Println("#####verified ok", okSlice)
+ fmt.Println("##### number of not ok", len(notOkSlice))
+ fmt.Println("#####verified not ok", notOkSlice)
+}
+
+func DoRunBenchmark() {
+ numberOfConsumers := 100
+ bucket := "test-bench"
+ consumer, _ := NewMinioConsumer("testkube-minio-service-testkube:9000", "minio", "minio123", "", "", bucket, minioconnecter.Insecure())
+
+ idChan := make(chan string, numberOfConsumers)
+ DoRunBenchmark2(idChan, numberOfConsumers, consumer)
+ verifyConsumer(idChan, bucket, consumer.minioClient)
+}
+
+func DoRunBenchmark2(idChan chan string, numberOfConsumers int, consumer *MinioConsumer) {
+ var counter atomic.Int32
+ var wg sync.WaitGroup
+ for i := 0; i < numberOfConsumers; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ randomString := strconv.Itoa(int(counter.Add(1)))
+ id := "test-bench" + "-" + randomString
+ testOneConsumer(consumer, id)
+ idChan <- id
+ }()
+ }
+ wg.Wait()
+ close(idChan)
+ fmt.Printf("#####Done buffInfo is %+v\n\n\n", consumer.buffInfos)
+}
diff --git a/pkg/storage/minio/minio.go b/pkg/storage/minio/minio.go
index 8078d90857e..7a7ea70bb22 100644
--- a/pkg/storage/minio/minio.go
+++ b/pkg/storage/minio/minio.go
@@ -3,8 +3,6 @@ package minio
import (
"bytes"
"context"
- "crypto/tls"
- "crypto/x509"
"fmt"
"hash/fnv"
"io"
@@ -16,7 +14,6 @@ import (
"github.com/pkg/errors"
"github.com/minio/minio-go/v7"
- "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/lifecycle"
"go.uber.org/zap"
@@ -35,89 +32,20 @@ var ErrArtifactsNotFound = errors.New("Execution doesn't have any artifacts asso
// Client for managing MinIO storage server
type Client struct {
- Endpoint string
- accessKeyID string
- secretAccessKey string
- ssl bool
- region string
- token string
- bucket string
- opts []Option
- minioclient *minio.Client
- tlsConfig *tls.Config
- Log *zap.SugaredLogger
-}
-
-type Option func(*Client) error
-
-// Insecure is an Option to enable TLS secure connections that skip server verification.
-func Insecure() Option {
- return func(o *Client) error {
- if o.tlsConfig == nil {
- o.tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
- }
- o.tlsConfig.InsecureSkipVerify = true
- o.ssl = true
- return nil
- }
-}
-
-// RootCAs is a helper option to provide the RootCAs pool from a list of filenames.
-// If Secure is not already set this will set it as well.
-func RootCAs(file ...string) Option {
- return func(o *Client) error {
- pool := x509.NewCertPool()
- for _, f := range file {
- rootPEM, err := os.ReadFile(f)
- if err != nil || rootPEM == nil {
- return fmt.Errorf("nats: error loading or parsing rootCA file: %v", err)
- }
- ok := pool.AppendCertsFromPEM(rootPEM)
- if !ok {
- return fmt.Errorf("nats: failed to parse root certificate from %q", f)
- }
- }
- if o.tlsConfig == nil {
- o.tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
- }
- o.tlsConfig.RootCAs = pool
- o.ssl = true
- return nil
- }
-}
-
-// ClientCert is a helper option to provide the client certificate from a file.
-// If Secure is not already set this will set it as well.
-func ClientCert(certFile, keyFile string) Option {
- return func(o *Client) error {
- cert, err := tls.LoadX509KeyPair(certFile, keyFile)
- if err != nil {
- return fmt.Errorf("nats: error loading client certificate: %v", err)
- }
- cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
- if err != nil {
- return fmt.Errorf("nats: error parsing client certificate: %v", err)
- }
- if o.tlsConfig == nil {
- o.tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
- }
- o.tlsConfig.Certificates = []tls.Certificate{cert}
- o.ssl = true
- return nil
- }
+ region string
+ bucket string
+ minioClient *minio.Client
+ Log *zap.SugaredLogger
+ minioConnecter *Connecter
}
// NewClient returns new MinIO client
func NewClient(endpoint, accessKeyID, secretAccessKey, region, token, bucket string, opts ...Option) *Client {
c := &Client{
- region: region,
- accessKeyID: accessKeyID,
- secretAccessKey: secretAccessKey,
- token: token,
- bucket: bucket,
- Endpoint: endpoint,
- opts: opts,
- Log: log.DefaultLogger,
+ minioConnecter: NewConnecter(endpoint, accessKeyID, secretAccessKey, region, token, bucket, log.DefaultLogger, opts...),
+ region: region,
+ bucket: bucket,
+ Log: log.DefaultLogger,
}
return c
@@ -125,47 +53,13 @@ func NewClient(endpoint, accessKeyID, secretAccessKey, region, token, bucket str
// Connect connects to MinIO server
func (c *Client) Connect() error {
- for _, opt := range c.opts {
- if err := opt(c); err != nil {
- return errors.Wrapf(err, "error connecting to server")
- }
- }
- creds := credentials.NewIAM("")
- c.Log.Debugw("connecting to server",
- "endpoint", c.Endpoint,
- "accessKeyID", c.accessKeyID,
- "region", c.region,
- "token", c.token,
- "bucket", c.bucket,
- "ssl", c.ssl)
- if c.accessKeyID != "" && c.secretAccessKey != "" {
- creds = credentials.NewStaticV4(c.accessKeyID, c.secretAccessKey, c.token)
- }
- transport, err := minio.DefaultTransport(c.ssl)
- if err != nil {
- c.Log.Errorw("error creating minio transport", "error", err)
- return err
- }
- transport.TLSClientConfig = c.tlsConfig
- opts := &minio.Options{
- Creds: creds,
- Secure: c.ssl,
- Transport: transport,
- }
- if c.region != "" {
- opts.Region = c.region
- }
- mclient, err := minio.New(c.Endpoint, opts)
- if err != nil {
- c.Log.Errorw("error connecting to minio", "error", err)
- return err
- }
- c.minioclient = mclient
+ var err error
+ c.minioClient, err = c.minioConnecter.GetClient()
return err
}
func (c *Client) SetExpirationPolicy(expirationDays int) error {
- if expirationDays != 0 && c.minioclient != nil {
+ if expirationDays != 0 && c.minioClient != nil {
lifecycleConfig := &lifecycle.Configuration{
Rules: []lifecycle.Rule{
{
@@ -177,7 +71,7 @@ func (c *Client) SetExpirationPolicy(expirationDays int) error {
},
},
}
- return c.minioclient.SetBucketLifecycle(context.TODO(), c.bucket, lifecycleConfig)
+ return c.minioClient.SetBucketLifecycle(context.TODO(), c.bucket, lifecycleConfig)
}
return nil
}
@@ -187,11 +81,11 @@ func (c *Client) CreateBucket(ctx context.Context, bucket string) error {
if err := c.Connect(); err != nil {
return err
}
- err := c.minioclient.MakeBucket(ctx, bucket, minio.MakeBucketOptions{Region: c.region})
+ err := c.minioClient.MakeBucket(ctx, bucket, minio.MakeBucketOptions{Region: c.region})
if err != nil {
c.Log.Errorw("error creating bucket", "error", err)
// Check to see if we already own this bucket (which happens if you run this twice)
- exists, errBucketExists := c.minioclient.BucketExists(ctx, bucket)
+ exists, errBucketExists := c.minioClient.BucketExists(ctx, bucket)
if errBucketExists == nil && exists {
return fmt.Errorf("bucket %q already exists", bucket)
} else {
@@ -206,7 +100,7 @@ func (c *Client) DeleteBucket(ctx context.Context, bucket string, force bool) er
if err := c.Connect(); err != nil {
return err
}
- return c.minioclient.RemoveBucketWithOptions(ctx, bucket, minio.RemoveBucketOptions{ForceDelete: force})
+ return c.minioClient.RemoveBucketWithOptions(ctx, bucket, minio.RemoveBucketOptions{ForceDelete: force})
}
// ListBuckets lists available buckets
@@ -215,7 +109,7 @@ func (c *Client) ListBuckets(ctx context.Context) ([]string, error) {
return nil, err
}
var toReturn []string
- if buckets, err := c.minioclient.ListBuckets(ctx); err != nil {
+ if buckets, err := c.minioClient.ListBuckets(ctx); err != nil {
return nil, err
} else {
for _, bucket := range buckets {
@@ -232,7 +126,7 @@ func (c *Client) listFiles(ctx context.Context, bucket, bucketFolder string) ([]
}
var toReturn []testkube.Artifact
- exists, err := c.minioclient.BucketExists(ctx, bucket)
+ exists, err := c.minioClient.BucketExists(ctx, bucket)
if err != nil {
return nil, err
}
@@ -246,7 +140,7 @@ func (c *Client) listFiles(ctx context.Context, bucket, bucketFolder string) ([]
listOptions.Prefix = bucketFolder
}
- for obj := range c.minioclient.ListObjects(ctx, bucket, listOptions) {
+ for obj := range c.minioClient.ListObjects(ctx, bucket, listOptions) {
if obj.Err != nil {
return nil, obj.Err
}
@@ -264,7 +158,7 @@ func (c *Client) ListFiles(ctx context.Context, bucketFolder string) ([]testkube
c.Log.Infow("listing files", "bucket", c.bucket, "bucketFolder", bucketFolder)
// TODO: this is for back compatibility, remove it sometime in the future
if bucketFolder != "" {
- if exist, err := c.minioclient.BucketExists(ctx, bucketFolder); err == nil && exist {
+ if exist, err := c.minioClient.BucketExists(ctx, bucketFolder); err == nil && exist {
formerResult, err := c.listFiles(ctx, bucketFolder, "")
if err == nil && len(formerResult) > 0 {
return formerResult, nil
@@ -291,7 +185,7 @@ func (c *Client) saveFile(ctx context.Context, bucket, bucketFolder, filePath st
return fmt.Errorf("minio object stat (file:%s) error: %w", filePath, err)
}
- exists, err := c.minioclient.BucketExists(ctx, bucket)
+ exists, err := c.minioClient.BucketExists(ctx, bucket)
if err != nil || !exists {
err := c.CreateBucket(ctx, bucket)
if err != nil {
@@ -305,7 +199,7 @@ func (c *Client) saveFile(ctx context.Context, bucket, bucketFolder, filePath st
}
c.Log.Debugw("saving object in minio", "filePath", filePath, "fileName", fileName, "bucket", bucket, "size", objectStat.Size())
- _, err = c.minioclient.PutObject(ctx, bucket, fileName, object, objectStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ _, err = c.minioClient.PutObject(ctx, bucket, fileName, object, objectStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
return fmt.Errorf("minio saving file (%s) put object error: %w", fileName, err)
}
@@ -314,7 +208,7 @@ func (c *Client) saveFile(ctx context.Context, bucket, bucketFolder, filePath st
}
func (c *Client) SaveFileDirect(ctx context.Context, folder, file string, data io.Reader, size int64, opts minio.PutObjectOptions) error {
- exists, err := c.minioclient.BucketExists(ctx, c.bucket)
+ exists, err := c.minioClient.BucketExists(ctx, c.bucket)
if err != nil {
return errors.Wrapf(err, "error checking does bucket %s exists", c.bucket)
}
@@ -333,7 +227,7 @@ func (c *Client) SaveFileDirect(ctx context.Context, folder, file string, data i
opts.ContentType = "application/octet-stream"
}
c.Log.Debugw("saving object in minio", "filename", filename, "bucket", c.bucket, "size", size)
- _, err = c.minioclient.PutObject(ctx, c.bucket, filename, data, size, opts)
+ _, err = c.minioClient.PutObject(ctx, c.bucket, filename, data, size, opts)
if err != nil {
return errors.Wrapf(err, "minio saving file (%s) put object error", filename)
}
@@ -354,7 +248,7 @@ func (c *Client) downloadFile(ctx context.Context, bucket, bucketFolder, file st
return nil, fmt.Errorf("minio DownloadFile .Connect error: %w", err)
}
- exists, err := c.minioclient.BucketExists(ctx, bucket)
+ exists, err := c.minioClient.BucketExists(ctx, bucket)
if err != nil {
return nil, err
}
@@ -368,7 +262,7 @@ func (c *Client) downloadFile(ctx context.Context, bucket, bucketFolder, file st
file = strings.Trim(bucketFolder, "/") + "/" + file
}
- reader, err := c.minioclient.GetObject(ctx, bucket, file, minio.GetObjectOptions{})
+ reader, err := c.minioClient.GetObject(ctx, bucket, file, minio.GetObjectOptions{})
if err != nil {
return nil, fmt.Errorf("minio DownloadFile GetObject error: %w", err)
}
@@ -388,7 +282,7 @@ func (c *Client) DownloadFile(ctx context.Context, bucketFolder, file string) (*
var objFirst *minio.Object
var errFirst error
if bucketFolder != "" {
- exists, err := c.minioclient.BucketExists(ctx, bucketFolder)
+ exists, err := c.minioClient.BucketExists(ctx, bucketFolder)
c.Log.Debugw("Checking if bucket exists", exists, err)
if err == nil && exists {
c.Log.Infow("Bucket exists, trying to get files from former bucket per execution", exists, err)
@@ -412,7 +306,7 @@ func (c *Client) downloadArchive(ctx context.Context, bucket, bucketFolder strin
return nil, fmt.Errorf("minio DownloadArchive .Connect error: %w", err)
}
- exists, err := c.minioclient.BucketExists(ctx, bucket)
+ exists, err := c.minioClient.BucketExists(ctx, bucket)
if err != nil {
return nil, err
}
@@ -441,7 +335,7 @@ func (c *Client) downloadArchive(ctx context.Context, bucket, bucketFolder strin
}
var files []*archive.File
- for obj := range c.minioclient.ListObjects(ctx, bucket, listOptions) {
+ for obj := range c.minioClient.ListObjects(ctx, bucket, listOptions) {
if obj.Err != nil {
return nil, fmt.Errorf("minio DownloadArchive ListObjects error: %w", obj.Err)
}
@@ -466,7 +360,7 @@ func (c *Client) downloadArchive(ctx context.Context, bucket, bucketFolder strin
}
for i := range files {
- reader, err := c.minioclient.GetObject(ctx, bucket, files[i].Name, minio.GetObjectOptions{})
+ reader, err := c.minioClient.GetObject(ctx, bucket, files[i].Name, minio.GetObjectOptions{})
if err != nil {
return nil, fmt.Errorf("minio DownloadArchive GetObject error: %w", err)
}
@@ -497,7 +391,7 @@ func (c *Client) DownloadArchive(ctx context.Context, bucketFolder string, masks
var objFirst io.Reader
var errFirst error
if bucketFolder != "" {
- exists, err := c.minioclient.BucketExists(ctx, bucketFolder)
+ exists, err := c.minioClient.BucketExists(ctx, bucketFolder)
c.Log.Debugw("Checking if bucket exists", exists, err)
if err == nil && exists {
c.Log.Infow("Bucket exists, trying to get archive from former bucket per execution", exists, err)
@@ -568,7 +462,7 @@ func (c *Client) uploadFile(ctx context.Context, bucket, bucketFolder, filePath
return fmt.Errorf("minio UploadFile connection error: %w", err)
}
- exists, err := c.minioclient.BucketExists(ctx, bucket)
+ exists, err := c.minioClient.BucketExists(ctx, bucket)
if err != nil {
return fmt.Errorf("could not check if bucket already exists for copy files: %w", err)
}
@@ -586,7 +480,7 @@ func (c *Client) uploadFile(ctx context.Context, bucket, bucketFolder, filePath
}
c.Log.Debugw("saving object in minio", "file", filePath, "bucket", bucket)
- _, err = c.minioclient.PutObject(ctx, bucket, filePath, reader, objectSize, minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ _, err = c.minioClient.PutObject(ctx, bucket, filePath, reader, objectSize, minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
return fmt.Errorf("minio saving file (%s) put object error: %w", filePath, err)
}
@@ -611,7 +505,7 @@ func (c *Client) PlaceFiles(ctx context.Context, bucketFolders []string, prefix
output.PrintLog(fmt.Sprintf("%s Minio PlaceFiles connection error: %s", ui.IconWarning, err.Error()))
return fmt.Errorf("minio PlaceFiles connection error: %w", err)
}
- exists, err := c.minioclient.BucketExists(ctx, c.bucket)
+ exists, err := c.minioClient.BucketExists(ctx, c.bucket)
if err != nil {
output.PrintLog(fmt.Sprintf("%s Could not check if bucket already exists for files %s", ui.IconWarning, err.Error()))
return fmt.Errorf("could not check if bucket already exists for files: %w", err)
@@ -644,7 +538,7 @@ func (c *Client) PlaceFiles(ctx context.Context, bucketFolders []string, prefix
}
path := filepath.Join(prefix, f.Name)
- err = c.minioclient.FGetObject(ctx, c.bucket, objectName, path, minio.GetObjectOptions{})
+ err = c.minioClient.FGetObject(ctx, c.bucket, objectName, path, minio.GetObjectOptions{})
if err != nil {
output.PrintEvent(fmt.Sprintf("%s Could not download file %s", ui.IconCross, f.Name))
return fmt.Errorf("could not persist file %s from bucket %s, folder %s: %w", f.Name, c.bucket, folder, err)
@@ -673,7 +567,7 @@ func (c *Client) deleteFile(ctx context.Context, bucket, bucketFolder, file stri
return fmt.Errorf("minio DeleteFile connection error: %w", err)
}
- exists, err := c.minioclient.BucketExists(ctx, bucket)
+ exists, err := c.minioClient.BucketExists(ctx, bucket)
if err != nil {
return fmt.Errorf("could not check if bucket already exists for delete file: %w", err)
}
@@ -687,7 +581,7 @@ func (c *Client) deleteFile(ctx context.Context, bucket, bucketFolder, file stri
file = strings.Trim(bucketFolder, "/") + "/" + file
}
- err = c.minioclient.RemoveObject(ctx, bucket, file, minio.RemoveObjectOptions{ForceDelete: true})
+ err = c.minioClient.RemoveObject(ctx, bucket, file, minio.RemoveObjectOptions{ForceDelete: true})
if err != nil {
return fmt.Errorf("minio DeleteFile RemoveObject error: %w", err)
}
@@ -700,7 +594,7 @@ func (c *Client) DeleteFile(ctx context.Context, bucketFolder, file string) erro
// TODO: this is for back compatibility, remove it sometime in the future
var errFirst error
if bucketFolder != "" {
- if exist, err := c.minioclient.BucketExists(ctx, bucketFolder); err != nil || !exist {
+ if exist, err := c.minioClient.BucketExists(ctx, bucketFolder); err != nil || !exist {
errFirst = c.DeleteFileFromBucket(ctx, bucketFolder, "", file)
if err == nil {
return nil
diff --git a/pkg/storage/minio/minio_connecter.go b/pkg/storage/minio/minio_connecter.go
new file mode 100644
index 00000000000..309c375b715
--- /dev/null
+++ b/pkg/storage/minio/minio_connecter.go
@@ -0,0 +1,147 @@
+package minio
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "os"
+
+ "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+ "github.com/pkg/errors"
+ "go.uber.org/zap"
+)
+
+type Option func(*Connecter) error
+
+// Insecure is an Option to enable TLS secure connections that skip server verification.
+func Insecure() Option {
+ return func(o *Connecter) error {
+ if o.TlsConfig == nil {
+ o.TlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
+ }
+ o.TlsConfig.InsecureSkipVerify = true
+ o.Ssl = true
+ return nil
+ }
+}
+
+// RootCAs is a helper option to provide the RootCAs pool from a list of filenames.
+// If Secure is not already set this will set it as well.
+func RootCAs(file ...string) Option {
+ return func(o *Connecter) error {
+ pool := x509.NewCertPool()
+ for _, f := range file {
+ rootPEM, err := os.ReadFile(f)
+ if err != nil || rootPEM == nil {
+ return fmt.Errorf("nats: error loading or parsing rootCA file: %v", err)
+ }
+ ok := pool.AppendCertsFromPEM(rootPEM)
+ if !ok {
+ return fmt.Errorf("nats: failed to parse root certificate from %q", f)
+ }
+ }
+ if o.TlsConfig == nil {
+ o.TlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
+ }
+ o.TlsConfig.RootCAs = pool
+ o.Ssl = true
+ return nil
+ }
+}
+
+// ClientCert is a helper option to provide the client certificate from a file.
+// If Secure is not already set this will set it as well.
+func ClientCert(certFile, keyFile string) Option {
+ return func(o *Connecter) error {
+ cert, err := tls.LoadX509KeyPair(certFile, keyFile)
+ if err != nil {
+ return fmt.Errorf("nats: error loading client certificate: %v", err)
+ }
+ cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
+ if err != nil {
+ return fmt.Errorf("nats: error parsing client certificate: %v", err)
+ }
+ if o.TlsConfig == nil {
+ o.TlsConfig = &tls.Config{MinVersion: tls.VersionTLS12}
+ }
+ o.TlsConfig.Certificates = []tls.Certificate{cert}
+ o.Ssl = true
+ return nil
+ }
+}
+
+type Connecter struct {
+ Endpoint string
+ AccessKeyID string
+ SecretAccessKey string
+ Region string
+ Token string
+ Bucket string
+ Ssl bool
+ TlsConfig *tls.Config
+ Opts []Option
+ Log *zap.SugaredLogger
+ client *minio.Client
+}
+
+// NewConnecter creates a new Connecter
+func NewConnecter(endpoint, accessKeyID, secretAccessKey, region, token, bucket string, log *zap.SugaredLogger, opts ...Option) *Connecter {
+ c := &Connecter{
+ Endpoint: endpoint,
+ AccessKeyID: accessKeyID,
+ SecretAccessKey: secretAccessKey,
+ Region: region,
+ Token: token,
+ Bucket: bucket,
+ Opts: opts,
+ Log: log,
+ }
+ return c
+}
+
+// GetClient() connects to MinIO
+func (c *Connecter) GetClient() (*minio.Client, error) {
+ if c.client != nil {
+ return c.client, nil
+ }
+
+ for _, opt := range c.Opts {
+ if err := opt(c); err != nil {
+ return nil, errors.Wrapf(err, "error connecting to server")
+ }
+ }
+ creds := credentials.NewIAM("")
+ c.Log.Debugw("connecting to server",
+ "endpoint", c.Endpoint,
+ "accessKeyID", c.AccessKeyID,
+ "region", c.Region,
+ "token", c.Token,
+ "bucket", c.Bucket,
+ "ssl", c.Ssl)
+ if c.AccessKeyID != "" && c.SecretAccessKey != "" {
+ creds = credentials.NewStaticV4(c.AccessKeyID, c.SecretAccessKey, c.Token)
+ }
+ transport, err := minio.DefaultTransport(c.Ssl)
+ if err != nil {
+ c.Log.Errorw("error creating minio transport", "error", err)
+ return nil, err
+ }
+ transport.TLSClientConfig = c.TlsConfig
+ opts := &minio.Options{
+ Creds: creds,
+ Secure: c.Ssl,
+ Transport: transport,
+ }
+ if c.Region != "" {
+ opts.Region = c.Region
+ }
+ mclient, err := minio.New(c.Endpoint, opts)
+ if err != nil {
+ c.Log.Errorw("error connecting to minio", "error", err)
+ return nil, err
+ }
+
+ c.client = mclient
+ return mclient, nil
+}
From cc25be898cdbcd95331d15587c7e0921fe6423e0 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Thu, 18 Jan 2024 14:39:06 +0300
Subject: [PATCH 016/234] fix: fiber doesn't allow to use query string outside
handler
---
internal/app/api/v1/executions.go | 3 ++-
internal/app/api/v1/testsuites.go | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/internal/app/api/v1/executions.go b/internal/app/api/v1/executions.go
index 16a029bee9c..97d11fb2ca5 100644
--- a/internal/app/api/v1/executions.go
+++ b/internal/app/api/v1/executions.go
@@ -10,6 +10,7 @@ import (
"net/url"
"os"
"strconv"
+ "strings"
"k8s.io/apimachinery/pkg/api/errors"
@@ -86,7 +87,7 @@ func (s *TestkubeAPI) ExecuteTestsHandler() fiber.Handler {
}
var results []testkube.Execution
if len(tests) != 0 {
- request.TestExecutionName = c.Query("testExecutionName")
+ request.TestExecutionName = strings.Clone(c.Query("testExecutionName"))
concurrencyLevel, err := strconv.Atoi(c.Query("concurrency", strconv.Itoa(scheduler.DefaultConcurrencyLevel)))
if err != nil {
return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: can't detect concurrency level: %w", errPrefix, err))
diff --git a/internal/app/api/v1/testsuites.go b/internal/app/api/v1/testsuites.go
index 566d7f2cdea..34e635bf099 100644
--- a/internal/app/api/v1/testsuites.go
+++ b/internal/app/api/v1/testsuites.go
@@ -577,7 +577,7 @@ func (s TestkubeAPI) ExecuteTestSuitesHandler() fiber.Handler {
var results []testkube.TestSuiteExecution
if len(testSuites) != 0 {
- request.TestSuiteExecutionName = c.Query("testSuiteExecutionName")
+ request.TestSuiteExecutionName = strings.Clone(c.Query("testSuiteExecutionName"))
concurrencyLevel, err := strconv.Atoi(c.Query("concurrency", strconv.Itoa(scheduler.DefaultConcurrencyLevel)))
if err != nil {
return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: can't detect concurrency level: %w", errPrefix, err))
From 600f1a8e3fbde1a1cd0d9bb4d2f63b1346d35af4 Mon Sep 17 00:00:00 2001
From: Tomasz Konieczny
Date: Thu, 18 Jan 2024 13:30:32 +0100
Subject: [PATCH 017/234] feat: Executor tests - container executor - Gradle
and Maven (#4898)
* executor tests - container executor - maven and gradle
* empty lines added
* empty lines added
---
.../executor-smoke/crd/gradle.yaml | 22 ++++++++++++++++
.../executor-smoke/crd/maven.yaml | 22 ++++++++++++++++
test/executors/container-executor-gradle.yaml | 9 +++++++
test/executors/container-executor-maven.yaml | 10 +++++++
test/scripts/executor-tests/run.sh | 26 +++++++++++++++++++
...executor-container-gradle-smoke-tests.yaml | 12 +++++++++
.../executor-container-maven-smoke-tests.yaml | 12 +++++++++
7 files changed, 113 insertions(+)
create mode 100644 test/container-executor/executor-smoke/crd/gradle.yaml
create mode 100644 test/container-executor/executor-smoke/crd/maven.yaml
create mode 100644 test/executors/container-executor-gradle.yaml
create mode 100644 test/executors/container-executor-maven.yaml
create mode 100644 test/suites/executor-container-gradle-smoke-tests.yaml
create mode 100644 test/suites/executor-container-maven-smoke-tests.yaml
diff --git a/test/container-executor/executor-smoke/crd/gradle.yaml b/test/container-executor/executor-smoke/crd/gradle.yaml
new file mode 100644
index 00000000000..a01732880ba
--- /dev/null
+++ b/test/container-executor/executor-smoke/crd/gradle.yaml
@@ -0,0 +1,22 @@
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: container-executor-gradle-jdk-11
+ labels:
+ core-tests: executors
+spec:
+ type: container-executor-gradle-8.5-jdk11/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube
+ branch: main
+ path: contrib/executor/gradle/examples/hello-gradle
+ workingDir: contrib/executor/gradle/examples/hello-gradle
+ executionRequest:
+ variables:
+ TESTKUBE_GRADLE:
+ name: TESTKUBE_GRADLE
+ value: "true"
+ type: basic
diff --git a/test/container-executor/executor-smoke/crd/maven.yaml b/test/container-executor/executor-smoke/crd/maven.yaml
new file mode 100644
index 00000000000..2eb4ff0ea62
--- /dev/null
+++ b/test/container-executor/executor-smoke/crd/maven.yaml
@@ -0,0 +1,22 @@
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: container-executor-maven-jdk-11
+ labels:
+ core-tests: executors
+spec:
+ type: container-executor-maven-3.9-jdk11/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube
+ branch: main
+ path: contrib/executor/maven/examples/hello-maven
+ workingDir: contrib/executor/maven/examples/hello-maven
+ executionRequest:
+ variables:
+ TESTKUBE_MAVEN:
+ name: TESTKUBE_MAVEN
+ value: "true"
+ type: basic
diff --git a/test/executors/container-executor-gradle.yaml b/test/executors/container-executor-gradle.yaml
new file mode 100644
index 00000000000..91249ff6251
--- /dev/null
+++ b/test/executors/container-executor-gradle.yaml
@@ -0,0 +1,9 @@
+apiVersion: executor.testkube.io/v1
+kind: Executor
+metadata:
+ name: container-executor-gradle-8.5-jdk11
+spec:
+ image: gradle:8.5.0-jdk11
+ executor_type: container
+ types:
+ - container-executor-gradle-8.5-jdk11/test
diff --git a/test/executors/container-executor-maven.yaml b/test/executors/container-executor-maven.yaml
new file mode 100644
index 00000000000..1b3f246749a
--- /dev/null
+++ b/test/executors/container-executor-maven.yaml
@@ -0,0 +1,10 @@
+apiVersion: executor.testkube.io/v1
+kind: Executor
+metadata:
+ name: container-executor-maven-3.9-jdk11
+spec:
+ image: maven:3.9.6-eclipse-temurin-11-focal
+ executor_type: container
+ types:
+ - container-executor-maven-3.9-jdk11/test
+ command: ["mvn", "test"]
diff --git a/test/scripts/executor-tests/run.sh b/test/scripts/executor-tests/run.sh
index 4d471a7e7da..740c23a04e2 100755
--- a/test/scripts/executor-tests/run.sh
+++ b/test/scripts/executor-tests/run.sh
@@ -142,6 +142,17 @@ container-cypress-smoke() {
common_run "$name" "$test_crd_file" "$testsuite_name" "$testsuite_file" "$custom_executor_crd_file"
}
+container-gradle-smoke() {
+ name="Container executor - Gradle"
+ test_crd_file="test/container-executor/executor-smoke/crd/gradle.yaml"
+ testsuite_name="executor-container-gradle-smoke-tests"
+ testsuite_file="test/suites/executor-container-gradle-smoke-tests.yaml"
+
+ custom_executor_crd_file="test/executors/container-executor-gradle.yaml"
+
+ common_run "$name" "$test_crd_file" "$testsuite_name" "$testsuite_file" "$custom_executor_crd_file"
+}
+
container-k6-smoke() {
name="Container executor - K6"
test_crd_file="test/container-executor/executor-smoke/crd/k6.yaml"
@@ -153,6 +164,17 @@ container-k6-smoke() {
common_run "$name" "$test_crd_file" "$testsuite_name" "$testsuite_file" "$custom_executor_crd_file"
}
+container-maven-smoke() {
+ name="Container executor - Maven"
+ test_crd_file="test/container-executor/executor-smoke/crd/maven.yaml"
+ testsuite_name="executor-container-maven-smoke-tests"
+ testsuite_file="test/suites/executor-container-maven-smoke-tests.yaml"
+
+ custom_executor_crd_file="test/executors/container-executor-maven.yaml"
+
+ common_run "$name" "$test_crd_file" "$testsuite_name" "$testsuite_file" "$custom_executor_crd_file"
+}
+
container-playwright-smoke() {
name="Container executor - Playwright"
test_crd_file="test/container-executor/executor-smoke/crd/playwright.yaml"
@@ -344,7 +366,9 @@ main() {
artillery-smoke
container-curl-smoke
container-cypress-smoke
+ container-gradle-smoke
container-k6-smoke
+ container-maven-smoke
container-postman-smoke
container-playwright-smoke
curl-smoke
@@ -365,7 +389,9 @@ main() {
artillery-smoke
container-curl-smoke
container-cypress-smoke
+ container-gradle-smoke
container-k6-smoke
+ container-maven-smoke
container-postman-smoke
container-playwright-smoke
curl-smoke
diff --git a/test/suites/executor-container-gradle-smoke-tests.yaml b/test/suites/executor-container-gradle-smoke-tests.yaml
new file mode 100644
index 00000000000..c642decd13e
--- /dev/null
+++ b/test/suites/executor-container-gradle-smoke-tests.yaml
@@ -0,0 +1,12 @@
+apiVersion: tests.testkube.io/v3
+kind: TestSuite
+metadata:
+ name: executor-container-gradle-smoke-tests
+ labels:
+ core-tests: executors
+spec:
+ description: "container executor gradle smoke tests"
+ steps:
+ - stopOnFailure: false
+ execute:
+ - test: container-executor-gradle-jdk-11
diff --git a/test/suites/executor-container-maven-smoke-tests.yaml b/test/suites/executor-container-maven-smoke-tests.yaml
new file mode 100644
index 00000000000..34b944eb83d
--- /dev/null
+++ b/test/suites/executor-container-maven-smoke-tests.yaml
@@ -0,0 +1,12 @@
+apiVersion: tests.testkube.io/v3
+kind: TestSuite
+metadata:
+ name: executor-container-maven-smoke-tests
+ labels:
+ core-tests: executors
+spec:
+ description: "container executor maven smoke tests"
+ steps:
+ - stopOnFailure: false
+ execute:
+ - test: container-executor-maven-jdk-11
From 740c95250aac2fe2aa2a791afa3cd3d2a951c270 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Thu, 18 Jan 2024 16:09:42 +0300
Subject: [PATCH 018/234] fix: dep update
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index c1e3f72a865..e3c75806c68 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
github.com/joshdk/go-junit v1.0.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/kubepug/kubepug v1.7.1
- github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20231214095624-483fef2d8731
+ github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118124335-9424636d3456
github.com/minio/minio-go/v7 v7.0.47
github.com/montanaflynn/stats v0.6.6
github.com/moogar0880/problems v0.1.1
diff --git a/go.sum b/go.sum
index abb930584df..e2d3038d64f 100644
--- a/go.sum
+++ b/go.sum
@@ -354,8 +354,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kubepug/kubepug v1.7.1 h1:LKhfSxS8Y5mXs50v+3Lpyec+cogErDLcV7CMUuiaisw=
github.com/kubepug/kubepug v1.7.1/go.mod h1:lv+HxD0oTFL7ZWjj0u6HKhMbbTIId3eG7aWIW0gyF8g=
-github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20231214095624-483fef2d8731 h1:otwyKLt+5Trz5c2EMS+yjMV33O26sdNlcJJkRSiu2tU=
-github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20231214095624-483fef2d8731/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk=
+github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118124335-9424636d3456 h1:rBJgy8PnkY6mQDUSKl11DJOKTekwwFAKg2CtXC1UIPo=
+github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118124335-9424636d3456/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
From d7bec40e860457ce132143f42328f17d5c7f9823 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Thu, 18 Jan 2024 16:22:25 +0300
Subject: [PATCH 019/234] fix: dep update
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index e3c75806c68..391c1805158 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
github.com/joshdk/go-junit v1.0.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/kubepug/kubepug v1.7.1
- github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118124335-9424636d3456
+ github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118132107-2c37729871a3
github.com/minio/minio-go/v7 v7.0.47
github.com/montanaflynn/stats v0.6.6
github.com/moogar0880/problems v0.1.1
diff --git a/go.sum b/go.sum
index e2d3038d64f..98d42309225 100644
--- a/go.sum
+++ b/go.sum
@@ -354,8 +354,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kubepug/kubepug v1.7.1 h1:LKhfSxS8Y5mXs50v+3Lpyec+cogErDLcV7CMUuiaisw=
github.com/kubepug/kubepug v1.7.1/go.mod h1:lv+HxD0oTFL7ZWjj0u6HKhMbbTIId3eG7aWIW0gyF8g=
-github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118124335-9424636d3456 h1:rBJgy8PnkY6mQDUSKl11DJOKTekwwFAKg2CtXC1UIPo=
-github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118124335-9424636d3456/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk=
+github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118132107-2c37729871a3 h1:R6xdH//ctWpE18U1GYwzNvq1HLiT9LUJogXkfyKDDGo=
+github.com/kubeshop/testkube-operator v1.15.2-beta1.0.20240118132107-2c37729871a3/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
From 94ddfe5ba4abf06f958816c1c6e4186e2f3400a9 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Thu, 18 Jan 2024 19:01:55 +0300
Subject: [PATCH 020/234] fix: add gradle home
---
contrib/executor/gradle/build/agent/Dockerfile.jdk11 | 3 +++
contrib/executor/gradle/build/agent/Dockerfile.jdk17 | 3 +++
contrib/executor/gradle/build/agent/Dockerfile.jdk18 | 3 +++
contrib/executor/gradle/build/agent/Dockerfile.jdk8 | 3 +++
4 files changed, 12 insertions(+)
diff --git a/contrib/executor/gradle/build/agent/Dockerfile.jdk11 b/contrib/executor/gradle/build/agent/Dockerfile.jdk11
index ed08d550d1f..8111390e8df 100644
--- a/contrib/executor/gradle/build/agent/Dockerfile.jdk11
+++ b/contrib/executor/gradle/build/agent/Dockerfile.jdk11
@@ -2,6 +2,9 @@
FROM gradle:8.5.0-jdk11
COPY gradle /bin/runner
+RUN chown -R 1001:1001 /home/gradle
+ENV GRADLE_USER_HOME /home/gradle
+
USER 1001
ENTRYPOINT ["/bin/runner"]
diff --git a/contrib/executor/gradle/build/agent/Dockerfile.jdk17 b/contrib/executor/gradle/build/agent/Dockerfile.jdk17
index 074ce893636..41353e9b66b 100644
--- a/contrib/executor/gradle/build/agent/Dockerfile.jdk17
+++ b/contrib/executor/gradle/build/agent/Dockerfile.jdk17
@@ -2,6 +2,9 @@
FROM gradle:8.5.0-jdk17
COPY gradle /bin/runner
+RUN chown -R 1001:1001 /home/gradle
+ENV GRADLE_USER_HOME /home/gradle
+
USER 1001
ENTRYPOINT ["/bin/runner"]
diff --git a/contrib/executor/gradle/build/agent/Dockerfile.jdk18 b/contrib/executor/gradle/build/agent/Dockerfile.jdk18
index 0a21cb34b13..e365ec77b03 100644
--- a/contrib/executor/gradle/build/agent/Dockerfile.jdk18
+++ b/contrib/executor/gradle/build/agent/Dockerfile.jdk18
@@ -2,6 +2,9 @@
FROM gradle:8.5.0-jdk18
COPY gradle /bin/runner
+RUN chown -R 1001:1001 /home/gradle
+ENV GRADLE_USER_HOME /home/gradle
+
USER 1001
ENTRYPOINT ["/bin/runner"]
diff --git a/contrib/executor/gradle/build/agent/Dockerfile.jdk8 b/contrib/executor/gradle/build/agent/Dockerfile.jdk8
index 463101d6afa..546000c545f 100644
--- a/contrib/executor/gradle/build/agent/Dockerfile.jdk8
+++ b/contrib/executor/gradle/build/agent/Dockerfile.jdk8
@@ -2,6 +2,9 @@
FROM gradle:8.5.0-jdk8
COPY gradle /bin/runner
+RUN chown -R 1001:1001 /home/gradle
+ENV GRADLE_USER_HOME /home/gradle
+
USER 1001
ENTRYPOINT ["/bin/runner"]
From f4ffbcf9c07926417c04c00da0592d72f4ab68bc Mon Sep 17 00:00:00 2001
From: Dejan Zele Pejchev
Date: Fri, 19 Jan 2024 13:02:44 +0100
Subject: [PATCH 021/234] add support for skipping cert verification of
presigned put urls (#4915)
---
pkg/cloud/data/artifact/scraper_integration_test.go | 4 ++--
pkg/cloud/data/artifact/uploader.go | 12 +++++++++---
pkg/cloud/data/artifact/uploader_test.go | 4 ++--
pkg/executor/scraper/factory/factory.go | 6 +++---
4 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/pkg/cloud/data/artifact/scraper_integration_test.go b/pkg/cloud/data/artifact/scraper_integration_test.go
index 0eda10921bb..8c132aaddef 100644
--- a/pkg/cloud/data/artifact/scraper_integration_test.go
+++ b/pkg/cloud/data/artifact/scraper_integration_test.go
@@ -61,7 +61,7 @@ func TestCloudScraper_ArchiveFilesystemExtractor_Integration(t *testing.T) {
defer testServer.Close()
mockExecutor := executor.NewMockExecutor(mockCtrl)
- cloudLoader := cloudscraper.NewCloudUploader(mockExecutor)
+ cloudLoader := cloudscraper.NewCloudUploader(mockExecutor, false)
req := &cloudscraper.PutObjectSignedURLRequest{
Object: "artifacts.tar.gz",
ExecutionID: "my-execution-id",
@@ -148,7 +148,7 @@ func TestCloudScraper_RecursiveFilesystemExtractor_Integration(t *testing.T) {
defer testServer.Close()
mockExecutor := executor.NewMockExecutor(mockCtrl)
- cloudLoader := cloudscraper.NewCloudUploader(mockExecutor)
+ cloudLoader := cloudscraper.NewCloudUploader(mockExecutor, false)
req1 := &cloudscraper.PutObjectSignedURLRequest{
Object: "file1.txt",
ExecutionID: "my-execution-id",
diff --git a/pkg/cloud/data/artifact/uploader.go b/pkg/cloud/data/artifact/uploader.go
index fa1f522de1c..b7a34c70805 100644
--- a/pkg/cloud/data/artifact/uploader.go
+++ b/pkg/cloud/data/artifact/uploader.go
@@ -2,6 +2,7 @@ package artifact
import (
"context"
+ "crypto/tls"
"encoding/json"
"io"
"net/http"
@@ -18,10 +19,12 @@ import (
type CloudUploader struct {
executor executor.Executor
+ // skipVerify is used to skip TLS verification when artifacts
+ skipVerify bool
}
-func NewCloudUploader(executor executor.Executor) *CloudUploader {
- return &CloudUploader{executor: executor}
+func NewCloudUploader(executor executor.Executor, skipVerify bool) *CloudUploader {
+ return &CloudUploader{executor: executor, skipVerify: skipVerify}
}
func (u *CloudUploader) Upload(ctx context.Context, object *scraper.Object, execution testkube.Execution) error {
@@ -63,7 +66,10 @@ func (u *CloudUploader) putObject(ctx context.Context, url string, data io.Reade
return err
}
req.Header.Set("Content-Type", "application/octet-stream")
- rsp, err := http.DefaultClient.Do(req)
+ tr := http.DefaultTransport.(*http.Transport).Clone()
+ tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: u.skipVerify}
+ client := &http.Client{Transport: tr}
+ rsp, err := client.Do(req)
if err != nil {
return errors.Wrap(err, "failed to send file to cloud")
}
diff --git a/pkg/cloud/data/artifact/uploader_test.go b/pkg/cloud/data/artifact/uploader_test.go
index 3b10730ba6f..2a78448920d 100644
--- a/pkg/cloud/data/artifact/uploader_test.go
+++ b/pkg/cloud/data/artifact/uploader_test.go
@@ -60,7 +60,7 @@ func TestCloudLoader_Load(t *testing.T) {
}
mockExecutor.EXPECT().Execute(gomock.Any(), cloudscraper.CmdScraperPutObjectSignedURL, gomock.Eq(req)).Return([]byte(`{"URL":"`+testServer.URL+`/dummy"}`), nil).Times(1)
- return cloudscraper.NewCloudUploader(mockExecutor)
+ return cloudscraper.NewCloudUploader(mockExecutor, false)
},
putErr: nil,
wantErr: false,
@@ -82,7 +82,7 @@ func TestCloudLoader_Load(t *testing.T) {
}
mockExecutor.EXPECT().Execute(gomock.Any(), cloudscraper.CmdScraperPutObjectSignedURL, gomock.Eq(req)).Return(nil, errors.New("connection error")).Times(1)
- return cloudscraper.NewCloudUploader(mockExecutor)
+ return cloudscraper.NewCloudUploader(mockExecutor, false)
},
wantErr: true,
errContains: "failed to get signed URL for object [my-object]: connection error",
diff --git a/pkg/executor/scraper/factory/factory.go b/pkg/executor/scraper/factory/factory.go
index e9b95968a7e..dc402468e2b 100644
--- a/pkg/executor/scraper/factory/factory.go
+++ b/pkg/executor/scraper/factory/factory.go
@@ -100,8 +100,8 @@ func getRemoteStorageUploader(ctx context.Context, params envs.Params) (uploader
defer cancel()
output.PrintLogf(
- "%s Uploading artifacts using Remote Storage Uploader (timeout:%ds, insecure:%v, skipVerify: %v, url: %s)",
- ui.IconCheckMark, params.CloudConnectionTimeoutSec, params.CloudAPITLSInsecure, params.CloudAPISkipVerify, params.CloudAPIURL)
+ "%s Uploading artifacts using Remote Storage Uploader (timeout:%ds, agentInsecure:%v, agentSkipVerify: %v, url: %s, scraperSkipVerify: %v)",
+ ui.IconCheckMark, params.CloudConnectionTimeoutSec, params.CloudAPITLSInsecure, params.CloudAPISkipVerify, params.CloudAPIURL, params.SkipVerify)
grpcConn, err := agent.NewGRPCConnection(ctxTimeout, params.CloudAPITLSInsecure, params.CloudAPISkipVerify, params.CloudAPIURL, log.DefaultLogger)
if err != nil {
return nil, err
@@ -110,7 +110,7 @@ func getRemoteStorageUploader(ctx context.Context, params envs.Params) (uploader
grpcClient := cloud.NewTestKubeCloudAPIClient(grpcConn)
cloudExecutor := cloudexecutor.NewCloudGRPCExecutor(grpcClient, grpcConn, params.CloudAPIKey)
- return cloudscraper.NewCloudUploader(cloudExecutor), nil
+ return cloudscraper.NewCloudUploader(cloudExecutor, params.SkipVerify), nil
}
func getMinIOUploader(params envs.Params) (*scraper.MinIOUploader, error) {
From 9f5ccc5888e83a9f0ebf44cf5b8748e93f864587 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Mon, 22 Jan 2024 08:31:39 +0100
Subject: [PATCH 022/234] fix: trigger event for logs start (#4916)
* feat: trigger logs startstop events
* fix: single NATS construct func
---
cmd/api-server/main.go | 26 +++++--------
cmd/logs/main.go | 11 +++++-
cmd/sidecar/main.go | 10 ++++-
internal/config/config.go | 1 +
pkg/event/bus/nats.go | 56 +++++++++++++++++++++++----
pkg/event/emitter_integration_test.go | 5 ++-
pkg/logs/config/logs_config.go | 20 +++++++---
pkg/scheduler/service.go | 4 +-
pkg/scheduler/test_scheduler.go | 46 ++++++++++++++++++++++
pkg/triggers/executor_test.go | 2 +-
pkg/triggers/service_test.go | 2 +-
11 files changed, 145 insertions(+), 38 deletions(-)
diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go
index 86cd6ff8eb1..b7aa3fc7402 100644
--- a/cmd/api-server/main.go
+++ b/cmd/api-server/main.go
@@ -2,7 +2,6 @@ package main
import (
"context"
- "crypto/tls"
"encoding/json"
"flag"
"fmt"
@@ -634,22 +633,15 @@ func parseDefaultExecutors(cfg *config.Config) (executors []testkube.ExecutorDet
}
func newNATSConnection(cfg *config.Config) (*nats.EncodedConn, error) {
- var opts []nats.Option
- if cfg.NatsSecure {
- if cfg.NatsSkipVerify {
- opts = append(opts, nats.Secure(&tls.Config{InsecureSkipVerify: true}))
- } else {
- opts = append(opts, nats.ClientCert(cfg.NatsCertFile, cfg.NatsKeyFile))
- if cfg.NatsCAFile != "" {
- opts = append(opts, nats.RootCAs(cfg.NatsCAFile))
- }
- }
- }
- nc, err := bus.NewNATSEncoddedConnection(cfg.NatsURI, opts...)
- if err != nil {
- log.DefaultLogger.Errorw("error creating NATS connection", "error", err)
- }
- return nc, nil
+ return bus.NewNATSEncodedConnection(bus.ConnectionConfig{
+ NatsURI: cfg.NatsURI,
+ NatsSecure: cfg.NatsSecure,
+ NatsSkipVerify: cfg.NatsSkipVerify,
+ NatsCertFile: cfg.NatsCertFile,
+ NatsKeyFile: cfg.NatsKeyFile,
+ NatsCAFile: cfg.NatsCAFile,
+ NatsConnectTimeout: cfg.NatsConnectTimeout,
+ })
}
func newStorageClient(cfg *config.Config) *minio.Client {
diff --git a/cmd/logs/main.go b/cmd/logs/main.go
index 3f23ba3a2e4..c642ad5d467 100644
--- a/cmd/logs/main.go
+++ b/cmd/logs/main.go
@@ -3,6 +3,7 @@ package main
import (
"context"
"errors"
+
"os"
"os/signal"
"syscall"
@@ -30,7 +31,15 @@ func main() {
cfg := Must(config.Get())
// Event bus
- nc := Must(bus.NewNATSConnection(cfg.NatsURI))
+ nc := Must(bus.NewNATSConnection(bus.ConnectionConfig{
+ NatsURI: cfg.NatsURI,
+ NatsSecure: cfg.NatsSecure,
+ NatsSkipVerify: cfg.NatsSkipVerify,
+ NatsCertFile: cfg.NatsCertFile,
+ NatsKeyFile: cfg.NatsKeyFile,
+ NatsCAFile: cfg.NatsCAFile,
+ NatsConnectTimeout: cfg.NatsConnectTimeout,
+ }))
defer func() {
log.Infof("closing nats connection")
nc.Close()
diff --git a/cmd/sidecar/main.go b/cmd/sidecar/main.go
index fab7ec5f0ff..6f069b8c20b 100644
--- a/cmd/sidecar/main.go
+++ b/cmd/sidecar/main.go
@@ -23,7 +23,15 @@ func main() {
cfg := Must(config.Get())
// Event bus
- nc := Must(bus.NewNATSConnection(cfg.NatsURI))
+ nc := Must(bus.NewNATSConnection(bus.ConnectionConfig{
+ NatsURI: cfg.NatsURI,
+ NatsSecure: cfg.NatsSecure,
+ NatsSkipVerify: cfg.NatsSkipVerify,
+ NatsCertFile: cfg.NatsCertFile,
+ NatsKeyFile: cfg.NatsKeyFile,
+ NatsCAFile: cfg.NatsCAFile,
+ NatsConnectTimeout: cfg.NatsConnectTimeout,
+ }))
defer func() {
log.Infof("closing nats connection")
nc.Close()
diff --git a/internal/config/config.go b/internal/config/config.go
index 01d1f9c9bb7..910796c3755 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -43,6 +43,7 @@ type Config struct {
NatsCertFile string `envconfig:"NATS_CERT_FILE" default:""`
NatsKeyFile string `envconfig:"NATS_KEY_FILE" default:""`
NatsCAFile string `envconfig:"NATS_CA_FILE" default:""`
+ NatsConnectTimeout time.Duration `envconfig:"NATS_CONNECT_TIMEOUT" default:"5s"`
JobServiceAccountName string `envconfig:"JOB_SERVICE_ACCOUNT_NAME" default:""`
JobTemplateFile string `envconfig:"JOB_TEMPLATE_FILE" default:""`
DisableTestTriggers bool `envconfig:"DISABLE_TEST_TRIGGERS" default:"false"`
diff --git a/pkg/event/bus/nats.go b/pkg/event/bus/nats.go
index 87e8dbd852a..e1ff85cdbcf 100644
--- a/pkg/event/bus/nats.go
+++ b/pkg/event/bus/nats.go
@@ -1,8 +1,10 @@
package bus
import (
+ "crypto/tls"
"fmt"
"sync"
+ "time"
"github.com/nats-io/nats.go"
@@ -22,18 +24,40 @@ const (
InternalSubscribeTopic = "internal.>"
)
-func NewNATSConnection(uri string, opts ...nats.Option) (*nats.Conn, error) {
- nc, err := nats.Connect(uri, opts...)
- if err != nil {
- log.DefaultLogger.Fatalw("error connecting to nats", "error", err)
- return nil, err
+type ConnectionConfig struct {
+ NatsURI string
+ NatsSecure bool
+ NatsSkipVerify bool
+ NatsCertFile string
+ NatsKeyFile string
+ NatsCAFile string
+ NatsConnectTimeout time.Duration
+}
+
+func optsFromConfig(cfg ConnectionConfig) (opts []nats.Option) {
+ opts = []nats.Option{}
+ if cfg.NatsSecure {
+ if cfg.NatsSkipVerify {
+ opts = append(opts, nats.Secure(&tls.Config{InsecureSkipVerify: true}))
+ } else {
+ opts = append(opts, nats.ClientCert(cfg.NatsCertFile, cfg.NatsKeyFile))
+ if cfg.NatsCAFile != "" {
+ opts = append(opts, nats.RootCAs(cfg.NatsCAFile))
+ }
+ }
}
- return nc, nil
+ if cfg.NatsConnectTimeout > 0 {
+ opts = append(opts, nats.Timeout(cfg.NatsConnectTimeout))
+ }
+
+ return opts
}
-func NewNATSEncoddedConnection(uri string, opts ...nats.Option) (*nats.EncodedConn, error) {
- nc, err := nats.Connect(uri, opts...)
+func NewNATSEncodedConnection(cfg ConnectionConfig, opts ...nats.Option) (*nats.EncodedConn, error) {
+ opts = append(opts, optsFromConfig(cfg)...)
+
+ nc, err := nats.Connect(cfg.NatsURI, opts...)
if err != nil {
log.DefaultLogger.Fatalw("error connecting to nats", "error", err)
return nil, err
@@ -46,9 +70,25 @@ func NewNATSEncoddedConnection(uri string, opts ...nats.Option) (*nats.EncodedCo
return nil, err
}
+ if err != nil {
+ log.DefaultLogger.Errorw("error creating NATS connection", "error", err)
+ }
+
return ec, nil
}
+func NewNATSConnection(cfg ConnectionConfig, opts ...nats.Option) (*nats.Conn, error) {
+ opts = append(opts, optsFromConfig(cfg)...)
+
+ nc, err := nats.Connect(cfg.NatsURI, opts...)
+ if err != nil {
+ log.DefaultLogger.Fatalw("error connecting to nats", "error", err)
+ return nil, err
+ }
+
+ return nc, nil
+}
+
func NewNATSBus(nc *nats.EncodedConn) *NATSBus {
return &NATSBus{
nc: nc,
diff --git a/pkg/event/emitter_integration_test.go b/pkg/event/emitter_integration_test.go
index a15a5e1c23d..b699e5ede6e 100644
--- a/pkg/event/emitter_integration_test.go
+++ b/pkg/event/emitter_integration_test.go
@@ -20,7 +20,10 @@ import (
func GetTestNATSEmitter() *Emitter {
os.Setenv("DEBUG", "true")
// configure NATS event bus
- nc, err := bus.NewNATSEncoddedConnection("http://localhost:4222")
+ nc, err := bus.NewNATSEncodedConnection(bus.ConnectionConfig{
+ NatsURI: "http://localhost:4222",
+ })
+
if err != nil {
panic(err)
}
diff --git a/pkg/logs/config/logs_config.go b/pkg/logs/config/logs_config.go
index 3296297d520..d776cdf7181 100644
--- a/pkg/logs/config/logs_config.go
+++ b/pkg/logs/config/logs_config.go
@@ -1,16 +1,24 @@
package config
import (
+ "time"
+
"github.com/kelseyhightower/envconfig"
)
type Config struct {
- NatsURI string `envconfig:"NATS_URI" default:"nats://localhost:4222"`
- Namespace string `envconfig:"NAMESPACE" default:"testkube"`
- ExecutionId string `envconfig:"ID" default:""`
- HttpAddress string `envconfig:"HTTP_ADDRESS" default:":8080"`
- GrpcAddress string `envconfig:"GRPC_ADDRESS" default:":9090"`
- KVBucketName string `envconfig:"KV_BUCKET_NAME" default:"logsState"`
+ NatsURI string `envconfig:"NATS_URI" default:"nats://localhost:4222"`
+ NatsSecure bool `envconfig:"NATS_SECURE" default:"false"`
+ NatsSkipVerify bool `envconfig:"NATS_SKIP_VERIFY" default:"false"`
+ NatsCertFile string `envconfig:"NATS_CERT_FILE" default:""`
+ NatsKeyFile string `envconfig:"NATS_KEY_FILE" default:""`
+ NatsCAFile string `envconfig:"NATS_CA_FILE" default:""`
+ NatsConnectTimeout time.Duration `envconfig:"NATS_CONNECT_TIMEOUT" default:"5s"`
+ Namespace string `envconfig:"NAMESPACE" default:"testkube"`
+ ExecutionId string `envconfig:"ID" default:""`
+ HttpAddress string `envconfig:"HTTP_ADDRESS" default:":8080"`
+ GrpcAddress string `envconfig:"GRPC_ADDRESS" default:":9090"`
+ KVBucketName string `envconfig:"KV_BUCKET_NAME" default:"logsState"`
}
func Get() (*Config, error) {
diff --git a/pkg/scheduler/service.go b/pkg/scheduler/service.go
index 349c1d8c688..69818463630 100644
--- a/pkg/scheduler/service.go
+++ b/pkg/scheduler/service.go
@@ -41,7 +41,7 @@ type Scheduler struct {
eventsBus bus.Bus
dashboardURI string
featureFlags featureflags.FeatureFlags
- logsStream logsclient.InitializedStreamPusher
+ logsStream logsclient.Stream
}
func NewScheduler(
@@ -63,7 +63,7 @@ func NewScheduler(
eventsBus bus.Bus,
dashboardURI string,
featureFlags featureflags.FeatureFlags,
- logsStream logsclient.InitializedStreamPusher,
+ logsStream logsclient.Stream,
) *Scheduler {
return &Scheduler{
metrics: metrics,
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index 11aaa82bbf8..630388b3409 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -58,6 +58,16 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
// test name + test execution name should be unique
execution, _ = s.testResults.GetByNameAndTest(ctx, request.Name, test.Name)
+
+ // for logs.v2 service trigger start / stop events
+ if s.featureFlags.LogsV2 {
+ err := s.triggerLogsStartEvent(ctx, execution.Id)
+ if err != nil {
+ return execution, err
+ }
+ defer s.triggerLogsStopEvent(ctx, execution.Id)
+ }
+
if execution.Name == request.Name {
err := errors.Errorf("test execution with name %s already exists", request.Name)
return s.handleExecutionError(ctx, execution, "duplicate execution: %w", err)
@@ -127,6 +137,7 @@ func (s *Scheduler) handleExecutionError(ctx context.Context, execution testkube
WithSource("test-scheduler")
s.logsStream.Push(ctx, execution.Id, *l)
+
}
// notify events that execution failed
@@ -808,3 +819,38 @@ func mergeSlavePodRequests(podBase *testkube.PodRequest, podAdjust *testkube.Pod
return podBase
}
+
+func (s *Scheduler) triggerLogsStartEvent(ctx context.Context, id string) error {
+ if s.featureFlags.LogsV2 {
+ r, err := s.logsStream.Start(ctx, id)
+ if err != nil {
+ return err
+ }
+
+ if r.Error {
+ return errors.New(string(r.Message))
+ }
+
+ s.logger.Infow("triggering logs start event", "id", id)
+ }
+
+ return nil
+}
+
+func (s *Scheduler) triggerLogsStopEvent(ctx context.Context, id string) error {
+ if s.featureFlags.LogsV2 {
+ r, err := s.logsStream.Stop(ctx, id)
+ if err != nil {
+ s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
+ return err
+ }
+
+ if r.Error {
+ s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
+ return err
+ }
+
+ s.logger.Infow("triggering logs stop event", "id", id)
+ }
+ return nil
+}
diff --git a/pkg/triggers/executor_test.go b/pkg/triggers/executor_test.go
index dce098cbd69..3c136a7e615 100644
--- a/pkg/triggers/executor_test.go
+++ b/pkg/triggers/executor_test.go
@@ -104,7 +104,7 @@ func TestExecute(t *testing.T) {
mockExecutor.EXPECT().Execute(gomock.Any(), gomock.Any(), gomock.Any()).Return(&mockExecutionResult, nil)
mockResultRepository.EXPECT().UpdateResult(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
- mockLogsStream := logsclient.NewMockInitializedStreamPusher(mockCtrl)
+ mockLogsStream := logsclient.NewMockStream(mockCtrl)
sched := scheduler.NewScheduler(
metricsHandle,
diff --git a/pkg/triggers/service_test.go b/pkg/triggers/service_test.go
index 7c847f32e4d..b0b65fa04b6 100644
--- a/pkg/triggers/service_test.go
+++ b/pkg/triggers/service_test.go
@@ -117,7 +117,7 @@ func TestService_Run(t *testing.T) {
testLogger := log.DefaultLogger
- mockLogsStream := logsclient.NewMockInitializedStreamPusher(mockCtrl)
+ mockLogsStream := logsclient.NewMockStream(mockCtrl)
sched := scheduler.NewScheduler(
testMetrics,
From f0df49fcce5c2b9f9876f860ed273462dcef0e50 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Mon, 22 Jan 2024 09:44:02 +0100
Subject: [PATCH 023/234] fix: use parametrized nats connection when creating
encoded one (#4918)
* feat: trigger logs startstop events
* fix: single NATS construct func
* fix: added nats conn with valid opts
---
pkg/event/bus/nats.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/event/bus/nats.go b/pkg/event/bus/nats.go
index e1ff85cdbcf..484b9b4a14c 100644
--- a/pkg/event/bus/nats.go
+++ b/pkg/event/bus/nats.go
@@ -57,7 +57,7 @@ func optsFromConfig(cfg ConnectionConfig) (opts []nats.Option) {
func NewNATSEncodedConnection(cfg ConnectionConfig, opts ...nats.Option) (*nats.EncodedConn, error) {
opts = append(opts, optsFromConfig(cfg)...)
- nc, err := nats.Connect(cfg.NatsURI, opts...)
+ nc, err := NewNATSConnection(cfg, opts...)
if err != nil {
log.DefaultLogger.Fatalw("error connecting to nats", "error", err)
return nil, err
From d9c77cd7b281c8a784ef1925c6c18c3cd38ace17 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 22 Jan 2024 10:43:52 +0100
Subject: [PATCH 024/234] build: bump follow-redirects from 1.15.1 to 1.15.4 in
/docs (#4868)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.1 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.1...v1.15.4)
---
updated-dependencies:
- dependency-name: follow-redirects
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
docs/package-lock.json | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 7c5ed49d3d0..5e36284d6ba 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -6712,16 +6712,15 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
- "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+ "version": "1.15.4",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
+ "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
- "license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -18513,9 +18512,9 @@
}
},
"follow-redirects": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
- "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
+ "version": "1.15.4",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
+ "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw=="
},
"foreach": {
"version": "2.0.6",
From 59253612df15c1e20254ab5af413583bf192515d Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Fri, 19 Jan 2024 19:25:53 +0300
Subject: [PATCH 025/234] fix: support missing run path
---
contrib/executor/k6/pkg/runner/runner.go | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/contrib/executor/k6/pkg/runner/runner.go b/contrib/executor/k6/pkg/runner/runner.go
index e80b203bb93..854f91671a0 100644
--- a/contrib/executor/k6/pkg/runner/runner.go
+++ b/contrib/executor/k6/pkg/runner/runner.go
@@ -110,6 +110,7 @@ func (r *K6Runner) Run(ctx context.Context, execution testkube.Execution) (resul
// in case of Git directory we will run k6 here and
// use the last argument as test file
+ changedArgs := false
if execution.Content.Type_ == string(testkube.TestContentTypeGitFile) ||
execution.Content.Type_ == string(testkube.TestContentTypeGitDir) ||
execution.Content.Type_ == string(testkube.TestContentTypeGit) {
@@ -130,7 +131,7 @@ func (r *K6Runner) Run(ctx context.Context, execution testkube.Execution) (resul
if fileInfo.IsDir() {
testPath = filepath.Join(path, args[len(args)-1])
args = append(args[:len(args)-1], args[len(args):]...)
-
+ changedArgs = true
} else {
testPath = path
}
@@ -144,6 +145,7 @@ func (r *K6Runner) Run(ctx context.Context, execution testkube.Execution) (resul
}
}
+ hasRunPath := false
for i := range args {
if args[i] == "" {
args[i] = k6Command
@@ -151,9 +153,14 @@ func (r *K6Runner) Run(ctx context.Context, execution testkube.Execution) (resul
if args[i] == "" {
args[i] = testPath
+ hasRunPath = true
}
}
+ if changedArgs && !hasRunPath {
+ args = append(args, testPath)
+ }
+
for i := range args {
if args[i] == "" {
newArgs := make([]string, len(args)+len(envVars)-1)
From 21b498f162d00d30cc625a55eef1da3058ad776c Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Mon, 22 Jan 2024 15:18:22 +0100
Subject: [PATCH 026/234] fix: execution id not passed to events (#4920)
---
pkg/logs/events.go | 2 +-
pkg/scheduler/test_scheduler.go | 25 +++++++++++++------------
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/pkg/logs/events.go b/pkg/logs/events.go
index 18278bfa1ce..33f2719a5a9 100644
--- a/pkg/logs/events.go
+++ b/pkg/logs/events.go
@@ -199,7 +199,7 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
if len(toDelete) == 0 {
ls.state.Put(ctx, event.Id, state.LogStateFinished)
- l.Infow("all logs consumers stopped", "id", event.Id)
+ l.Infow("execution logs consumers stopped", "id", event.Id)
err = msg.Respond([]byte("stopped"))
if err != nil {
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index 630388b3409..ee37d7cff00 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -54,20 +54,9 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
request.Name = fmt.Sprintf("%s-%d", request.Name, request.Number)
}
- s.events.Notify(testkube.NewEventStartTest(&execution))
-
// test name + test execution name should be unique
execution, _ = s.testResults.GetByNameAndTest(ctx, request.Name, test.Name)
- // for logs.v2 service trigger start / stop events
- if s.featureFlags.LogsV2 {
- err := s.triggerLogsStartEvent(ctx, execution.Id)
- if err != nil {
- return execution, err
- }
- defer s.triggerLogsStopEvent(ctx, execution.Id)
- }
-
if execution.Name == request.Name {
err := errors.Errorf("test execution with name %s already exists", request.Name)
return s.handleExecutionError(ctx, execution, "duplicate execution: %w", err)
@@ -89,6 +78,18 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
execution = newExecutionFromExecutionOptions(options)
options.ID = execution.Id
+ // TODO consider using single event for test start and logs
+ s.events.Notify(testkube.NewEventStartTest(&execution))
+
+ // for logs.v2 service trigger start / stop events
+ if s.featureFlags.LogsV2 {
+ err := s.triggerLogsStartEvent(ctx, execution.Id)
+ if err != nil {
+ return execution, err
+ }
+ defer s.triggerLogsStopEvent(ctx, execution.Id)
+ }
+
if err := s.createSecretsReferences(&execution); err != nil {
return s.handleExecutionError(ctx, execution, "can't create secret variables `Secret` references: %w", err)
}
@@ -98,7 +99,7 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
return s.handleExecutionError(ctx, execution, "can't create new test execution, can't insert into storage: %w", err)
}
- s.logger.Infow("calling executor with options", "options", options.Request)
+ s.logger.Infow("calling executor with options", "executionId", execution.Id, "options", options.Request)
execution.Start()
From 80c9807e143f5ac524efbf1377d5f7e84d9bd657 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Tue, 23 Jan 2024 08:22:07 +0100
Subject: [PATCH 027/234] fix: added cooldown time to not stop events too early
(#4921)
* fix: execution id not passed to events
* fix: async stop
* fix: rollback to debug on message
* fix: context aware stop wait time
* fix: tests fixed with stop wait time
---
pkg/logs/events.go | 20 ++++++++++++++++----
pkg/logs/events_test.go | 12 +++++++++---
pkg/logs/service.go | 12 ++++++++++++
pkg/scheduler/test_scheduler.go | 23 +++++++++++++----------
4 files changed, 50 insertions(+), 17 deletions(-)
diff --git a/pkg/logs/events.go b/pkg/logs/events.go
index 33f2719a5a9..2e92aae3da1 100644
--- a/pkg/logs/events.go
+++ b/pkg/logs/events.go
@@ -147,6 +147,13 @@ func (ls *LogsService) handleStart(ctx context.Context) func(msg *nats.Msg) {
// handleStop will handle stop event and stop logs consumers, also clean consumers state
func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
return func(msg *nats.Msg) {
+ ls.log.Debugw("got stop event")
+
+ t := time.NewTicker(ls.stopWaitTime)
+ select {
+ case <-t.C:
+ case <-ctx.Done():
+ }
event := events.Trigger{}
err := json.Unmarshal(msg.Data, &event)
@@ -161,6 +168,7 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
repeated := 0
toDelete := []string{}
+ deleted := false
for _, adapter := range ls.adapters {
toDelete = append(toDelete, event.Id+"_"+adapter.Name())
}
@@ -174,7 +182,7 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
// load consumer and check if has pending messages
c, found := ls.consumerInstances.Load(name)
if !found {
- l.Warnw("consumer not found", "found", found, "name", name)
+ l.Debugw("consumer not found on this pod", "found", found, "name", name)
toDelete = append(toDelete[:i], toDelete[i+1:]...)
goto loop // rewrite toDelete and start again
}
@@ -189,6 +197,9 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
// finally delete consumer
if info.NumPending == 0 {
+ if !deleted {
+ deleted = true
+ }
consumer.Context.Stop()
ls.consumerInstances.Delete(name)
toDelete = append(toDelete[:i], toDelete[i+1:]...)
@@ -197,16 +208,17 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
}
}
- if len(toDelete) == 0 {
+ if len(toDelete) == 0 && !deleted {
+ l.Debugw("no consumers on this pod registered for id", "id", event.Id)
+ return
+ } else if len(toDelete) == 0 {
ls.state.Put(ctx, event.Id, state.LogStateFinished)
l.Infow("execution logs consumers stopped", "id", event.Id)
-
err = msg.Respond([]byte("stopped"))
if err != nil {
l.Errorw("error responding to stop event", "error", err)
return
}
-
return
}
diff --git a/pkg/logs/events_test.go b/pkg/logs/events_test.go
index 879dcb06c87..68c084d644a 100644
--- a/pkg/logs/events_test.go
+++ b/pkg/logs/events_test.go
@@ -17,6 +17,8 @@ import (
"github.com/kubeshop/testkube/pkg/logs/state"
)
+var waitTime = time.Millisecond * 100
+
func TestLogs_EventsFlow(t *testing.T) {
t.Parallel()
@@ -43,7 +45,8 @@ func TestLogs_EventsFlow(t *testing.T) {
// and initialized log service
log := NewLogsService(nc, js, state).
- WithRandomPort()
+ WithRandomPort().
+ WithStopWaitTime(waitTime)
// given example adapters
a := NewMockAdapter("aaa")
@@ -76,6 +79,7 @@ func TestLogs_EventsFlow(t *testing.T) {
// and when data pushed to the log stream
stream.Push(ctx, "stop-test", events.NewLogResponse(time.Now(), []byte("hello 1")))
+ stream.Push(ctx, "stop-test", events.NewLogResponse(time.Now(), []byte("hello 2")))
// and stop event triggered
_, err = stream.Stop(ctx, "stop-test")
@@ -108,7 +112,8 @@ func TestLogs_EventsFlow(t *testing.T) {
// and initialized log service
log := NewLogsService(nc, js, state).
- WithRandomPort()
+ WithRandomPort().
+ WithStopWaitTime(waitTime)
// given example adapter
a := NewMockAdapter()
@@ -179,7 +184,8 @@ func TestLogs_EventsFlow(t *testing.T) {
// and initialized log service
log := NewLogsService(nc, js, state).
- WithRandomPort()
+ WithRandomPort().
+ WithStopWaitTime(waitTime)
// given example adapters
a := NewMockAdapter("aaa")
diff --git a/pkg/logs/service.go b/pkg/logs/service.go
index 98013158067..b710d4f9b9a 100644
--- a/pkg/logs/service.go
+++ b/pkg/logs/service.go
@@ -11,6 +11,7 @@ import (
"net"
"net/http"
"sync"
+ "time"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -27,6 +28,8 @@ import (
const (
DefaultHttpAddress = ":8080"
DefaultGrpcAddress = ":9090"
+
+ DefaultStopWaitTime = 60 * time.Second // when stop event is faster than first message arrived
)
func NewLogsService(nats *nats.Conn, js jetstream.JetStream, state state.Interface) *LogsService {
@@ -40,6 +43,7 @@ func NewLogsService(nats *nats.Conn, js jetstream.JetStream, state state.Interfa
grpcAddress: DefaultGrpcAddress,
consumerInstances: sync.Map{},
state: state,
+ stopWaitTime: DefaultStopWaitTime,
}
}
@@ -70,6 +74,9 @@ type LogsService struct {
// will allow to distiguish from where load data from in OSS
// cloud will be loading always them locally
state state.Interface
+
+ // stop wait time for messages cool down
+ stopWaitTime time.Duration
}
// AddAdapter adds new adapter to logs service adapters will be configred based on given mode
@@ -142,6 +149,11 @@ func (ls *LogsService) WithGrpcAddress(address string) *LogsService {
return ls
}
+func (ls *LogsService) WithStopWaitTime(duration time.Duration) *LogsService {
+ ls.stopWaitTime = duration
+ return ls
+}
+
func (ls *LogsService) WithRandomPort() *LogsService {
port := rand.Intn(1000) + 17000
ls.httpAddress = fmt.Sprintf("127.0.0.1:%d", port)
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index ee37d7cff00..43ca934cccc 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -840,18 +840,21 @@ func (s *Scheduler) triggerLogsStartEvent(ctx context.Context, id string) error
func (s *Scheduler) triggerLogsStopEvent(ctx context.Context, id string) error {
if s.featureFlags.LogsV2 {
- r, err := s.logsStream.Stop(ctx, id)
- if err != nil {
- s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
- return err
- }
+ // as Stop is synchro
+ go func() {
+ r, err := s.logsStream.Stop(ctx, id)
+ if err != nil {
+ s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
+ return
+ }
- if r.Error {
- s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
- return err
- }
+ if r.Error {
+ s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
+ return
+ }
- s.logger.Infow("triggering logs stop event", "id", id)
+ s.logger.Infow("triggering logs stop event", "id", id)
+ }()
}
return nil
}
From b56be732cd6865eb7ba471b3fce831b8c31f9bc9 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Tue, 23 Jan 2024 08:22:23 +0100
Subject: [PATCH 028/234] fix: smaller docker file (#4919)
---
build/sidecar/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build/sidecar/Dockerfile b/build/sidecar/Dockerfile
index 8642df1803b..a2618e91bb7 100644
--- a/build/sidecar/Dockerfile
+++ b/build/sidecar/Dockerfile
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
ARG ALPINE_IMAGE
FROM ${ALPINE_IMAGE}
-RUN apk --no-cache add ca-certificates libssl1.1 git skopeo
+RUN apk --no-cache add ca-certificates libssl1.1
WORKDIR /root/
COPY testkube-logs-sidecar /bin/app
USER 1001
From e6286c278227cb06fa661daad5895eb2d1b2d029 Mon Sep 17 00:00:00 2001
From: Catalin <20538711+devcatalin@users.noreply.github.com>
Date: Tue, 23 Jan 2024 11:41:46 +0200
Subject: [PATCH 029/234] docs: add CircleCI article (#4885)
* docs: add CircleCI article
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/circleci.md
Co-authored-by: Julianne Fermi
---------
Co-authored-by: Julianne Fermi
---
docs/docs/articles/cicd-overview.md | 1 +
docs/docs/articles/circleci.md | 197 ++++++++++++++++++++++++++++
2 files changed, 198 insertions(+)
create mode 100644 docs/docs/articles/circleci.md
diff --git a/docs/docs/articles/cicd-overview.md b/docs/docs/articles/cicd-overview.md
index 32aa3d1a3aa..45142cbd97d 100644
--- a/docs/docs/articles/cicd-overview.md
+++ b/docs/docs/articles/cicd-overview.md
@@ -8,6 +8,7 @@ We have different tutorials for the options of being CI driven or using GitOps a
- [Github Actions - running Testkube CLI commands with setup-testkube-action](./github-actions.md)
- [Testkube Docker CLI](./testkube-cli-docker.md)
- [Gitlab CI](./gitlab.md)
+- [CircleCI](./circleci.md)
- [GitOps Testing](./gitops-overview.md)
- [Flux](./flux-integration.md)
- [ArgoCD](./argocd-integration.md)
diff --git a/docs/docs/articles/circleci.md b/docs/docs/articles/circleci.md
new file mode 100644
index 00000000000..02236401c71
--- /dev/null
+++ b/docs/docs/articles/circleci.md
@@ -0,0 +1,197 @@
+# Testkube CircleCI
+
+The Testkube CircleCI integration facilitates the installation of Testkube and allows the execution of any [Testkube CLI](https://docs.testkube.io/cli/testkube) command within a CircleCI pipeline. This integration can be seamlessly incorporated into your CircleCI repositories to enhance your CI/CD workflows.
+The integration offers a versatile approach to align with your pipeline requirements and is compatible with Testkube Pro, Testkube Enterprise, and the open-source Testkube platform. It enables CircleCI users to leverage the powerful features of Testkube directly within their CI/CD pipelines, ensuring efficient and flexible test execution.
+
+## Testkube Pro
+
+### How to configure Testkube CLI action for Testkube Pro and run a test
+
+To use CircleCI for [Testkube Pro](https://app.testkube.io/), you need to create an [API token](https://docs.testkube.io/testkube-pro/articles/organization-management/#api-tokens).
+Then, pass the **organization** and **environment** IDs, along with the **token** and other parameters specific for your use case.
+
+If a test is already created, you can run it using the command `testkube run test test-name -f` . However, if you need to create a test in this workflow, please add a creation command, e.g.: `testkube create test --name test-name --file path_to_file.json`.
+
+```yaml
+version: 2.1
+
+jobs:
+ run-tests:
+ docker:
+ - image: kubeshop/testkube-cli
+ working_directory: /.testkube
+ environment:
+ TESTKUBE_API_KEY: tkcapi_0123456789abcdef0123456789abcd
+ TESTKUBE_ORG_ID: tkcorg_0123456789abcdef
+ TESTKUBE_ENV_ID: tkcenv_fedcba9876543210
+ steps:
+ - run:
+ name: "Set Testkube Context"
+ command: "testkube set context --api-key $TESTKUBE_API_KEY --org $TESTKUBE_ORG_ID --env $TESTKUBE_ENV_ID --cloud-root-domain testkube.dev"
+ - run:
+ name: "Trigger testkube test"
+ command: "testkube run test test-name -f"
+
+workflows:
+ run-tests-workflow:
+ jobs:
+ - run-tests
+```
+
+It is recommended that sensitive values should never be stored as plaintext in workflow files, but rather as [project variables](https://circleci.com/docs/set-environment-variable/#set-an-environment-variable-in-a-project). Secrets can be configured at the organization or project level and allow you to store sensitive information in CircleCI.
+
+```yaml
+version: 2.1
+
+jobs:
+ run-tests:
+ docker:
+ - image: kubeshop/testkube-cli
+ working_directory: /.testkube
+ steps:
+ - run:
+ name: "Set Testkube Context"
+ command: "testkube set context --api-key $TESTKUBE_API_KEY --org $TESTKUBE_ORG_ID --env $TESTKUBE_ENV_ID --cloud-root-domain testkube.dev"
+ - run:
+ name: "Trigger testkube test"
+ command: "testkube run test test-name -f"
+
+workflows:
+ run-tests-workflow:
+ jobs:
+ - run-tests
+```
+## Testkube OSS
+
+### How to configure Testkube CLI action for TK OSS and run a test
+
+To connect to the self-hosted instance, you need to have **kubectl** configured for accessing your Kubernetes cluster and pass an optional namespace, if Testkube is not deployed in the default **testkube** namespace.
+
+If a test is already created, you can run it using the command `testkube run test test-name -f` . However, if you need to create a test in this workflow, please add a creation command, e.g.: `testkube create test --name test-name --file path_to_file.json`.
+
+In order to connect to your own cluster, you can put your kubeconfig file into CircleCI variable named KUBECONFIGFILE.
+
+```yaml
+version: 2.1
+
+jobs:
+ run-tests:
+ docker:
+ - image: kubeshop/testkube-cli
+ working_directory: /.testkube
+ steps:
+ - run:
+ name: "Export kubeconfig"
+ command: |
+ echo $KUBECONFIGFILE > /.testkube/tmp/kubeconfig/config
+ export KUBECONFIG=/.testkube/tmp/kubeconfig/config
+ - run:
+ name: "Set Testkube Context"
+ command: "testkube set context --api-key $TESTKUBE_API_KEY --org $TESTKUBE_ORG_ID --env $TESTKUBE_ENV_ID --cloud-root-domain testkube.dev"
+ - run:
+ name: "Trigger testkube test"
+ command: "testkube run test test-name -f"
+
+workflows:
+ run-tests-workflow:
+ jobs:
+ - run-tests
+```
+
+The steps to connect to your Kubernetes cluster differ for each provider. You should check the docs of your Cloud provider for how to connect to the Kubernetes cluster from CircleCI.
+
+### How to configure Testkube CLI action for TK OSS and run a test
+
+This workflow establishes a connection to the EKS cluster and creates and runs a test using TK CLI. In this example we also use CircleCI variables not to reveal sensitive data. Please make sure that the following points are satisfied:
+- The **_AwsAccessKeyId_**, **_AwsSecretAccessKeyId_** secrets should contain your AWS IAM keys with proper permissions to connect to EKS cluster.
+- The **_AwsRegion_** secret should contain the AWS region where EKS is.
+- Tke **EksClusterName** secret points to the name of the EKS cluster you want to connect.
+
+```yaml
+version: 2.1
+
+jobs:
+ setup-aws:
+ docker:
+ - image: amazon/aws-cli
+ steps:
+ - run:
+ name: "Configure AWS CLI"
+ command: |
+ mkdir -p /.testkube/tmp/kubeconfig/config
+ aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
+ aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
+ aws configure set region $AWS_REGION
+ aws eks update-kubeconfig --name $EKS_CLUSTER_NAME --region $AWS_REGION --kubeconfig /.testkube/tmp/kubeconfig/config
+
+ run-testkube-on-aws:
+ docker:
+ - image: kubeshop/testkube-cli
+ working_directory: /.testkube
+ environment:
+ NAMESPACE: custom-testkube
+ steps:
+ - run:
+ name: "Run Testkube Test on EKS"
+ command: |
+ export KUBECONFIG=/.testkube/tmp/kubeconfig/config
+ testkube set context --kubeconfig --namespace $NAMESPACE
+ echo "Running Testkube test..."
+ testkube run test test-name -f
+
+workflows:
+ aws-testkube-workflow:
+ jobs:
+ - setup-aws
+ - run-testkube-on-aws:
+ requires:
+ - setup-aws
+```
+
+### How to connect to GKE (Google Kubernetes Engine) cluster and run a test
+
+This example connects to a k8s cluster in Google Cloud then creates and runs a test using Testkube CircleCI. Please make sure that the following points are satisfied:
+- The **_GKE Sevice Account_** should already be created in Google Cloud and added to CircleCI variables along with **_GKE Project_** value.
+- The **_GKE Cluster Name_** and **_GKE Zone_** can be added as environment variables in the workflow.
+
+
+```yaml
+version: 2.1
+
+jobs:
+ setup-gcp:
+ docker:
+ - image: google/cloud-sdk:latest
+ working_directory: /.testkube
+ steps:
+ - run:
+ name: "Setup GCP"
+ command: |
+ mkdir -p /.testkube/tmp/kubeconfig/config
+ export KUBECONFIG=$CI_PROJECT_DIR/tmp/kubeconfig/config
+ echo $GKE_SA_KEY | base64 -d > gke-sa-key.json
+ gcloud auth activate-service-account --key-file=gke-sa-key.json
+ gcloud config set project $GKE_PROJECT
+ gcloud --quiet auth configure-docker
+ gcloud container clusters get-credentials $GKE_CLUSTER_NAME --zone $GKE_ZONE
+
+ run-testkube-on-gcp:
+ docker:
+ - image: kubeshop/testkube-cli
+ working_directory: /.testkube
+ steps:
+ - run:
+ name: "Run Testkube Test on GKE"
+ command: |
+ export KUBECONFIG=/.testkube/tmp/kubeconfig/config
+ testkube set context --kubeconfig --namespace $NAMESPACE
+ testkube run test test-name -f
+
+workflows:
+ gke-testkube-workflow:
+ jobs:
+ - setup-gcp
+ - run-testkube-on-gcp:
+ requires:
+ - setup-gcp
+```
From 6ef90d90a0e348a8af42ede35efd2efab6c43054 Mon Sep 17 00:00:00 2001
From: Tomasz Konieczny
Date: Tue, 23 Jan 2024 21:20:09 +0100
Subject: [PATCH 030/234] feat: Executor tests jmeterd special cases extended,
run script fixed (labels) (#4929)
* jmeterd executor tests - special cases extended
* tests - run script fixed (labels)
* empty lines added
* empty lines added
---
.../executor-tests/crd/special-cases.yaml | 113 ++++++++++++++++++
test/scripts/executor-tests/run.sh | 6 +-
.../special-cases/jmeter-special-cases.yaml | 6 +
3 files changed, 122 insertions(+), 3 deletions(-)
diff --git a/test/jmeter/executor-tests/crd/special-cases.yaml b/test/jmeter/executor-tests/crd/special-cases.yaml
index a073e6e9c97..98fa4a7ee03 100644
--- a/test/jmeter/executor-tests/crd/special-cases.yaml
+++ b/test/jmeter/executor-tests/crd/special-cases.yaml
@@ -164,3 +164,116 @@ spec:
limits:
cpu: 500m
memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-directory-t-o
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: develop
+ path: test/jmeter/executor-tests
+ executionRequest:
+ args:
+ - "-t"
+ - "/data/repo/test/jmeter/executor-tests/jmeter-executor-smoke-2.jmx"
+ - "-o"
+ - "/data/output/custom-report-directory"
+ - "-l"
+ - "/data/output/custom-report.jtl"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-directory-t-o-slaves-2
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: develop
+ path: test/jmeter/executor-tests
+ executionRequest:
+ args:
+ - "-t"
+ - "/data/repo/test/jmeter/executor-tests/jmeter-executor-smoke-2.jmx"
+ - "-o"
+ - "/data/output/custom-report-directory"
+ - "-l"
+ - "/data/output/custom-report.jtl"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ variables:
+ SLAVES_COUNT:
+ name: SLAVES_COUNT
+ value: "2"
+ type: basic
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-directory-wdir-t-o-slaves-2
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: develop
+ path: test/jmeter/executor-tests
+ workingDir: test/jmeter/executor-tests
+ executionRequest:
+ args:
+ - "-t"
+ - "/data/repo/test/jmeter/executor-tests/jmeter-executor-smoke-2.jmx"
+ - "-o"
+ - "/data/output/custom-report-directory"
+ - "-l"
+ - "/data/output/custom-report.jtl"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ variables:
+ SLAVES_COUNT:
+ name: SLAVES_COUNT
+ value: "2"
+ type: basic
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
diff --git a/test/scripts/executor-tests/run.sh b/test/scripts/executor-tests/run.sh
index 740c23a04e2..ed5a080d5e6 100755
--- a/test/scripts/executor-tests/run.sh
+++ b/test/scripts/executor-tests/run.sh
@@ -47,9 +47,9 @@ create_update_testsuite_json() { # testsuite_name testsuite_path
if [ "$schedule" = true ] ; then # workaround for appending schedule
random_minute="$(($RANDOM % 59))"
- cat $2 | kubectl testkube --namespace $namespace $type testsuite --name $1 --label app=testkube --schedule "$random_minute */4 * * *"
+ cat $2 | kubectl testkube --namespace $namespace $type testsuite --name $1 --schedule "$random_minute */4 * * *"
else
- cat $2 | kubectl testkube --namespace $namespace $type testsuite --name $1 --label app=testkube
+ cat $2 | kubectl testkube --namespace $namespace $type testsuite --name $1
fi
}
@@ -58,7 +58,7 @@ create_update_testsuite() { # testsuite_name testsuite_path
if [ "$schedule" = true ] ; then # workaround for appending schedule
random_minute="$(($RANDOM % 59))"
- kubectl testkube --namespace $namespace update testsuite --name $1 --label app=testkube --schedule "$random_minute */4 * * *"
+ kubectl testkube --namespace $namespace update testsuite --name $1 --schedule "$random_minute */4 * * *"
fi
}
diff --git a/test/suites/special-cases/jmeter-special-cases.yaml b/test/suites/special-cases/jmeter-special-cases.yaml
index afac7662614..6e8b7c0d1b3 100644
--- a/test/suites/special-cases/jmeter-special-cases.yaml
+++ b/test/suites/special-cases/jmeter-special-cases.yaml
@@ -22,3 +22,9 @@ spec:
- stopOnFailure: false
execute:
- test: jmeterd-executor-smoke-slaves-sharedbetweenpods
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-directory-t-o
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-directory-t-o-slaves-2
From 79e38f341954212fc1cb3cfd69662e84d784c039 Mon Sep 17 00:00:00 2001
From: Julianne Fermi
Date: Tue, 23 Jan 2024 13:46:10 -0800
Subject: [PATCH 031/234] Discord-Slack Migration (#4928)
Update docs to contain link to Slack channel.
---
DESIGN.md | 19 +++++++++----------
README.md | 4 ++--
contrib/executor/artillery/README.md | 4 ++--
contrib/executor/gradle/README.md | 4 ++--
contrib/executor/k6/README.md | 4 ++--
contrib/executor/kubepug/README.md | 4 ++--
contrib/executor/maven/README.md | 4 ++--
contrib/executor/template/README.md | 4 ++--
contrib/executor/tracetest/README.md | 4 ++--
docs/docs/articles/argocd-integration.md | 8 ++++----
docs/docs/articles/common-issues.md | 4 ++--
docs/docs/articles/deploying-in-aws.md | 2 +-
docs/docs/articles/getting-started.md | 2 +-
.../testkube-pro/articles/AI-test-insights.md | 2 +-
.../testkube-pro/articles/status-pages.md | 2 +-
docs/docusaurus.config.js | 4 ++--
16 files changed, 37 insertions(+), 38 deletions(-)
diff --git a/DESIGN.md b/DESIGN.md
index 8bc838e663c..cc5e6a5a1ad 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -19,24 +19,24 @@ Testkube consists of 3 different parts.
## 🚢 How to contribute design
-1. Check out open [issues](https://github.com/kubeshop/testkube/issues) here on GitHub (we tend to label them with `🚨 needs-ux`)
+1. Check out open [issues](https://github.com/kubeshop/testkube/issues) here on GitHub (we tend to label them with `🚨 needs-ux`).
2. Feel free to open an issue on your own if you find something you would like to contribute to the project and use the `idea 💡` label for it.
-3. Clone the public Figma files or create new ones and share them publicly
-4. Add your contributions to an issue and we promise we will review your contribution carefully and foster discussions around it
+3. Clone the public Figma files or create new ones and share them publicly.
+4. Add your contributions to an issue and we promise we will review your contribution carefully and foster discussions around it.
**We encourage you to:**
-- Get in touch with the team by starting a discussion on [GitHub](https://github.com/kubeshop/testkube/issues) or on our [Discord Server](https://discord.gg/hfq44wtR6Q).
+- Get in touch with the team by starting a discussion on [GitHub](https://github.com/kubeshop/testkube/issues) or on our [Slack Channel](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email).
- Check out our [Contributor Guide](https://github.com/kubeshop/testkube/blob/main/CONTRIBUTING.md) and
- [Code of Conduct](https://github.com/kubeshop/testkube/blob/main/CODE_OF_CONDUCT.md)
+ [Code of Conduct](https://github.com/kubeshop/testkube/blob/main/CODE_OF_CONDUCT.md).
-## 🎭 Target audience
+## 🎭 Target Audience
Since we are creating a product for Testers and Developers our target audience is pretty straight forward. Sometimes wo do also like to include DevOps people into our considerations.
-## 💅 Design relevant materials
+## 💅 Design Relevant Materials
-We currently aim to to build a more comprehensive Design System which will also include some guidance on Component usage, Wording, and patterns.
+We currently aim to to build a more comprehensive Design System which will also include some guidance on Component usage, Wording, and Patterns.
For now – here is a list of design relevant information and materials:
@@ -56,7 +56,6 @@ https://www.figma.com/file/59vZTaJ6O2wTk0Qyqh2IJJ/Testkube-CLI?t=CBjcXzIKoEcG2AG
## 🎓 License
-All design work is licensed under the
-[MIT](https://mit-license.org/)
+All design work is licensed under [MIT](https://mit-license.org/).
[(Back to top)](#-table-of-contents)
\ No newline at end of file
diff --git a/README.md b/README.md
index 5c83dc888cc..0e4ae73d365 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
Website |
Documentation |
Twitter |
- Discord |
+ Slack |
Blog
@@ -112,4 +112,4 @@ Go to [contribution document](CONTRIBUTING.md) to read more how can you help us
# Feedback
Whether it helps you or not - we'd LOVE to hear from you. Please let us know what you think and of course, how we can make it better.
-Please join our growing community on [Discord](https://discord.com/invite/6zupCZFQbe)
+Please join our growing community on [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
diff --git a/contrib/executor/artillery/README.md b/contrib/executor/artillery/README.md
index 0784b5d01ec..ad724060a8a 100644
--- a/contrib/executor/artillery/README.md
+++ b/contrib/executor/artillery/README.md
@@ -63,5 +63,5 @@ For more info go to [main testkube repo](https://github.com/kubeshop/testkube)
![Docker builds](https://img.shields.io/docker/automated/kubeshop/testkube-api-server) ![Code build](https://img.shields.io/github/workflow/status/kubeshop/testkube/Code%20build%20and%20checks) ![Release date](https://img.shields.io/github/release-date/kubeshop/testkube)
-![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Discord](https://img.shields.io/discord/884464549347074049)
- #### [Documentation](https://docs.testkube.io) | [Discord](https://discord.gg/hfq44wtR6Q)
\ No newline at end of file
+![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
+ #### [Documentation](https://docs.testkube.io) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
\ No newline at end of file
diff --git a/contrib/executor/gradle/README.md b/contrib/executor/gradle/README.md
index 194b76818de..7a98d3f3d01 100644
--- a/contrib/executor/gradle/README.md
+++ b/contrib/executor/gradle/README.md
@@ -31,5 +31,5 @@ For more info go to [main testkube repo](https://github.com/kubeshop/testkube)
![Docker builds](https://img.shields.io/docker/automated/kubeshop/testkube-api-server) ![Code build](https://img.shields.io/github/workflow/status/kubeshop/testkube/Code%20build%20and%20checks) ![Release date](https://img.shields.io/github/release-date/kubeshop/testkube)
-![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Discord](https://img.shields.io/discord/884464549347074049)
- #### [Documentation](https://docs.testkube.io) | [Discord](https://discord.gg/hfq44wtR6Q)
+![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
+ #### [Documentation](https://docs.testkube.io) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
diff --git a/contrib/executor/k6/README.md b/contrib/executor/k6/README.md
index 15c9b6be3a7..6c352b38cee 100644
--- a/contrib/executor/k6/README.md
+++ b/contrib/executor/k6/README.md
@@ -60,5 +60,5 @@ For more info go to [main testkube repo](https://github.com/kubeshop/testkube)
![Docker builds](https://img.shields.io/docker/automated/kubeshop/testkube-api-server) ![Code build](https://img.shields.io/github/workflow/status/kubeshop/testkube/Code%20build%20and%20checks) ![Release date](https://img.shields.io/github/release-date/kubeshop/testkube)
-![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Discord](https://img.shields.io/discord/884464549347074049)
- #### [Documentation](https://docs.testkube.io) | [Discord](https://discord.gg/hfq44wtR6Q)
\ No newline at end of file
+![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
+ #### [Documentation](https://docs.testkube.io) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
\ No newline at end of file
diff --git a/contrib/executor/kubepug/README.md b/contrib/executor/kubepug/README.md
index 85d1182ba66..cb74c5fddd4 100644
--- a/contrib/executor/kubepug/README.md
+++ b/contrib/executor/kubepug/README.md
@@ -120,6 +120,6 @@ For more info go to [main Testkube repo](https://github.com/kubeshop/testkube)
![Docker builds](https://img.shields.io/docker/automated/kubeshop/testkube-api-server) ![Code build](https://img.shields.io/github/workflow/status/kubeshop/testkube/Code%20build%20and%20checks) ![Release date](https://img.shields.io/github/release-date/kubeshop/testkube)
-![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Discord](https://img.shields.io/discord/884464549347074049)
+![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
-#### [Documentation](https://docs.testkube.io) | [Discord](https://discord.gg/hfq44wtR6Q)
+#### [Documentation](https://docs.testkube.io) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
diff --git a/contrib/executor/maven/README.md b/contrib/executor/maven/README.md
index 3d1328f0828..92b0a10691d 100644
--- a/contrib/executor/maven/README.md
+++ b/contrib/executor/maven/README.md
@@ -30,5 +30,5 @@ For more info go to [main testkube repo](https://github.com/kubeshop/testkube)
![Docker builds](https://img.shields.io/docker/automated/kubeshop/testkube-api-server) ![Code build](https://img.shields.io/github/workflow/status/kubeshop/testkube/Code%20build%20and%20checks) ![Release date](https://img.shields.io/github/release-date/kubeshop/testkube)
-![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Discord](https://img.shields.io/discord/884464549347074049)
- #### [Documentation](https://docs.testkube.io) | [Discord](https://discord.gg/hfq44wtR6Q)
+![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
+ #### [Documentation](https://docs.testkube.io) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
diff --git a/contrib/executor/template/README.md b/contrib/executor/template/README.md
index dc5135d4aea..1af1e5ea708 100644
--- a/contrib/executor/template/README.md
+++ b/contrib/executor/template/README.md
@@ -73,5 +73,5 @@ For more info go to [main testkube repo](https://github.com/kubeshop/testkube)
![Docker builds](https://img.shields.io/docker/automated/kubeshop/testkube-api-server) ![Code build](https://img.shields.io/github/workflow/status/kubeshop/testkube/Code%20build%20and%20checks) ![Release date](https://img.shields.io/github/release-date/kubeshop/testkube)
-![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Discord](https://img.shields.io/discord/884464549347074049)
- #### [Documentation](https://docs.testkube.io/openapi) | [Discord](https://discord.gg/hfq44wtR6Q)
\ No newline at end of file
+![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social) ![Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
+ #### [Documentation](https://docs.testkube.io/openapi) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
\ No newline at end of file
diff --git a/contrib/executor/tracetest/README.md b/contrib/executor/tracetest/README.md
index 9186508d672..14623b38ea9 100644
--- a/contrib/executor/tracetest/README.md
+++ b/contrib/executor/tracetest/README.md
@@ -125,7 +125,7 @@ For more info go to [main testkube repo](https://github.com/kubeshop/testkube)
![Twitter](https://img.shields.io/twitter/follow/thekubeshop?style=social)
-#### [Documentation](https://kubeshop.github.io/testkube) | [Discord](https://discord.gg/hfq44wtR6Q)
+#### [Documentation](https://kubeshop.github.io/testkube) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
# Tracetest
@@ -133,4 +133,4 @@ For more info go to [main tracetest repo](https://github.com/kubeshop/tracetest)
![Twitter](https://img.shields.io/twitter/follow/tracetest_io?style=social)
-#### [Documentation](https://docs.tracetest.io/) | [Discord](https://discord.gg/6zupCZFQbe)
\ No newline at end of file
+#### [Documentation](https://docs.tracetest.io/) | [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)
\ No newline at end of file
diff --git a/docs/docs/articles/argocd-integration.md b/docs/docs/articles/argocd-integration.md
index 0cffd2be9c5..6dca98a886a 100644
--- a/docs/docs/articles/argocd-integration.md
+++ b/docs/docs/articles/argocd-integration.md
@@ -219,7 +219,7 @@ spec:
namespace: testkube
```
-Notice that we have defined path `postman-collections` which is the test folder with our Postman collections from the steps earlier. With Testkube you can use multiple test executors like `curl`, for example, so it is convenient to have a folder for each. We have also defined the `.destination.namespace` to be `testkube`, which is where the tests should be deployed in our cluster.
+Notice that we have defined the path `postman-collections` which is the test folder with our Postman collections from the steps earlier. With Testkube you can use multiple test executors like `curl`, for example, so it is convenient to have a folder for each. We have also defined the `.destination.namespace` to be `testkube`, which is where the tests should be deployed in our cluster.
Now let’s create the application with:
@@ -297,12 +297,12 @@ And you will be able to see the results of the execution in the Executions tab a
We now have an automated test deployment and execution pipeline based on GitOps principles!
-### 11. Allow to add ownerReferences to CronJobs metadata for Tests and Test Suites
+### 11. Allow adding ownerReferences to CronJobs metadata for Tests and Test Suites
-You will need to enable helm chart variable `useArgoCDSync = true` in order to make CronJobs created for Tests and Test Suites syncronized in ArgoCD.
+You will need to enable the Helm chart variable `useArgoCDSync = true` in order to make CronJobs created for Tests and Test Suites syncronized in ArgoCD.
## GitOps Takeaways
Once fully realized - using GitOps for testing of Kubernetes applications as described above provides a powerful alternative to a more traditional approach where orchestration is tied to your current CI/CD tooling and not closely aligned with the lifecycle of Kubernetes applications.
-We would love to get your thoughts on the above approach - over-engineering done right? Waste of time? Let us know on [our Discord server](https://discord.com/channels/884464549347074049/885185660808474664)!
+We would love to get your thoughts on the above approach - over-engineering done right? Waste of time? Let us know on [our Slack Channel](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email)!
diff --git a/docs/docs/articles/common-issues.md b/docs/docs/articles/common-issues.md
index 3bf32777f85..2934b790069 100644
--- a/docs/docs/articles/common-issues.md
+++ b/docs/docs/articles/common-issues.md
@@ -83,11 +83,11 @@ Please stop the application that listens on 8080, 8088 ports.
## If You're Still Having Issues
-If these guides do not solve the issue that you encountered or you have other questions or comments, please contact us on [Discord](https://discord.com/invite/6zupCZFQbe).
+If these guides do not solve the issue that you encountered or you have other questions or comments, please contact us on [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email).
## Other Installation Methods
-### Installation on OpenShift deployed on GCP
+### Installation on OpenShift Deployed on GCP
To install Testkube you need an empty OpenShift cluster. Once the cluster is up and running update `values.yaml` file, including the configuration below.
diff --git a/docs/docs/articles/deploying-in-aws.md b/docs/docs/articles/deploying-in-aws.md
index bf07bd96635..d6c6cb4c960 100644
--- a/docs/docs/articles/deploying-in-aws.md
+++ b/docs/docs/articles/deploying-in-aws.md
@@ -250,4 +250,4 @@ data "aws_iam_policy_document" "testkube" {
With just a few changes you can deploy Testkube into an EKS cluster and expose it to the outside world while all the necessary resources are created automatically.
-If you have any questions you can [join our Discord community](https://discord.com/invite/6zupCZFQbe) or, if you have any ideas for other useful features, you can create feature requests at our [GitHub Issues](https://github.com/kubeshop/testkube) page.
+If you have any questions you can [join our Slack Channel](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email) or, if you have any ideas for other useful features, you can create feature requests at our [GitHub Issues](https://github.com/kubeshop/testkube) page.
diff --git a/docs/docs/articles/getting-started.md b/docs/docs/articles/getting-started.md
index 2e57896ed44..f05c2869b22 100644
--- a/docs/docs/articles/getting-started.md
+++ b/docs/docs/articles/getting-started.md
@@ -51,7 +51,7 @@ By default, Testkube is installed in the `testkube` namespace.
## Need Help?
-- Join our community on [Discord](https://discord.com/invite/6zupCZFQbe).
+- Join our community on [Slack](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email).
- [Schedule a call](https://calendly.com/bryan-3pu/support-product-feedback-call?month=2023-10) with one of our experts.
- Check out our guides.
- [Integrating Testkube with your CI/CD](https://docs.testkube.io/articles/cicd-overview/).
diff --git a/docs/docs/testkube-pro/articles/AI-test-insights.md b/docs/docs/testkube-pro/articles/AI-test-insights.md
index 788f6ad817f..6160d6395f6 100644
--- a/docs/docs/testkube-pro/articles/AI-test-insights.md
+++ b/docs/docs/testkube-pro/articles/AI-test-insights.md
@@ -82,7 +82,7 @@ Now if you execute the test again, it passes. Note that the AI Analysis tab is n
This was a simple demo to show you how to use Testkube’s AI Analysis feature to analyze logs and fix failing tests quickly. You can create complex tests to test your applications and infrastructure.
-If you have feedback or concerns using the AI analysis feature, do share them on our [Discord channel](https://discord.com/invite/6zupCZFQbe) for faster resolution.
+If you have feedback or concerns using the AI analysis feature, do share them on our [Slack Channel](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email) for faster resolution.
diff --git a/docs/docs/testkube-pro/articles/status-pages.md b/docs/docs/testkube-pro/articles/status-pages.md
index b5ae4bf4677..19d69913780 100644
--- a/docs/docs/testkube-pro/articles/status-pages.md
+++ b/docs/docs/testkube-pro/articles/status-pages.md
@@ -222,4 +222,4 @@ Custom Slugs: If applicable, configure custom slugs for your status pages to mat
These best practices will help you maximize the effectiveness of Testkube Status Pages, ensuring that it serves as a valuable communication tool for both technical and non-technical stakeholders. By following these guidelines, you can maintain transparency, respond efficiently to incidents, and provide a reliable source of information about the status of your software projects.
-If you have any questions or need assistance, our team is ready to assist you in our [Discord channel](https://discord.com/invite/6zupCZFQbe).
+If you have any questions or need assistance, our team is ready to assist you in our [Slack Channel](https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email).
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
index 93e6d0cca30..2a78b293129 100644
--- a/docs/docusaurus.config.js
+++ b/docs/docusaurus.config.js
@@ -114,8 +114,8 @@ const config = {
title: "Community",
items: [
{
- label: "Discord",
- href: "https://discord.com/invite/6zupCZFQbe",
+ label: "Slack",
+ href: "https://testkubeworkspace.slack.com/join/shared_invite/zt-2arhz5vmu-U2r3WZ69iPya5Fw0hMhRDg#/shared-invite/email",
},
{
label: "Twitter",
From e1127f063b9f83f7222c6e8eee2167649981e673 Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 24 Jan 2024 11:07:18 +0200
Subject: [PATCH 032/234] update cimfor logs service
---
.github/workflows/release-dev-log-server.yaml | 4 ++--
.github/workflows/release-dev-log-sidecar.yaml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/release-dev-log-server.yaml b/.github/workflows/release-dev-log-server.yaml
index 8a7d3d2f794..cc6da49741e 100644
--- a/.github/workflows/release-dev-log-server.yaml
+++ b/.github/workflows/release-dev-log-server.yaml
@@ -2,8 +2,8 @@ name: Release logs server dev
on:
push:
- tags:
- - "v[0-9]+.[0-9]+.[0-9]+-*"
+ branches:
+ - develop
permissions:
id-token: write
diff --git a/.github/workflows/release-dev-log-sidecar.yaml b/.github/workflows/release-dev-log-sidecar.yaml
index 9b0c186dd2e..6b52fbf04c1 100644
--- a/.github/workflows/release-dev-log-sidecar.yaml
+++ b/.github/workflows/release-dev-log-sidecar.yaml
@@ -2,8 +2,8 @@ name: Release logs sidecar dev
on:
push:
- tags:
- - "v[0-9]+.[0-9]+.[0-9]+-*"
+ branches:
+ - develop
permissions:
id-token: write
From 8efb2690b70cb7b4402e5bdd3e088c3040745dea Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 24 Jan 2024 11:23:51 +0200
Subject: [PATCH 033/234] add manifest for logs service
---
.github/workflows/release-dev-log-server.yaml | 4 ++--
.github/workflows/release-dev-log-sidecar.yaml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/release-dev-log-server.yaml b/.github/workflows/release-dev-log-server.yaml
index cc6da49741e..da47184e43a 100644
--- a/.github/workflows/release-dev-log-server.yaml
+++ b/.github/workflows/release-dev-log-server.yaml
@@ -72,8 +72,8 @@ jobs:
- name: Push Docker images
run: |
- docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64
+ docker manifest create kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }} --amend kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --amend kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64
+ docker manifest push -p kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}
- name: Push README to Dockerhub
uses: christian-korneck/update-container-description-action@v1
diff --git a/.github/workflows/release-dev-log-sidecar.yaml b/.github/workflows/release-dev-log-sidecar.yaml
index 6b52fbf04c1..44412d05b07 100644
--- a/.github/workflows/release-dev-log-sidecar.yaml
+++ b/.github/workflows/release-dev-log-sidecar.yaml
@@ -72,8 +72,8 @@ jobs:
- name: Push Docker images
run: |
- docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64
+ docker manifest create kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }} --amend kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --amend kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64
+ docker manifest push -p kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}
- name: Push README to Dockerhub
uses: christian-korneck/update-container-description-action@v1
From ea652d2758819125ba5972877112c1f919bc8408 Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 24 Jan 2024 11:53:52 +0200
Subject: [PATCH 034/234] fix bug in manifest creation for logs services
---
.github/workflows/release-dev-log-server.yaml | 3 +++
.github/workflows/release-dev-log-sidecar.yaml | 3 +++
2 files changed, 6 insertions(+)
diff --git a/.github/workflows/release-dev-log-server.yaml b/.github/workflows/release-dev-log-server.yaml
index da47184e43a..9192c4b7e1b 100644
--- a/.github/workflows/release-dev-log-server.yaml
+++ b/.github/workflows/release-dev-log-server.yaml
@@ -72,6 +72,9 @@ jobs:
- name: Push Docker images
run: |
+ docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
+ docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64
+
docker manifest create kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }} --amend kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --amend kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64
docker manifest push -p kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}
diff --git a/.github/workflows/release-dev-log-sidecar.yaml b/.github/workflows/release-dev-log-sidecar.yaml
index 44412d05b07..474987cd81e 100644
--- a/.github/workflows/release-dev-log-sidecar.yaml
+++ b/.github/workflows/release-dev-log-sidecar.yaml
@@ -72,6 +72,9 @@ jobs:
- name: Push Docker images
run: |
+ docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
+ docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64
+
docker manifest create kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }} --amend kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --amend kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64
docker manifest push -p kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}
From cad17745e0e86acb7c6f5cf6db8e258f5070e367 Mon Sep 17 00:00:00 2001
From: Tomasz Konieczny
Date: Wed, 24 Jan 2024 13:32:17 +0100
Subject: [PATCH 035/234] feat: executor tests - gradle and maven - tests
extended, non-default executor tests fixed (#4930)
* executor tests - gradle and maven - tests extended, non-default executor tests - fixed
* empty lines added
* maven testsuite fixed
---
test/executors/gradle.yaml | 36 +++++++++++++--
test/executors/maven.yaml | 30 +++++++++++--
test/gradle/executor-smoke/crd/crd.yaml | 46 +++++++++++++++-----
test/maven/executor-smoke/crd/crd.yaml | 26 ++++++++++-
test/suites/executor-gradle-smoke-tests.yaml | 3 ++
test/suites/executor-maven-smoke-tests.yaml | 3 ++
6 files changed, 123 insertions(+), 21 deletions(-)
diff --git a/test/executors/gradle.yaml b/test/executors/gradle.yaml
index 9c563477f6f..b8694a6aafa 100644
--- a/test/executors/gradle.yaml
+++ b/test/executors/gradle.yaml
@@ -7,7 +7,14 @@ spec:
types:
- gradle:jdk18/project
- gradle:jdk18/test
- - gradle:jdk18/integrationTest
+ - gradle:jdk18/integrationTest
+ command: ["gradle"]
+ args: [
+ "--no-daemon",
+ "",
+ "-p",
+ ""
+ ]
---
apiVersion: executor.testkube.io/v1
kind: Executor
@@ -18,7 +25,14 @@ spec:
types:
- gradle:jdk17/project
- gradle:jdk17/test
- - gradle:jdk17/integrationTest
+ - gradle:jdk17/integrationTest
+ command: ["gradle"]
+ args: [
+ "--no-daemon",
+ "",
+ "-p",
+ ""
+ ]
---
apiVersion: executor.testkube.io/v1
kind: Executor
@@ -29,7 +43,14 @@ spec:
types:
- gradle:jdk11/project
- gradle:jdk11/test
- - gradle:jdk11/integrationTest
+ - gradle:jdk11/integrationTest
+ command: ["gradle"]
+ args: [
+ "--no-daemon",
+ "",
+ "-p",
+ ""
+ ]
---
apiVersion: executor.testkube.io/v1
kind: Executor
@@ -40,4 +61,11 @@ spec:
types:
- gradle:jdk8/project
- gradle:jdk8/test
- - gradle:jdk8/integrationTest
\ No newline at end of file
+ - gradle:jdk8/integrationTest
+ command: ["gradle"]
+ args: [
+ "--no-daemon",
+ "",
+ "-p",
+ ""
+ ]
diff --git a/test/executors/maven.yaml b/test/executors/maven.yaml
index 1f106339b19..c0ae589a9f0 100644
--- a/test/executors/maven.yaml
+++ b/test/executors/maven.yaml
@@ -7,7 +7,15 @@ spec:
types:
- maven:jdk18/project
- maven:jdk18/test
- - maven:jdk18/integration-test
+ - maven:jdk18/integration-test
+ command: ["mvn"]
+ args: [
+ "--settings",
+ "",
+ "",
+ "-Duser.home",
+ ""
+ ]
---
apiVersion: executor.testkube.io/v1
kind: Executor
@@ -18,7 +26,15 @@ spec:
types:
- maven:jdk11/project
- maven:jdk11/test
- - maven:jdk11/integration-test
+ - maven:jdk11/integration-test
+ command: ["mvn"]
+ args: [
+ "--settings",
+ "",
+ "",
+ "-Duser.home",
+ ""
+ ]
---
apiVersion: executor.testkube.io/v1
kind: Executor
@@ -29,4 +45,12 @@ spec:
types:
- maven:jdk8/project
- maven:jdk8/test
- - maven:jdk8/integration-test
\ No newline at end of file
+ - maven:jdk8/integration-test
+ command: ["mvn"]
+ args: [
+ "--settings",
+ "",
+ "",
+ "-Duser.home",
+ ""
+ ]
diff --git a/test/gradle/executor-smoke/crd/crd.yaml b/test/gradle/executor-smoke/crd/crd.yaml
index dd29941beff..185b85428d3 100644
--- a/test/gradle/executor-smoke/crd/crd.yaml
+++ b/test/gradle/executor-smoke/crd/crd.yaml
@@ -1,5 +1,27 @@
-# https://github.com/kubeshop/testkube-executor-gradle/tree/main/examples
-
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: gradle-executor-smoke
+ labels:
+ core-tests: executors
+spec:
+ type: gradle/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: contrib/executor/gradle/examples/hello-gradle-jdk18
+ executionRequest:
+ variables:
+ TESTKUBE_GRADLE:
+ name: TESTKUBE_GRADLE
+ value: "true"
+ type: basic
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+---
apiVersion: tests.testkube.io/v3
kind: Test
metadata:
@@ -12,9 +34,9 @@ spec:
type: git
repository:
type: git
- uri: https://github.com/kubeshop/testkube-executor-gradle.git
+ uri: https://github.com/kubeshop/testkube.git
branch: main
- path: examples/hello-gradle-jdk18
+ path: contrib/executor/gradle/examples/hello-gradle-jdk18
executionRequest:
variables:
TESTKUBE_GRADLE:
@@ -36,9 +58,9 @@ spec:
type: git
repository:
type: git
- uri: https://github.com/kubeshop/testkube-executor-gradle.git
+ uri: https://github.com/kubeshop/testkube.git
branch: main
- path: examples/hello-gradle
+ path: contrib/executor/gradle/examples/hello-gradle
executionRequest:
variables:
TESTKUBE_GRADLE:
@@ -60,9 +82,9 @@ spec:
type: git
repository:
type: git
- uri: https://github.com/kubeshop/testkube-executor-gradle.git
+ uri: https://github.com/kubeshop/testkube.git
branch: main
- path: examples/hello-gradle
+ path: contrib/executor/gradle/examples/hello-gradle
executionRequest:
variables:
TESTKUBE_GRADLE:
@@ -84,9 +106,9 @@ spec:
type: git
repository:
type: git
- uri: https://github.com/kubeshop/testkube-executor-gradle.git
+ uri: https://github.com/kubeshop/testkube.git
branch: main
- path: examples/hello-gradle
+ path: contrib/executor/gradle/examples/hello-gradle
executionRequest:
variables:
TESTKUBE_GRADLE:
@@ -108,9 +130,9 @@ spec:
type: git
repository:
type: git
- uri: https://github.com/kubeshop/testkube-executor-gradle.git
+ uri: https://github.com/kubeshop/testkube.git
branch: main
- path: examples/hello-gradle-jdk18
+ path: contrib/executor/gradle/examples/hello-gradle-jdk18
executionRequest:
negativeTest: true
jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
diff --git a/test/maven/executor-smoke/crd/crd.yaml b/test/maven/executor-smoke/crd/crd.yaml
index 69da5c03cb2..37ca9ae400c 100644
--- a/test/maven/executor-smoke/crd/crd.yaml
+++ b/test/maven/executor-smoke/crd/crd.yaml
@@ -1,5 +1,27 @@
-# https://github.com/kubeshop/testkube-executor-maven/tree/main/examples
-
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: maven-executor-smoke
+ labels:
+ core-tests: executors
+spec:
+ type: maven/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: contrib/executor/maven/examples/hello-maven-jdk18
+ executionRequest:
+ variables:
+ TESTKUBE_MAVEN:
+ name: TESTKUBE_MAVEN
+ value: "true"
+ type: basic
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 256Mi\n cpu: 256m\n"
+ activeDeadlineSeconds: 180
+---
apiVersion: tests.testkube.io/v3
kind: Test
metadata:
diff --git a/test/suites/executor-gradle-smoke-tests.yaml b/test/suites/executor-gradle-smoke-tests.yaml
index 592d3875bdf..cbc683cf255 100644
--- a/test/suites/executor-gradle-smoke-tests.yaml
+++ b/test/suites/executor-gradle-smoke-tests.yaml
@@ -7,6 +7,9 @@ metadata:
spec:
description: "gradle executor smoke tests"
steps:
+ - stopOnFailure: false
+ execute:
+ - test: gradle-executor-smoke
- stopOnFailure: false
execute:
- test: gradle-executor-smoke-jdk18
diff --git a/test/suites/executor-maven-smoke-tests.yaml b/test/suites/executor-maven-smoke-tests.yaml
index 84dc640ca9a..15398f9dc0e 100644
--- a/test/suites/executor-maven-smoke-tests.yaml
+++ b/test/suites/executor-maven-smoke-tests.yaml
@@ -7,6 +7,9 @@ metadata:
spec:
description: "maven executor smoke tests"
steps:
+ - stopOnFailure: false
+ execute:
+ - test: maven-executor-smoke
- stopOnFailure: false
execute:
- test: maven-executor-smoke-jdk18
From 6af915268cb4304f66ba7f75578067f0e00788a8 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Wed, 24 Jan 2024 13:42:59 +0100
Subject: [PATCH 036/234] chore: refactor of client.Client into
client.StreamGetter (#4933)
---
pkg/logs/client/client.go | 7 ++--
pkg/logs/client/interface.go | 9 ++---
pkg/logs/client/mock_client.go | 50 ---------------------------
pkg/logs/client/mock_streamgetter.go | 51 ++++++++++++++++++++++++++++
pkg/logs/logsserver.go | 6 +++-
pkg/logs/logsserver_test.go | 7 ++--
pkg/logs/repository/factory.go | 2 +-
pkg/logs/repository/interface.go | 2 +-
pkg/logs/repository/jetstream.go | 6 ++--
pkg/logs/repository/minio.go | 4 +--
10 files changed, 74 insertions(+), 70 deletions(-)
delete mode 100644 pkg/logs/client/mock_client.go
create mode 100644 pkg/logs/client/mock_streamgetter.go
diff --git a/pkg/logs/client/client.go b/pkg/logs/client/client.go
index a955ffd15bd..189c9ae79c6 100644
--- a/pkg/logs/client/client.go
+++ b/pkg/logs/client/client.go
@@ -18,7 +18,8 @@ const (
buffer = 100
)
-func NewGrpcClient(address string) Client {
+// NewGrpcClient imlpements getter interface for log stream for given ID
+func NewGrpcClient(address string) StreamGetter {
return &GrpcClient{
log: log.DefaultLogger.With("service", "logs-grpc-client"),
address: address,
@@ -31,7 +32,7 @@ type GrpcClient struct {
}
// Get returns channel with log stream chunks for given execution id connects through GRPC to log service
-func (c GrpcClient) Get(ctx context.Context, id string) chan events.LogResponse {
+func (c GrpcClient) Get(ctx context.Context, id string) (chan events.LogResponse, error) {
ch := make(chan events.LogResponse, buffer)
log := c.log.With("id", id)
@@ -78,5 +79,5 @@ func (c GrpcClient) Get(ctx context.Context, id string) chan events.LogResponse
}
}()
- return ch
+ return ch, nil
}
diff --git a/pkg/logs/client/interface.go b/pkg/logs/client/interface.go
index 595dffa93db..58458f23cd3 100644
--- a/pkg/logs/client/interface.go
+++ b/pkg/logs/client/interface.go
@@ -13,11 +13,6 @@ const (
StopSubject = "events.logs.stop"
)
-//go:generate mockgen -destination=./mock_client.go -package=client "github.com/kubeshop/testkube/pkg/logs/client" Client
-type Client interface {
- Get(ctx context.Context, id string) chan events.LogResponse
-}
-
//go:generate mockgen -destination=./mock_stream.go -package=client "github.com/kubeshop/testkube/pkg/logs/client" Stream
type Stream interface {
StreamInitializer
@@ -54,7 +49,9 @@ type StreamPusher interface {
PushBytes(ctx context.Context, id string, chunk []byte) error
}
-// LogStream is a single log stream chunk with possible errors
+// StreamGetter interface for getting logs stream channel
+//
+//go:generate mockgen -destination=./mock_streamgetter.go -package=client "github.com/kubeshop/testkube/pkg/logs/client" StreamGetter
type StreamGetter interface {
// Init creates or updates stream on demand
Get(ctx context.Context, id string) (chan events.LogResponse, error)
diff --git a/pkg/logs/client/mock_client.go b/pkg/logs/client/mock_client.go
deleted file mode 100644
index a87031ea1af..00000000000
--- a/pkg/logs/client/mock_client.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/kubeshop/testkube/pkg/logs/client (interfaces: Client)
-
-// Package client is a generated GoMock package.
-package client
-
-import (
- context "context"
- reflect "reflect"
-
- gomock "github.com/golang/mock/gomock"
- events "github.com/kubeshop/testkube/pkg/logs/events"
-)
-
-// MockClient is a mock of Client interface.
-type MockClient struct {
- ctrl *gomock.Controller
- recorder *MockClientMockRecorder
-}
-
-// MockClientMockRecorder is the mock recorder for MockClient.
-type MockClientMockRecorder struct {
- mock *MockClient
-}
-
-// NewMockClient creates a new mock instance.
-func NewMockClient(ctrl *gomock.Controller) *MockClient {
- mock := &MockClient{ctrl: ctrl}
- mock.recorder = &MockClientMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockClient) EXPECT() *MockClientMockRecorder {
- return m.recorder
-}
-
-// Get mocks base method.
-func (m *MockClient) Get(arg0 context.Context, arg1 string) chan events.LogResponse {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Get", arg0, arg1)
- ret0, _ := ret[0].(chan events.LogResponse)
- return ret0
-}
-
-// Get indicates an expected call of Get.
-func (mr *MockClientMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), arg0, arg1)
-}
diff --git a/pkg/logs/client/mock_streamgetter.go b/pkg/logs/client/mock_streamgetter.go
new file mode 100644
index 00000000000..c319a0ebeb6
--- /dev/null
+++ b/pkg/logs/client/mock_streamgetter.go
@@ -0,0 +1,51 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/kubeshop/testkube/pkg/logs/client (interfaces: StreamGetter)
+
+// Package client is a generated GoMock package.
+package client
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ events "github.com/kubeshop/testkube/pkg/logs/events"
+)
+
+// MockStreamGetter is a mock of StreamGetter interface.
+type MockStreamGetter struct {
+ ctrl *gomock.Controller
+ recorder *MockStreamGetterMockRecorder
+}
+
+// MockStreamGetterMockRecorder is the mock recorder for MockStreamGetter.
+type MockStreamGetterMockRecorder struct {
+ mock *MockStreamGetter
+}
+
+// NewMockStreamGetter creates a new mock instance.
+func NewMockStreamGetter(ctrl *gomock.Controller) *MockStreamGetter {
+ mock := &MockStreamGetter{ctrl: ctrl}
+ mock.recorder = &MockStreamGetterMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockStreamGetter) EXPECT() *MockStreamGetterMockRecorder {
+ return m.recorder
+}
+
+// Get mocks base method.
+func (m *MockStreamGetter) Get(arg0 context.Context, arg1 string) (chan events.LogResponse, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Get", arg0, arg1)
+ ret0, _ := ret[0].(chan events.LogResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockStreamGetterMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStreamGetter)(nil).Get), arg0, arg1)
+}
diff --git a/pkg/logs/logsserver.go b/pkg/logs/logsserver.go
index c5d2c55eae7..70810c48bd3 100644
--- a/pkg/logs/logsserver.go
+++ b/pkg/logs/logsserver.go
@@ -44,8 +44,12 @@ func (s LogsServer) Logs(req *pb.LogRequest, stream pb.LogsService_LogsServer) e
s.log.Debugw("starting sending stream", "repo", repo)
// stream logs from repository through GRPC channel
- for l := range repo.Get(ctx, req.ExecutionId) {
+ ch, err := repo.Get(ctx, req.ExecutionId)
+ if err != nil {
+ return err
+ }
+ for l := range ch {
s.log.Debug("sending log chunk", "log", l)
if err := stream.Send(pb.MapResponseToPB(l)); err != nil {
return err
diff --git a/pkg/logs/logsserver_test.go b/pkg/logs/logsserver_test.go
index 31a0a254836..aa21cc9ef47 100644
--- a/pkg/logs/logsserver_test.go
+++ b/pkg/logs/logsserver_test.go
@@ -35,7 +35,8 @@ func TestGRPC_Server(t *testing.T) {
expectedCount := 0
stream := client.NewGrpcClient(ls.grpcAddress)
- ch := stream.Get(ctx, "id1")
+ ch, err := stream.Get(ctx, "id1")
+ assert.NoError(t, err)
t.Log("waiting for logs")
@@ -68,12 +69,12 @@ func (l LogsFactoryMock) GetRepository(state state.LogState) (repository.LogsRep
type LogsRepositoryMock struct{}
-func (l LogsRepositoryMock) Get(ctx context.Context, id string) chan events.LogResponse {
+func (l LogsRepositoryMock) Get(ctx context.Context, id string) (chan events.LogResponse, error) {
ch := make(chan events.LogResponse, 10)
defer close(ch)
for i := 0; i < count; i++ {
ch <- events.LogResponse{Log: events.Log{Time: time.Now(), Content: fmt.Sprintf("test %d", i), Error: false, Type: "test", Source: "test", Metadata: map[string]string{"test": "test"}}}
}
- return ch
+ return ch, nil
}
diff --git a/pkg/logs/repository/factory.go b/pkg/logs/repository/factory.go
index b6b0f2d853f..5ac80512910 100644
--- a/pkg/logs/repository/factory.go
+++ b/pkg/logs/repository/factory.go
@@ -16,7 +16,7 @@ type Factory interface {
type JsMinioFactory struct {
minio *minio.Client
- js client.Client
+ js client.StreamGetter
}
func (b JsMinioFactory) GetRepository(s state.LogState) (LogsRepository, error) {
diff --git a/pkg/logs/repository/interface.go b/pkg/logs/repository/interface.go
index 376b6050097..845ad7feef3 100644
--- a/pkg/logs/repository/interface.go
+++ b/pkg/logs/repository/interface.go
@@ -15,5 +15,5 @@ type RepositoryBuilder interface {
// LogsRepository is the repository primitive to get logs from
type LogsRepository interface {
- Get(ctx context.Context, id string) chan events.LogResponse
+ Get(ctx context.Context, id string) (chan events.LogResponse, error)
}
diff --git a/pkg/logs/repository/jetstream.go b/pkg/logs/repository/jetstream.go
index a538fa57c04..b11305e93c4 100644
--- a/pkg/logs/repository/jetstream.go
+++ b/pkg/logs/repository/jetstream.go
@@ -9,15 +9,15 @@ import (
var _ LogsRepository = &JetstreamLogsRepository{}
-func NewJetstreamRepository(client client.Client) LogsRepository {
+func NewJetstreamRepository(client client.StreamGetter) LogsRepository {
return JetstreamLogsRepository{c: client}
}
// Jet
type JetstreamLogsRepository struct {
- c client.Client
+ c client.StreamGetter
}
-func (r JetstreamLogsRepository) Get(ctx context.Context, id string) chan events.LogResponse {
+func (r JetstreamLogsRepository) Get(ctx context.Context, id string) (chan events.LogResponse, error) {
return r.c.Get(ctx, id)
}
diff --git a/pkg/logs/repository/minio.go b/pkg/logs/repository/minio.go
index 7adfe5c55ff..c9a26ffb8e2 100644
--- a/pkg/logs/repository/minio.go
+++ b/pkg/logs/repository/minio.go
@@ -14,7 +14,7 @@ func NewMinioRepository(minio *minio.Client) LogsRepository {
type MinioLogsRepository struct {
}
-func (r MinioLogsRepository) Get(ctx context.Context, id string) chan events.LogResponse {
+func (r MinioLogsRepository) Get(ctx context.Context, id string) (chan events.LogResponse, error) {
ch := make(chan events.LogResponse, 100)
- return ch
+ return ch, nil
}
From 86ced8b3cedc55f98a51ece08b7884450c075211 Mon Sep 17 00:00:00 2001
From: Tomasz Konieczny
Date: Wed, 24 Jan 2024 14:40:46 +0100
Subject: [PATCH 037/234] feat: executor tests - JMeterd special cases -
incorrect filename (#4935)
* executor tests - jmeter special cases extended - incorrect file name
* empty lines added
---
.../executor-tests/crd/special-cases.yaml | 33 +++++++++++++++++++
.../special-cases/jmeter-special-cases.yaml | 3 ++
2 files changed, 36 insertions(+)
diff --git a/test/jmeter/executor-tests/crd/special-cases.yaml b/test/jmeter/executor-tests/crd/special-cases.yaml
index 98fa4a7ee03..8a9eacf13e4 100644
--- a/test/jmeter/executor-tests/crd/special-cases.yaml
+++ b/test/jmeter/executor-tests/crd/special-cases.yaml
@@ -277,3 +277,36 @@ spec:
limits:
cpu: 500m
memory: 512Mi
+---
+apiVersion: tests.testkube.io/v3
+kind: Test
+metadata:
+ name: jmeterd-executor-smoke-incorrect-file-path-negative
+ labels:
+ core-tests: special-cases-jmeter
+spec:
+ type: jmeterd/test
+ content:
+ type: git
+ repository:
+ type: git
+ uri: https://github.com/kubeshop/testkube.git
+ branch: main
+ path: test/jmeter/executor-tests
+ executionRequest:
+ negativeTest: true
+ args:
+ - "-t"
+ - "/data/repo/test/jmeter/executor-tests/some-incorrect-file-name.jmx"
+ - "-o"
+ - "/data/output/custom-report.jtl"
+ jobTemplate: "apiVersion: batch/v1\nkind: Job\nspec:\n template:\n spec:\n containers:\n - name: \"{{ .Name }}\"\n image: {{ .Image }}\n resources:\n requests:\n memory: 512Mi\n cpu: 512m\n"
+ activeDeadlineSeconds: 180
+ slavePodRequest:
+ resources:
+ requests:
+ cpu: 400m
+ memory: 512Mi
+ limits:
+ cpu: 500m
+ memory: 512Mi
diff --git a/test/suites/special-cases/jmeter-special-cases.yaml b/test/suites/special-cases/jmeter-special-cases.yaml
index 6e8b7c0d1b3..1ae0bf4d2bd 100644
--- a/test/suites/special-cases/jmeter-special-cases.yaml
+++ b/test/suites/special-cases/jmeter-special-cases.yaml
@@ -28,3 +28,6 @@ spec:
- stopOnFailure: false
execute:
- test: jmeterd-executor-smoke-directory-t-o-slaves-2
+ - stopOnFailure: false
+ execute:
+ - test: jmeterd-executor-smoke-incorrect-file-path-negative
From 2d829dc91ea8bfb050716ec46b391ea33778787e Mon Sep 17 00:00:00 2001
From: Ale <93217218+alelthomas@users.noreply.github.com>
Date: Wed, 24 Jan 2024 13:14:27 -0500
Subject: [PATCH 038/234] docs: add demo to docs overview (#4938)
* add demo to docs overview
* add demo to docs overview
---
docs/docs/index.mdx | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx
index 519e605ea00..70787ce2844 100644
--- a/docs/docs/index.mdx
+++ b/docs/docs/index.mdx
@@ -10,6 +10,11 @@ This is the place where you'll find everything you need to get ramped up and sta
Testkube is a Kubernetes-native testing framework for Testers, Developers, and DevOps practitioners that allows you to automate the executions of your existing testing tools inside your Kubernetes cluster, removing all the complexity from your CI/CD pipelines.
+
+
## Try It Out!
export const DocCardList = (input) => (
From e51705909bbd304ab713d6be6e2751a9c54b3432 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Thu, 25 Jan 2024 09:50:27 +0100
Subject: [PATCH 039/234] chore: refactor of stop function (#4934)
* feat: pass execution config to the logs
* fix: there is no map string any in grpc
* fix: rollback to map string string
* fix: improved logs message
* fix: stop response async
* fix: tests
* fix: added test source to the log output
* fix: refactored stop function with retries and stream and consumer comparison
* fix: refactor of stop function with messages occurence handling
---
pkg/logs/events.go | 152 ++++++++++++++++++--------------
pkg/logs/events/events.go | 4 +
pkg/logs/events_test.go | 53 +++++++----
pkg/logs/service.go | 10 +--
pkg/scheduler/test_scheduler.go | 27 +++++-
5 files changed, 152 insertions(+), 94 deletions(-)
diff --git a/pkg/logs/events.go b/pkg/logs/events.go
index 2e92aae3da1..91bb1c59fe4 100644
--- a/pkg/logs/events.go
+++ b/pkg/logs/events.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "sync"
"time"
"github.com/nats-io/nats.go"
@@ -28,6 +29,8 @@ const (
)
type Consumer struct {
+ // Name of the consumer
+ Name string
// Context is a consumer context you can call Stop() method on it when no more messages are expected
Context jetstream.ConsumeContext
// Instance is a NATS consumer instance
@@ -37,8 +40,8 @@ type Consumer struct {
func (ls *LogsService) initConsumer(ctx context.Context, a adapter.Adapter, streamName, id string, i int) (jetstream.Consumer, error) {
name := fmt.Sprintf("lc%s%s%d", id, a.Name(), i)
return ls.js.CreateOrUpdateConsumer(ctx, streamName, jetstream.ConsumerConfig{
- Name: name,
- Durable: name,
+ Name: name,
+ // Durable: name,
// FilterSubject: streamName,
DeliverPolicy: jetstream.DeliverAllPolicy,
})
@@ -127,6 +130,7 @@ func (ls *LogsService) handleStart(ctx context.Context) func(msg *nats.Msg) {
// store consumer instance so we can stop it later in StopSubject handler
ls.consumerInstances.Store(event.Id+"_"+adapter.Name(), Consumer{
+ Name: event.Id + "_" + adapter.Name(),
Context: cons,
Instance: c,
})
@@ -147,15 +151,14 @@ func (ls *LogsService) handleStart(ctx context.Context) func(msg *nats.Msg) {
// handleStop will handle stop event and stop logs consumers, also clean consumers state
func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
return func(msg *nats.Msg) {
- ls.log.Debugw("got stop event")
+ var (
+ wg sync.WaitGroup
+ stopped = 0
+ event = events.Trigger{}
+ )
- t := time.NewTicker(ls.stopWaitTime)
- select {
- case <-t.C:
- case <-ctx.Done():
- }
+ ls.log.Debugw("got stop event")
- event := events.Trigger{}
err := json.Unmarshal(msg.Data, &event)
if err != nil {
ls.log.Errorw("can't handle stop event", "error", err)
@@ -164,73 +167,86 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
l := ls.log.With("id", event.Id, "event", "stop")
- maxTries := 10
- repeated := 0
+ err = msg.Respond([]byte("stop-queued"))
+ if err != nil {
+ l.Errorw("error responding to stop event", "error", err)
+ }
- toDelete := []string{}
- deleted := false
for _, adapter := range ls.adapters {
- toDelete = append(toDelete, event.Id+"_"+adapter.Name())
- }
+ consumerName := event.Id + "_" + adapter.Name()
- consumerDeleteWaitInterval := 5 * time.Second
-
- for {
- loop:
- // Delete each consumer for given execution id
- for i, name := range toDelete {
- // load consumer and check if has pending messages
- c, found := ls.consumerInstances.Load(name)
- if !found {
- l.Debugw("consumer not found on this pod", "found", found, "name", name)
- toDelete = append(toDelete[:i], toDelete[i+1:]...)
- goto loop // rewrite toDelete and start again
- }
-
- consumer := c.(Consumer)
-
- info, err := consumer.Instance.Info(ctx)
- if err != nil {
- l.Errorw("error getting consumer info", "error", err, "id", event.Id)
- continue
- }
-
- // finally delete consumer
- if info.NumPending == 0 {
- if !deleted {
- deleted = true
- }
- consumer.Context.Stop()
- ls.consumerInstances.Delete(name)
- toDelete = append(toDelete[:i], toDelete[i+1:]...)
- l.Infow("stopping consumer", "id", name)
- goto loop // rewrite toDelete and start again
- }
+ // locate consumer on this pod
+ c, found := ls.consumerInstances.Load(consumerName)
+ l.Debugw("consumer instance", "c", c, "found", found, "name", consumerName)
+ if !found {
+ l.Debugw("consumer not found on this pod", "found", found, "name", consumerName)
+ continue
}
- if len(toDelete) == 0 && !deleted {
- l.Debugw("no consumers on this pod registered for id", "id", event.Id)
- return
- } else if len(toDelete) == 0 {
- ls.state.Put(ctx, event.Id, state.LogStateFinished)
- l.Infow("execution logs consumers stopped", "id", event.Id)
- err = msg.Respond([]byte("stopped"))
- if err != nil {
- l.Errorw("error responding to stop event", "error", err)
- return
- }
- return
- }
+ // stop consumer
+ wg.Add(1)
+ stopped++
+ consumer := c.(Consumer)
- // handle max tries of cleaning executors
- repeated++
- if repeated >= maxTries {
- l.Errorw("error cleaning consumeres after max tries", "toDeleteLeft", toDelete, "tries", repeated)
- return
- }
+ go ls.stopConsumer(ctx, &wg, consumer)
+ }
+
+ wg.Wait()
+ l.Debugw("wait completed")
+
+ if stopped > 0 {
+ ls.state.Put(ctx, event.Id, state.LogStateFinished)
+ l.Infow("execution logs consumers stopped", "id", event.Id, "stopped", stopped)
+ } else {
+ l.Debugw("no consumers found on this pod to stop")
+ }
- time.Sleep(consumerDeleteWaitInterval)
+ }
+}
+
+func (ls *LogsService) stopConsumer(ctx context.Context, wg *sync.WaitGroup, consumer Consumer) {
+ defer wg.Done()
+
+ var (
+ info *jetstream.ConsumerInfo
+ err error
+ l = ls.log
+ retries = 0
+ maxRetries = 50
+ )
+
+ l.Debugw("stopping consumer", "name", consumer.Name)
+
+ for {
+ info, err = consumer.Instance.Info(ctx)
+ if err != nil {
+ l.Errorw("error getting consumer info", "error", err, "name", consumer.Name)
+ return
}
+
+ nothingToProcess := info.NumAckPending == 0 && info.NumPending == 0
+ messagesDelivered := info.Delivered.Consumer > 0 && info.Delivered.Stream > 0
+
+ l.Debugw("consumer info", "nothingToProcess", nothingToProcess, "messagesDelivered", messagesDelivered, "info", info)
+
+ // check if there was some messages processed
+ if nothingToProcess && messagesDelivered {
+ consumer.Context.Stop()
+ ls.consumerInstances.Delete(consumer.Name)
+ l.Infow("stopping and removing consumer", "name", consumer.Name, "consumerSeq", info.Delivered.Consumer, "streamSeq", info.Delivered.Stream, "last", info.Delivered.Last)
+ return
+ }
+
+ // retry if there is no messages processed as there could be slower logs
+ retries++
+ if retries >= maxRetries {
+ l.Errorw("error stopping consumer", "error", err, "name", consumer.Name, "consumerSeq", info.Delivered.Consumer, "streamSeq", info.Delivered.Stream, "last", info.Delivered.Last)
+ return
+ }
+
+ // pause a little bit
+ l.Debugw("waiting for consumer to finish", "name", consumer.Name, "retries", retries, "consumerSeq", info.Delivered.Consumer, "streamSeq", info.Delivered.Stream, "last", info.Delivered.Last)
+ time.Sleep(ls.stopPauseInterval)
}
}
diff --git a/pkg/logs/events/events.go b/pkg/logs/events/events.go
index 032299405d2..6697c978d0a 100644
--- a/pkg/logs/events/events.go
+++ b/pkg/logs/events/events.go
@@ -23,6 +23,8 @@ const (
LogVersionV1 LogVersion = "v1"
// v2 - raw binary format, timestamps are based on Kubernetes logs, line is raw log line
LogVersionV2 LogVersion = "v2"
+
+ JobPodLogSource = "job-pod"
)
type LogResponse struct {
@@ -142,6 +144,7 @@ func NewLogResponseFromBytes(b []byte) Log {
Type: o.Type_,
Error: true,
Version: LogVersionV1,
+ Source: JobPodLogSource,
}
}
@@ -171,5 +174,6 @@ func NewLogResponseFromBytes(b []byte) Log {
Time: ts,
Content: string(b),
Version: LogVersionV2,
+ Source: JobPodLogSource,
}
}
diff --git a/pkg/logs/events_test.go b/pkg/logs/events_test.go
index 68c084d644a..6986c0154d6 100644
--- a/pkg/logs/events_test.go
+++ b/pkg/logs/events_test.go
@@ -17,7 +17,7 @@ import (
"github.com/kubeshop/testkube/pkg/logs/state"
)
-var waitTime = time.Millisecond * 100
+var waitTime = time.Second
func TestLogs_EventsFlow(t *testing.T) {
t.Parallel()
@@ -31,6 +31,8 @@ func TestLogs_EventsFlow(t *testing.T) {
ns, nc := bus.TestServerWithConnection()
defer ns.Shutdown()
+ id := "stop-test"
+
// and jetstream configured
js, err := jetstream.New(nc)
assert.NoError(t, err)
@@ -45,8 +47,7 @@ func TestLogs_EventsFlow(t *testing.T) {
// and initialized log service
log := NewLogsService(nc, js, state).
- WithRandomPort().
- WithStopWaitTime(waitTime)
+ WithRandomPort()
// given example adapters
a := NewMockAdapter("aaa")
@@ -69,22 +70,25 @@ func TestLogs_EventsFlow(t *testing.T) {
assert.NoError(t, err)
// and initialized log stream for given ID
- meta, err := stream.Init(ctx, "stop-test")
+ meta, err := stream.Init(ctx, id)
assert.NotEmpty(t, meta.Name)
assert.NoError(t, err)
// when start event triggered
- _, err = stream.Start(ctx, "stop-test")
+ _, err = stream.Start(ctx, id)
assert.NoError(t, err)
// and when data pushed to the log stream
- stream.Push(ctx, "stop-test", events.NewLogResponse(time.Now(), []byte("hello 1")))
- stream.Push(ctx, "stop-test", events.NewLogResponse(time.Now(), []byte("hello 2")))
+ stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
+ stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 2")))
// and stop event triggered
- _, err = stream.Stop(ctx, "stop-test")
+ _, err = stream.Stop(ctx, id)
assert.NoError(t, err)
+ // cooldown stop time
+ time.Sleep(waitTime * 2)
+
// then all adapters should be gracefully stopped
assert.Equal(t, 0, log.GetConsumersStats(ctx).Count)
})
@@ -98,6 +102,8 @@ func TestLogs_EventsFlow(t *testing.T) {
ns, nc := bus.TestServerWithConnection()
defer ns.Shutdown()
+ id := "messages-test"
+
// and jetstream configured
js, err := jetstream.New(nc)
assert.NoError(t, err)
@@ -112,8 +118,7 @@ func TestLogs_EventsFlow(t *testing.T) {
// and initialized log service
log := NewLogsService(nc, js, state).
- WithRandomPort().
- WithStopWaitTime(waitTime)
+ WithRandomPort()
// given example adapter
a := NewMockAdapter()
@@ -139,22 +144,22 @@ func TestLogs_EventsFlow(t *testing.T) {
assert.NoError(t, err)
// and initialized log stream for given ID
- meta, err := stream.Init(ctx, "messages-test")
+ meta, err := stream.Init(ctx, id)
assert.NotEmpty(t, meta.Name)
assert.NoError(t, err)
// when start event triggered
- _, err = stream.Start(ctx, "messages-test")
+ _, err = stream.Start(ctx, id)
assert.NoError(t, err)
for i := 0; i < messagesCount; i++ {
// and when data pushed to the log stream
- err = stream.Push(ctx, "messages-test", events.NewLogResponse(time.Now(), []byte("hello")))
+ err = stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello")))
assert.NoError(t, err)
}
// and wait for message to be propagated
- _, err = stream.Stop(ctx, "messages-test")
+ _, err = stream.Stop(ctx, id)
assert.NoError(t, err)
assertMessagesCount(t, a, 4*messagesCount)
@@ -174,6 +179,8 @@ func TestLogs_EventsFlow(t *testing.T) {
js, err := jetstream.New(nc)
assert.NoError(t, err)
+ id := "executionid1"
+
// and KV store
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "state-test"})
assert.NoError(t, err)
@@ -184,8 +191,7 @@ func TestLogs_EventsFlow(t *testing.T) {
// and initialized log service
log := NewLogsService(nc, js, state).
- WithRandomPort().
- WithStopWaitTime(waitTime)
+ WithRandomPort()
// given example adapters
a := NewMockAdapter("aaa")
@@ -208,21 +214,30 @@ func TestLogs_EventsFlow(t *testing.T) {
assert.NoError(t, err)
// and initialized log stream for given ID
- meta, err := stream.Init(ctx, "consumer-stats")
+ meta, err := stream.Init(ctx, id)
assert.NotEmpty(t, meta.Name)
assert.NoError(t, err)
// when start event triggered
- _, err = stream.Start(ctx, "consumer-stats")
+ _, err = stream.Start(ctx, id)
assert.NoError(t, err)
// then we should have 2 consumers
stats := log.GetConsumersStats(ctx)
assert.Equal(t, 2, stats.Count)
+ stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
+ stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
+ stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
+
// when stop event triggered
- _, err = stream.Stop(ctx, "consumer-stats")
+ r, err := stream.Stop(ctx, id)
assert.NoError(t, err)
+ assert.False(t, r.Error)
+ assert.Equal(t, "stop-queued", string(r.Message))
+
+ // there will be wait for mess
+ time.Sleep(waitTime * 2)
// then all adapters should be gracefully stopped
assert.Equal(t, 0, log.GetConsumersStats(ctx).Count)
diff --git a/pkg/logs/service.go b/pkg/logs/service.go
index b710d4f9b9a..12eff7a0dff 100644
--- a/pkg/logs/service.go
+++ b/pkg/logs/service.go
@@ -29,7 +29,7 @@ const (
DefaultHttpAddress = ":8080"
DefaultGrpcAddress = ":9090"
- DefaultStopWaitTime = 60 * time.Second // when stop event is faster than first message arrived
+ defaultStopPauseInterval = 200 * time.Millisecond
)
func NewLogsService(nats *nats.Conn, js jetstream.JetStream, state state.Interface) *LogsService {
@@ -43,7 +43,7 @@ func NewLogsService(nats *nats.Conn, js jetstream.JetStream, state state.Interfa
grpcAddress: DefaultGrpcAddress,
consumerInstances: sync.Map{},
state: state,
- stopWaitTime: DefaultStopWaitTime,
+ stopPauseInterval: defaultStopPauseInterval,
}
}
@@ -76,7 +76,7 @@ type LogsService struct {
state state.Interface
// stop wait time for messages cool down
- stopWaitTime time.Duration
+ stopPauseInterval time.Duration
}
// AddAdapter adds new adapter to logs service adapters will be configred based on given mode
@@ -149,8 +149,8 @@ func (ls *LogsService) WithGrpcAddress(address string) *LogsService {
return ls
}
-func (ls *LogsService) WithStopWaitTime(duration time.Duration) *LogsService {
- ls.stopWaitTime = duration
+func (ls *LogsService) WithPauseInterval(duration time.Duration) *LogsService {
+ ls.stopPauseInterval = duration
return ls
}
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index 43ca934cccc..b64a58ee678 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"path/filepath"
+ "strings"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
@@ -126,9 +127,31 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
s.logger.Infow("test started", "executionId", execution.Id, "status", execution.ExecutionResult.Status)
+ s.handleExecutionStart(ctx, execution)
+
return execution, nil
}
+func (s *Scheduler) handleExecutionStart(ctx context.Context, execution testkube.Execution) {
+ // pass here all needed execution data to the log
+ if s.featureFlags.LogsV2 {
+
+ l := events.NewLog(fmt.Sprintf("starting execution %s (%s)", execution.Name, execution.Id)).
+ WithType("execution-config").
+ WithVersion(events.LogVersionV2).
+ WithSource("test-scheduler")
+
+ // TODO try to store map[strin]any through protobuf for now it'll be map[string]string
+ l.WithMetadataEntry("command", strings.Join(execution.Command, " "))
+ l.WithMetadataEntry("argsmode", execution.ArgsMode)
+ l.WithMetadataEntry("args", strings.Join(execution.Args, " "))
+ l.WithMetadataEntry("pre-run", execution.PreRunScript)
+ l.WithMetadataEntry("post-run", execution.PostRunScript)
+
+ s.logsStream.Push(ctx, execution.Id, *l)
+ }
+}
+
func (s *Scheduler) handleExecutionError(ctx context.Context, execution testkube.Execution, msgTpl string, err error) (testkube.Execution, error) {
// push error log to the log stream if logs v2 enabled
if s.featureFlags.LogsV2 {
@@ -849,11 +872,11 @@ func (s *Scheduler) triggerLogsStopEvent(ctx context.Context, id string) error {
}
if r.Error {
- s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
+ s.logger.Errorw("received invalid response from log stream on stop event", "id", id, "response", r)
return
}
- s.logger.Infow("triggering logs stop event", "id", id)
+ s.logger.Infow("triggering logs stop event", "id", id, "response", string(r.Message))
}()
}
return nil
From 8ef2ccdafc902eaea16e8b9c3124c73437ec4666 Mon Sep 17 00:00:00 2001
From: Dejan Zele Pejchev
Date: Thu, 25 Jan 2024 13:50:05 +0100
Subject: [PATCH 040/234] jmeterd: add sanity checking for test file (#4947)
---
.../executor/jmeterd/pkg/runner/helpers.go | 23 ++++++-
.../jmeterd/pkg/runner/helpers_test.go | 31 +++++++++
contrib/executor/jmeterd/pkg/runner/runner.go | 33 +++++++--
.../jmeterd/pkg/runner/runner_test.go | 67 +++++++++++++++++++
4 files changed, 147 insertions(+), 7 deletions(-)
diff --git a/contrib/executor/jmeterd/pkg/runner/helpers.go b/contrib/executor/jmeterd/pkg/runner/helpers.go
index 6dfa6b85fc7..e83947748d1 100644
--- a/contrib/executor/jmeterd/pkg/runner/helpers.go
+++ b/contrib/executor/jmeterd/pkg/runner/helpers.go
@@ -18,6 +18,11 @@ const (
envVarPrefix = "$"
)
+var (
+ ErrParamMissingValue = errors.New("no value found for parameter")
+ ErrMissingParam = errors.New("parameter not found")
+)
+
func getTestPathAndWorkingDir(fs filesystem.FileSystem, execution *testkube.Execution, dataDir string) (testPath string, workingDir, testFile string, err error) {
testPath, workingDir, err = content.GetPathAndWorkingDir(execution.Content, dataDir)
if err != nil {
@@ -41,7 +46,6 @@ func getTestPathAndWorkingDir(fs filesystem.FileSystem, execution *testkube.Exec
if err != nil || fileInfo.IsDir() {
output.PrintLogf("%s Could not find file %s in the directory, error: %s", ui.IconCross, testFile, err)
return "", "", "", errors.Wrapf(err, "could not find file %s in the directory", testFile)
-
}
}
return
@@ -71,7 +75,7 @@ func findTestFile(fs filesystem.FileSystem, execution *testkube.Execution, testP
}
}
if testFile == "" {
- output.PrintLogf("%s %s file not found in args or test path!", ui.IconCross, testExtension)
+ output.PrintLogf("%s %s file not found in args or test path!", ui.IconCross, testExtension)
return "", errors.Errorf("no %s file found", testExtension)
}
return testFile, nil
@@ -114,3 +118,18 @@ func injectAndExpandEnvVars(args []string, params []string) []string {
return copied
}
+
+// getParamValue searches for a parameter in the args slice and returns its value.
+// It returns an error if the parameter is not found or if it does not have an associated value.
+func getParamValue(args []string, param string) (string, error) {
+ for i, arg := range args {
+ if arg == param {
+ // Check if the next element exists
+ if i+1 < len(args) {
+ return args[i+1], nil
+ }
+ return "", errors.WithStack(ErrParamMissingValue)
+ }
+ }
+ return "", errors.WithStack(ErrMissingParam)
+}
diff --git a/contrib/executor/jmeterd/pkg/runner/helpers_test.go b/contrib/executor/jmeterd/pkg/runner/helpers_test.go
index 5625effcc0c..c91d3f7d7bf 100644
--- a/contrib/executor/jmeterd/pkg/runner/helpers_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/helpers_test.go
@@ -281,3 +281,34 @@ func TestInjectAndExpandEnvVars(t *testing.T) {
})
}
}
+
+func TestGetParamValue(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ args []string
+ param string
+ expected string
+ wantErr error
+ }{
+ {name: "get last param successfully", args: []string{"-n", "-o", "/data", "-t", "/data/repo"}, param: "-t", expected: "/data/repo", wantErr: nil},
+ {name: "get middle param successfully", args: []string{"-n", "-o", "/data", "-t", "/data/repo"}, param: "-o", expected: "/data", wantErr: nil},
+ {name: "param missing value returns error", args: []string{"-n", "-o", "/data", "-t"}, param: "-t", expected: "", wantErr: ErrParamMissingValue},
+ {name: "param missing", args: []string{"-n", "-o", "/data", "-t", "/data/repo"}, param: "-x", expected: "", wantErr: ErrMissingParam},
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ value, err := getParamValue(tc.args, tc.param)
+ if tc.wantErr != nil {
+ assert.ErrorIs(t, err, tc.wantErr)
+ assert.Empty(t, value)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tc.expected, value)
+ }
+ })
+ }
+}
diff --git a/contrib/executor/jmeterd/pkg/runner/runner.go b/contrib/executor/jmeterd/pkg/runner/runner.go
index b1dfa26438a..a871d59c90b 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner.go
@@ -32,11 +32,10 @@ import (
type JMeterMode string
const (
- jmeterModeStandalone JMeterMode = "standalone"
- jmeterModeDistributed JMeterMode = "distributed"
- globalJMeterParamPrefix = "-G"
- standaloneJMeterParamPrefix = "-J"
- jmxExtension = "jmx"
+ jmeterModeStandalone JMeterMode = "standalone"
+ jmeterModeDistributed JMeterMode = "distributed"
+ jmxExtension = "jmx"
+ jmeterTestFileFlag = "-t"
)
// JMeterDRunner runner
@@ -158,6 +157,12 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
args = injectAndExpandEnvVars(args, nil)
output.PrintLogf("%s Using arguments: %v", ui.IconWorld, envManager.ObfuscateStringSlice(args))
+ // TODO: this is a workaround, the check should be ideally performed in the getTestPathAndWorkingDir function
+ if err := checkIfTestFileExists(r.fs, args); err != nil {
+ output.PrintLogf("%s Error validating test file exists: %v", ui.IconCross, err.Error())
+ return result, errors.WithStack(err)
+ }
+
entryPoint := getEntryPoint()
for i := range execution.Command {
if execution.Command[i] == "" {
@@ -231,7 +236,25 @@ func initSlaves(
return slaveClient.DeleteSlaves(ctx, slaveMeta)
}
return slaveMeta, cleanupFunc, nil
+}
+
+func checkIfTestFileExists(fs filesystem.FileSystem, args []string) error {
+ if len(args) == 0 {
+ return errors.New("no arguments provided")
+ }
+ testParamValue, err := getParamValue(args, jmeterTestFileFlag)
+ if err != nil {
+ return errors.Wrapf(err, "error extracting value for %s flag", jmeterTestFileFlag)
+ }
+ info, err := fs.Stat(testParamValue)
+ if err != nil {
+ return errors.WithStack(err)
+ }
+ if info.IsDir() {
+ return errors.Errorf("test file %s is a directory", testParamValue)
+ }
+ return nil
}
func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool, result []string) {
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index dd3755dd61b..190b4eb9a35 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -6,6 +6,11 @@ import (
"path/filepath"
"testing"
+ "github.com/golang/mock/gomock"
+ "github.com/pkg/errors"
+
+ "github.com/kubeshop/testkube/pkg/filesystem"
+
"github.com/kubeshop/testkube/pkg/utils/test"
"github.com/stretchr/testify/assert"
@@ -14,6 +19,68 @@ import (
"github.com/kubeshop/testkube/pkg/envs"
)
+func TestCheckIfTestFileExists(t *testing.T) {
+ t.Parallel()
+
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ testCases := []struct {
+ name string
+ args []string
+ setupMock func(*filesystem.MockFileSystem)
+ expectError bool
+ }{
+ {
+ name: "no arguments",
+ args: []string{},
+ setupMock: func(mockFS *filesystem.MockFileSystem) {},
+ expectError: true,
+ },
+ {
+ name: "file does not exist",
+ args: []string{"-t", "test.txt"},
+ setupMock: func(mockFS *filesystem.MockFileSystem) {
+ mockFS.EXPECT().Stat("test.txt").Return(nil, errors.New("file not found"))
+ },
+ expectError: true,
+ },
+ {
+ name: "file is a directory",
+ args: []string{"-t", "testdir"},
+ setupMock: func(mockFS *filesystem.MockFileSystem) {
+ mockFileInfo := filesystem.MockFileInfo{FIsDir: true}
+ mockFS.EXPECT().Stat("testdir").Return(&mockFileInfo, nil)
+ },
+ expectError: true,
+ },
+ {
+ name: "file exists",
+ args: []string{"-t", "test.txt"},
+ setupMock: func(mockFS *filesystem.MockFileSystem) {
+ mockFileInfo := filesystem.MockFileInfo{FName: "test.txt", FSize: 100}
+ mockFS.EXPECT().Stat("test.txt").Return(&mockFileInfo, nil)
+ },
+ expectError: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ mockFS := filesystem.NewMockFileSystem(mockCtrl)
+ tc.setupMock(mockFS)
+ err := checkIfTestFileExists(mockFS, tc.args)
+ if tc.expectError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
+
func TestPrepareArgsReplacements(t *testing.T) {
t.Parallel()
From 6306ad99be0ad65f6f326dc8987a882e18070dd8 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Thu, 25 Jan 2024 14:14:48 +0100
Subject: [PATCH 041/234] fix: use pointer everywhere when passing log chunk
(#4943)
* fix: use pointer everywhere when passing log chunk
* chore: refactor method name - no response prefix anymore
* fix: added source to old logs
* fix: cleaning how source is set for logs in proxy
* fix: pass feature flags to the executor
* chore: rename
* fix: passed features to constructors
* fix: streamLogs method in job executor
---
cmd/api-server/main.go | 15 +++--
internal/app/api/v1/server.go | 4 ++
pkg/executor/client/job.go | 52 +++++++++++++++--
.../containerexecutor/containerexecutor.go | 7 +++
pkg/executor/output/parser.go | 13 +++--
pkg/executor/output/parser_test.go | 18 +++---
pkg/logs/client/interface.go | 4 +-
.../client/mock_initializedstreampusher.go | 2 +-
pkg/logs/client/mock_stream.go | 2 +-
pkg/logs/client/stream.go | 8 +--
pkg/logs/events/events.go | 56 +++++++++++++------
pkg/logs/events_test.go | 12 ++--
pkg/logs/sidecar/proxy.go | 21 +++----
pkg/scheduler/test_scheduler.go | 18 +++---
14 files changed, 156 insertions(+), 76 deletions(-)
diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go
index b7aa3fc7402..d9978508a21 100644
--- a/cmd/api-server/main.go
+++ b/cmd/api-server/main.go
@@ -133,10 +133,10 @@ func main() {
cfg.CleanLegacyVars()
ui.ExitOnError("error getting application config", err)
- ff, err := featureflags.Get()
+ features, err := featureflags.Get()
ui.ExitOnError("error getting application feature flags", err)
- log.DefaultLogger.Infow("Feature flags configured", "ff", ff)
+ log.DefaultLogger.Infow("Feature flags configured", "ff", features)
// Run services within an errgroup to propagate errors between services.
g, ctx := errgroup.WithContext(context.Background())
@@ -350,7 +350,7 @@ func main() {
var logsStream logsclient.Stream
- if ff.LogsV2 {
+ if features.LogsV2 {
logsStream, err = logsclient.NewNatsLogStream(nc.Conn)
if err != nil {
ui.ExitOnError("Creating logs streaming client", err)
@@ -394,6 +394,8 @@ func main() {
"http://"+cfg.APIServerFullname+":"+cfg.APIServerPort,
cfg.NatsURI,
cfg.Debug,
+ logsStream,
+ features,
)
if err != nil {
ui.ExitOnError("Creating executor client", err)
@@ -424,6 +426,8 @@ func main() {
"http://"+cfg.APIServerFullname+":"+cfg.APIServerPort,
cfg.NatsURI,
cfg.Debug,
+ logsStream,
+ features,
)
if err != nil {
ui.ExitOnError("Creating container executor", err)
@@ -447,7 +451,7 @@ func main() {
testsuiteExecutionsClient,
eventBus,
cfg.TestkubeDashboardURI,
- ff,
+ features,
logsStream,
)
@@ -486,7 +490,8 @@ func main() {
mode,
eventBus,
cfg.EnableSecretsEndpoint,
- ff,
+ features,
+ logsStream,
)
if mode == common.ModeAgent {
diff --git a/internal/app/api/v1/server.go b/internal/app/api/v1/server.go
index 6204fbdbb19..f4d96d26dfb 100644
--- a/internal/app/api/v1/server.go
+++ b/internal/app/api/v1/server.go
@@ -44,6 +44,7 @@ import (
"github.com/kubeshop/testkube/pkg/event/kind/webhook"
ws "github.com/kubeshop/testkube/pkg/event/kind/websocket"
"github.com/kubeshop/testkube/pkg/executor/client"
+ logsclient "github.com/kubeshop/testkube/pkg/logs/client"
"github.com/kubeshop/testkube/pkg/oauth"
"github.com/kubeshop/testkube/pkg/scheduler"
"github.com/kubeshop/testkube/pkg/secret"
@@ -89,6 +90,7 @@ func NewTestkubeAPI(
eventsBus bus.Bus,
enableSecretsEndpoint bool,
ff featureflags.FeatureFlags,
+ logsStream logsclient.Stream,
) TestkubeAPI {
var httpConfig server.Config
@@ -134,6 +136,7 @@ func NewTestkubeAPI(
eventsBus: eventsBus,
enableSecretsEndpoint: enableSecretsEndpoint,
featureFlags: ff,
+ logsStream: logsStream,
}
// will be reused in websockets handler
@@ -191,6 +194,7 @@ type TestkubeAPI struct {
eventsBus bus.Bus
enableSecretsEndpoint bool
featureFlags featureflags.FeatureFlags
+ logsStream logsclient.Stream
}
type storageParams struct {
diff --git a/pkg/executor/client/job.go b/pkg/executor/client/job.go
index 26f1813f96a..dc9a49739e5 100644
--- a/pkg/executor/client/job.go
+++ b/pkg/executor/client/job.go
@@ -44,6 +44,8 @@ import (
"github.com/kubeshop/testkube/pkg/executor/env"
"github.com/kubeshop/testkube/pkg/executor/output"
"github.com/kubeshop/testkube/pkg/log"
+ logsclient "github.com/kubeshop/testkube/pkg/logs/client"
+ "github.com/kubeshop/testkube/pkg/logs/events"
testexecutionsmapper "github.com/kubeshop/testkube/pkg/mapper/testexecutions"
testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests"
"github.com/kubeshop/testkube/pkg/telemetry"
@@ -93,6 +95,8 @@ func NewJobExecutor(
apiURI string,
natsURI string,
debug bool,
+ logsStream logsclient.Stream,
+ features featureflags.FeatureFlags,
) (client *JobExecutor, err error) {
return &JobExecutor{
ClientSet: clientset,
@@ -115,6 +119,8 @@ func NewJobExecutor(
apiURI: apiURI,
natsURI: natsURI,
debug: debug,
+ logsStream: logsStream,
+ features: features,
}, nil
}
@@ -145,6 +151,8 @@ type JobExecutor struct {
apiURI string
natsURI string
debug bool
+ logsStream logsclient.Stream
+ features featureflags.FeatureFlags
}
type JobOptions struct {
@@ -225,6 +233,9 @@ func (c *JobExecutor) Execute(ctx context.Context, execution *testkube.Execution
if err != nil {
return result.Err(err), err
}
+
+ c.streamLog(ctx, execution.Id, events.NewLog("created kubernetes job").WithSource(events.SourceJobExecutor))
+
if !options.Sync {
go c.MonitorJobForTimeout(ctx, execution.Id)
}
@@ -237,6 +248,8 @@ func (c *JobExecutor) Execute(ctx context.Context, execution *testkube.Execution
l := c.Log.With("executionID", execution.Id, "type", "async")
+ c.streamLog(ctx, execution.Id, events.NewLog("waiting for pod to spin up").WithSource(events.SourceJobExecutor))
+
for _, pod := range pods.Items {
if pod.Status.Phase != corev1.PodRunning && pod.Labels["job-name"] == execution.Id {
// for sync block and complete
@@ -258,7 +271,7 @@ func (c *JobExecutor) Execute(ctx context.Context, execution *testkube.Execution
l.Debugw("no pods was found", "totalPodsCount", len(pods.Items))
- return testkube.NewRunningExecutionResult(), nil
+ return result, nil
}
func (c *JobExecutor) MonitorJobForTimeout(ctx context.Context, jobName string) {
@@ -282,7 +295,7 @@ func (c *JobExecutor) MonitorJobForTimeout(ctx context.Context, jobName string)
job := jobs.Items[0]
if job.Status.Succeeded > 0 {
- l.Debugw("job succeeded", "status")
+ l.Debugw("job succeeded", "status", "succeded")
return
}
@@ -347,12 +360,14 @@ func (c *JobExecutor) updateResultsFromPod(ctx context.Context, pod corev1.Pod,
// save stop time and final state
defer func() {
if err := c.stopExecution(ctx, l, execution, execution.ExecutionResult, isNegativeTest, err); err != nil {
+ c.streamLog(ctx, execution.Id, events.NewErrorLog(err))
l.Errorw("error stopping execution after updating results from pod", "error", err)
}
}()
// wait for pod to be loggable
if err = wait.PollUntilContextTimeout(ctx, pollInterval, c.podStartTimeout, true, executor.IsPodLoggable(c.ClientSet, pod.Name, c.Namespace)); err != nil {
+ c.streamLog(ctx, execution.Id, events.NewErrorLog(errors.Wrap(err, "can't start test job pod")))
l.Errorw("waiting for pod started error", "error", err)
}
@@ -360,13 +375,16 @@ func (c *JobExecutor) updateResultsFromPod(ctx context.Context, pod corev1.Pod,
// wait for pod
if err = wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, true, executor.IsPodReady(c.ClientSet, pod.Name, c.Namespace)); err != nil {
// continue on poll err and try to get logs later
+ c.streamLog(ctx, execution.Id, events.NewErrorLog(errors.Wrap(err, "can't read data from pod, pod was not completed")))
l.Errorw("waiting for pod complete error", "error", err)
}
+
if err != nil {
execution.ExecutionResult.Err(err)
}
l.Debug("poll immediate end")
+ c.streamLog(ctx, execution.Id, events.NewLog("analyzing test results and artfacts"))
if execution.ArtifactRequest != nil &&
execution.ArtifactRequest.StorageClassName != "" {
pvcsClient := c.ClientSet.CoreV1().PersistentVolumeClaims(c.Namespace)
@@ -380,13 +398,17 @@ func (c *JobExecutor) updateResultsFromPod(ctx context.Context, pod corev1.Pod,
logs, err = executor.GetPodLogs(ctx, c.ClientSet, c.Namespace, pod)
if err != nil {
l.Errorw("get pod logs error", "error", err)
+ c.streamLog(ctx, execution.Id, events.NewErrorLog(err))
return execution.ExecutionResult, err
}
+ // attachLogs only for previous version of logs, they are not needed here as will be passed from other sources
+ attachLogs := !c.features.LogsV2
// parse job output log (JSON stream)
- execution.ExecutionResult, err = output.ParseRunnerOutput(logs)
+ execution.ExecutionResult, err = output.ParseRunnerOutput(logs, attachLogs)
if err != nil {
l.Errorw("parse output error", "error", err)
+ c.streamLog(ctx, execution.Id, events.NewErrorLog(errors.Wrap(err, "can't get test execution job output")))
return execution.ExecutionResult, err
}
@@ -397,6 +419,10 @@ func (c *JobExecutor) updateResultsFromPod(ctx context.Context, pod corev1.Pod,
}
execution.ExecutionResult.ErrorMessage = errorMessage
+
+ c.streamLog(ctx, execution.Id, events.NewErrorLog(errors.Wrap(err, "test execution finished with failed state")))
+ } else {
+ c.streamLog(ctx, execution.Id, events.NewLog("test execution finshed").WithMetadataEntry("status", string(*execution.ExecutionResult.Status)))
}
// saving result in the defer function
@@ -409,21 +435,28 @@ func (c *JobExecutor) stopExecution(ctx context.Context, l *zap.SugaredLogger, e
l.Errorw("get execution error", "error", err)
return err
}
+
+ logEvent := events.NewLog().WithSource(events.SourceJobExecutor)
+
l.Debugw("stopping execution", "executionId", execution.Id, "status", result.Status, "executionStatus", execution.ExecutionResult.Status, "passedError", passedErr, "savedExecutionStatus", savedExecution.ExecutionResult.Status)
+ c.streamLog(ctx, execution.Id, logEvent.WithContent("stopping execution"))
+ defer c.streamLog(ctx, execution.Id, logEvent.WithContent("execution stopped"))
+
if savedExecution.IsCanceled() || savedExecution.IsTimeout() {
+ c.streamLog(ctx, execution.Id, logEvent.WithContent("execution is cancelled"))
return nil
}
execution.Stop()
if isNegativeTest {
if result.IsFailed() {
- l.Infow("test run was expected to fail, and it failed as expected", "test", execution.TestName)
+ l.Debugw("test run was expected to fail, and it failed as expected", "test", execution.TestName)
execution.ExecutionResult.Status = testkube.ExecutionStatusPassed
result.Status = testkube.ExecutionStatusPassed
result.Output = result.Output + "\nTest run was expected to fail, and it failed as expected"
} else {
- l.Infow("test run was expected to fail - the result will be reversed", "test", execution.TestName)
+ l.Debugw("test run was expected to fail - the result will be reversed", "test", execution.TestName)
execution.ExecutionResult.Status = testkube.ExecutionStatusFailed
result.Status = testkube.ExecutionStatusFailed
result.Output = result.Output + "\nTest run was expected to fail, the result will be reversed"
@@ -738,6 +771,9 @@ func (c *JobExecutor) Timeout(ctx context.Context, jobName string) (result *test
l.Errorw("error getting execution", "error", err)
return
}
+
+ c.streamLog(ctx, execution.Id, events.NewLog("execution took too long, pod deadline exceeded"))
+
result = &testkube.ExecutionResult{
Status: testkube.ExecutionStatusTimeout,
}
@@ -748,6 +784,12 @@ func (c *JobExecutor) Timeout(ctx context.Context, jobName string) (result *test
return
}
+func (c *JobExecutor) streamLog(ctx context.Context, id string, log *events.Log) {
+ if c.features.LogsV2 {
+ c.logsStream.Push(ctx, id, log)
+ }
+}
+
// NewJobSpec is a method to create new job spec
func NewJobSpec(log *zap.SugaredLogger, options JobOptions) (*batchv1.Job, error) {
envManager := env.NewManager()
diff --git a/pkg/executor/containerexecutor/containerexecutor.go b/pkg/executor/containerexecutor/containerexecutor.go
index 875825fed1e..00f5f12e7ac 100644
--- a/pkg/executor/containerexecutor/containerexecutor.go
+++ b/pkg/executor/containerexecutor/containerexecutor.go
@@ -31,6 +31,7 @@ import (
"github.com/kubeshop/testkube/pkg/executor/output"
"github.com/kubeshop/testkube/pkg/k8sclient"
"github.com/kubeshop/testkube/pkg/log"
+ logsclient "github.com/kubeshop/testkube/pkg/logs/client"
testexecutionsmapper "github.com/kubeshop/testkube/pkg/mapper/testexecutions"
testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests"
"github.com/kubeshop/testkube/pkg/telemetry"
@@ -71,6 +72,8 @@ func NewContainerExecutor(
apiURI string,
natsUri string,
debug bool,
+ logsStream logsclient.Stream,
+ features featureflags.FeatureFlags,
) (client *ContainerExecutor, err error) {
clientSet, err := k8sclient.ConnectToK8s()
if err != nil {
@@ -99,6 +102,8 @@ func NewContainerExecutor(
apiURI: apiURI,
natsURI: natsUri,
debug: debug,
+ logsStream: logsStream,
+ features: features,
}, nil
}
@@ -129,6 +134,8 @@ type ContainerExecutor struct {
apiURI string
natsURI string
debug bool
+ logsStream logsclient.Stream
+ features featureflags.FeatureFlags
}
type JobOptions struct {
diff --git a/pkg/executor/output/parser.go b/pkg/executor/output/parser.go
index c3445334e8f..8372d09dd94 100644
--- a/pkg/executor/output/parser.go
+++ b/pkg/executor/output/parser.go
@@ -36,11 +36,13 @@ func GetLogEntry(b []byte) (out Output, err error) {
// {"type": "line", "message": "runner execution started ------------", "time": "..."}
// {"type": "line", "message": "GET /results", "time": "..."}
// {"type": "result", "result": {"id": "2323", "output": "-----"}, "time": "..."}
-func ParseRunnerOutput(b []byte) (*testkube.ExecutionResult, error) {
+func ParseRunnerOutput(b []byte, attachLogs bool) (*testkube.ExecutionResult, error) {
result := &testkube.ExecutionResult{}
if len(b) == 0 {
errMessage := "no logs found"
- result.Output = errMessage
+ if attachLogs {
+ result.Output = errMessage
+ }
return result.Err(errors.New(errMessage)), nil
}
logs, err := parseLogs(b)
@@ -69,7 +71,10 @@ func ParseRunnerOutput(b []byte) (*testkube.ExecutionResult, error) {
default:
result.Err(fmt.Errorf("wrong log type was found as last log: %v", log))
}
- result.Output = sanitizeLogs(logs)
+
+ if attachLogs {
+ result.Output = sanitizeLogs(logs)
+ }
return result, nil
}
@@ -308,7 +313,7 @@ func getResultMessage(result testkube.ExecutionResult) string {
return result.Output
}
- return fmt.Sprintf("%s", *result.Status)
+ return string(*result.Status)
}
// sameSeverity decides if a and b are of the same severity type
diff --git a/pkg/executor/output/parser_test.go b/pkg/executor/output/parser_test.go
index 045d3f033b3..cb842bb1f67 100644
--- a/pkg/executor/output/parser_test.go
+++ b/pkg/executor/output/parser_test.go
@@ -64,7 +64,7 @@ func TestParseRunnerOutput(t *testing.T) {
t.Run("Empty runner output", func(t *testing.T) {
t.Parallel()
- result, err := ParseRunnerOutput([]byte{})
+ result, err := ParseRunnerOutput([]byte{}, true)
assert.Equal(t, "no logs found", result.Output)
assert.NoError(t, err)
@@ -75,7 +75,7 @@ func TestParseRunnerOutput(t *testing.T) {
t.Parallel()
invalidOutput := []byte(`{not a json}`)
- result, err := ParseRunnerOutput(invalidOutput)
+ result, err := ParseRunnerOutput(invalidOutput, true)
expectedErrMessage := "ERROR can't get log entry: invalid character 'n' looking for beginning of object key string, ((({not a json})))"
assert.Equal(t, expectedErrMessage+"\n", result.Output)
@@ -100,7 +100,7 @@ func TestParseRunnerOutput(t *testing.T) {
{"type":"line","content":"\n # failure detail \n \n 1. Error \n connect ECONNREFUSED 127.0.0.1:8088 \n at request \n inside \"Health\" \n \n 2. AssertionError Status code is 200 \n expected { Object (id, _details, ...) } to have property 'code' \n at assertion:0 in test-script \n inside \"Health\" \n"}
{"type":"result","result":{"status":"failed","startTime":"2021-10-29T11:35:35.759Z","endTime":"2021-10-29T11:35:36.771Z","output":"newman\n\nLocal-API-Health\n\n→ Health\n GET http://localhost:8088/health [errored]\n connect ECONNREFUSED 127.0.0.1:8088\n 2. Status code is 200\n\n┌─────────────────────────┬──────────┬──────────┐\n│ │ executed │ failed │\n├─────────────────────────┼──────────┼──────────┤\n│ iterations │ 1 │ 0 │\n├─────────────────────────┼──────────┼──────────┤\n│ requests │ 1 │ 1 │\n├─────────────────────────┼──────────┼──────────┤\n│ test-scripts │ 1 │ 0 │\n├─────────────────────────┼──────────┼──────────┤\n│ prerequest-scripts │ 0 │ 0 │\n├─────────────────────────┼──────────┼──────────┤\n│ assertions │ 1 │ 1 │\n├─────────────────────────┴──────────┴──────────┤\n│ total run duration: 1012ms │\n├───────────────────────────────────────────────┤\n│ total data received: 0B (approx) │\n└───────────────────────────────────────────────┘\n\n # failure detail \n \n 1. Error \n connect ECONNREFUSED 127.0.0.1:8088 \n at request \n inside \"Health\" \n \n 2. AssertionError Status code is 200 \n expected { Object (id, _details, ...) } to have property 'code' \n at assertion:0 in test-script \n inside \"Health\" \n","outputType":"text/plain","errorMessage":"process error: exit status 1","steps":[{"name":"Health","duration":"0s","status":"failed","assertionResults":[{"name":"Status code is 200","status":"failed","errorMessage":"expected { Object (id, _details, ...) } to have property 'code'"}]}]}}
`)
- result, err := ParseRunnerOutput(exampleOutput)
+ result, err := ParseRunnerOutput(exampleOutput, true)
assert.Len(t, result.Output, 4624)
assert.NoError(t, err)
@@ -150,7 +150,7 @@ func TestParseRunnerOutput(t *testing.T) {
{"type":"line","content":"✅ Got Newman result successfully","time":"2023-07-18T19:12:46.126116248Z"}
{"type":"line","content":"✅ Mapped Newman result successfully","time":"2023-07-18T19:12:46.126152021Z"}
{"type":"result","result":{"status":"passed","output":"newman\n\nCore App Tests - WebPlayer\n\n→ core-eks-test.poppcore.co client=testdb sign=testct1 company=41574150-b952-413b-898b-dc5336b4bd12\n GET https://na.com/v6-wplt/?client=testdb\u0026sign=testct1\u0026company=41574150-b952-413b-898b-dc5336b4bd12 [200 OK, 33.9kB, 326ms]\n ✓ Status code is 200\n\n┌─────────────────────────┬────────────────────┬───────────────────┐\n│ │ executed │ failed │\n├─────────────────────────┼────────────────────┼───────────────────┤\n│ iterations │ 1 │ 0 │\n├─────────────────────────┼────────────────────┼───────────────────┤\n│ requests │ 1 │ 0 │\n├─────────────────────────┼────────────────────┼───────────────────┤\n│ test-scripts │ 1 │ 0 │\n├─────────────────────────┼────────────────────┼───────────────────┤\n│ prerequest-scripts │ 0 │ 0 │\n├─────────────────────────┼────────────────────┼───────────────────┤\n│ assertions │ 1 │ 0 │\n├─────────────────────────┴────────────────────┴───────────────────┤\n│ total run duration: 429ms │\n├──────────────────────────────────────────────────────────────────┤\n│ total data received: 33.45kB (approx) │\n├──────────────────────────────────────────────────────────────────┤\n│ average response time: 326ms [min: 326ms, max: 326ms, s.d.: 0µs] │\n└──────────────────────────────────────────────────────────────────┘\n","outputType":"text/plain","steps":[{"name":"na.com client=testdb sign=testct1 company=41574150-b952-413b-898b-dc5336b4bd12","duration":"326ms","status":"passed","assertionResults":[{"name":"Status code is 200","status":"passed"}]}]},"time":"2023-07-18T19:12:46.12615853Z"}`)
- result, err := ParseRunnerOutput(exampleOutput)
+ result, err := ParseRunnerOutput(exampleOutput, true)
assert.Len(t, result.Output, 7304)
assert.NoError(t, err)
@@ -203,7 +203,7 @@ can't find branch or commit in params, repo:&{Type_:git-file Uri:https://github.
running test [63c6bec1790802b7e3e57048]
🚚 Preparing for test run
`
- result, err := ParseRunnerOutput(unorderedOutput)
+ result, err := ParseRunnerOutput(unorderedOutput, true)
assert.Equal(t, expectedOutput, result.Output)
assert.NoError(t, err)
@@ -221,7 +221,7 @@ running test [63c6bec1790802b7e3e57048]
Running [ ./zap-api-scan.py [-t https://www.example.com/openapi.json -f openapi -c examples/zap-api.conf -d -D 5 -I -l INFO -n examples/context.config -S -T 60 -U anonymous -O https://www.example.com -z -config aaa=bbb -r api-test-report.html]]
could not start process: fork/exec ./zap-api-scan.py: no such file or directory
`
- result, err := ParseRunnerOutput(output)
+ result, err := ParseRunnerOutput(output, true)
assert.Equal(t, expectedOutput, result.Output)
assert.NoError(t, err)
@@ -276,7 +276,7 @@ running test [63c960287104b0fa0b7a45ef]
can't find branch or commit in params, repo:&{Type_:git-file Uri:https://github.com/kubeshop/testkube.git Branch: Commit: Path:test/cypress/executor-smoke/cypress-11 Username: Token: UsernameSecret: TokenSecret: WorkingDir:}
`
- result, err := ParseRunnerOutput(output)
+ result, err := ParseRunnerOutput(output, true)
assert.Equal(t, expectedOutput, result.Output)
assert.NoError(t, err)
@@ -337,7 +337,7 @@ running test [63c960287104b0fa0b7a45ef]
can't find branch or commit in params, repo:&{Type_:git-file Uri:https://github.com/kubeshop/testkube.git Branch: Commit: Path:test/cypress/executor-smoke/cypress-11 Username: Token: UsernameSecret: TokenSecret: WorkingDir:}
`
- result, err := ParseRunnerOutput(output)
+ result, err := ParseRunnerOutput(output, true)
assert.Equal(t, expectedOutput, result.Output)
assert.NoError(t, err)
@@ -392,7 +392,7 @@ running test [63ca8c8988564860327a16b5]
❌ can't find branch or commit in params, repo:&{Type_:git-file Uri:https://github.com/kubeshop/testkube.git Branch: Commit: Path:test/cypress/executor-smoke/cypress-11 Username: Token: UsernameSecret: TokenSecret: WorkingDir:}
`
- result, err := ParseRunnerOutput(output)
+ result, err := ParseRunnerOutput(output, true)
assert.Equal(t, expectedOutput, result.Output)
assert.NoError(t, err)
diff --git a/pkg/logs/client/interface.go b/pkg/logs/client/interface.go
index 58458f23cd3..edeed1bc248 100644
--- a/pkg/logs/client/interface.go
+++ b/pkg/logs/client/interface.go
@@ -44,9 +44,9 @@ type StreamInitializer interface {
type StreamPusher interface {
// Push sends logs to log stream
- Push(ctx context.Context, id string, chunk events.Log) error
+ Push(ctx context.Context, id string, log *events.Log) error
// PushBytes sends RAW bytes to log stream, developer is responsible for marshaling valid data
- PushBytes(ctx context.Context, id string, chunk []byte) error
+ PushBytes(ctx context.Context, id string, bytes []byte) error
}
// StreamGetter interface for getting logs stream channel
diff --git a/pkg/logs/client/mock_initializedstreampusher.go b/pkg/logs/client/mock_initializedstreampusher.go
index 9892c9b9ef2..8d67710a751 100644
--- a/pkg/logs/client/mock_initializedstreampusher.go
+++ b/pkg/logs/client/mock_initializedstreampusher.go
@@ -51,7 +51,7 @@ func (mr *MockInitializedStreamPusherMockRecorder) Init(arg0, arg1 interface{})
}
// Push mocks base method.
-func (m *MockInitializedStreamPusher) Push(arg0 context.Context, arg1 string, arg2 events.Log) error {
+func (m *MockInitializedStreamPusher) Push(arg0 context.Context, arg1 string, arg2 *events.Log) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Push", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
diff --git a/pkg/logs/client/mock_stream.go b/pkg/logs/client/mock_stream.go
index 4b605daacc9..eb0cb02cbe2 100644
--- a/pkg/logs/client/mock_stream.go
+++ b/pkg/logs/client/mock_stream.go
@@ -66,7 +66,7 @@ func (mr *MockStreamMockRecorder) Init(arg0, arg1 interface{}) *gomock.Call {
}
// Push mocks base method.
-func (m *MockStream) Push(arg0 context.Context, arg1 string, arg2 events.Log) error {
+func (m *MockStream) Push(arg0 context.Context, arg1 string, arg2 *events.Log) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Push", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
diff --git a/pkg/logs/client/stream.go b/pkg/logs/client/stream.go
index e8bccf966d6..9e20732d78f 100644
--- a/pkg/logs/client/stream.go
+++ b/pkg/logs/client/stream.go
@@ -51,8 +51,8 @@ func (c NatsLogStream) Init(ctx context.Context, id string) (StreamMetadata, err
}
// Push log chunk to NATS stream
-func (c NatsLogStream) Push(ctx context.Context, id string, chunk events.Log) error {
- b, err := json.Marshal(chunk)
+func (c NatsLogStream) Push(ctx context.Context, id string, log *events.Log) error {
+ b, err := json.Marshal(log)
if err != nil {
return err
}
@@ -61,8 +61,8 @@ func (c NatsLogStream) Push(ctx context.Context, id string, chunk events.Log) er
// Push log chunk to NATS stream
// TODO handle message repeat with backoff strategy on error
-func (c NatsLogStream) PushBytes(ctx context.Context, id string, chunk []byte) error {
- _, err := c.js.Publish(ctx, c.streamName(id), chunk)
+func (c NatsLogStream) PushBytes(ctx context.Context, id string, bytes []byte) error {
+ _, err := c.js.Publish(ctx, c.streamName(id), bytes)
return err
}
diff --git a/pkg/logs/events/events.go b/pkg/logs/events/events.go
index 6697c978d0a..0febcf09c4c 100644
--- a/pkg/logs/events/events.go
+++ b/pkg/logs/events/events.go
@@ -24,7 +24,10 @@ const (
// v2 - raw binary format, timestamps are based on Kubernetes logs, line is raw log line
LogVersionV2 LogVersion = "v2"
- JobPodLogSource = "job-pod"
+ SourceJobPod = "job-pod"
+ SourceScheduler = "test-scheduler"
+ SourceContainerExecutor = "container-executor"
+ SourceJobExecutor = "job-executor"
)
type LogResponse struct {
@@ -48,20 +51,43 @@ type LogOutputV1 struct {
Result *testkube.ExecutionResult
}
-func NewLog(content string) *Log {
+func NewErrorLog(err error) *Log {
+ var msg string
+ if err != nil {
+ msg = err.Error()
+ }
return &Log{
+ Error: true,
+ Content: msg,
+ }
+}
+
+func NewLog(content ...string) *Log {
+ log := &Log{
Time: time.Now(),
- Content: string(content),
Metadata: map[string]string{},
}
+
+ if len(content) > 0 {
+ log.WithContent(content[0])
+ }
+
+ return log
}
-func NewLogResponse(ts time.Time, content []byte) Log {
- return Log{
- Time: ts,
- Content: string(content),
- Metadata: map[string]string{},
+func (l *Log) WithContent(s string) *Log {
+ l.Content = s
+ return l
+}
+
+func (l *Log) WithError(err error) *Log {
+ l.Error = true
+
+ if err != nil {
+ l.Content = err.Error()
}
+
+ return l
}
func (l *Log) WithMetadataEntry(key, value string) *Log {
@@ -94,9 +120,9 @@ func (l *Log) WithV1Result(result *testkube.ExecutionResult) *Log {
var timestampRegexp = regexp.MustCompile("^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*")
-// NewLogResponseFromBytes creates new LogResponse from bytes it's aware of new and old log formats
+// NewLogFromBytes creates new LogResponse from bytes it's aware of new and old log formats
// default log format will be based on raw bytes with timestamp on the beginning
-func NewLogResponseFromBytes(b []byte) Log {
+func NewLogFromBytes(b []byte) *Log {
// detect timestamp - new logs have timestamp
var (
@@ -138,20 +164,19 @@ func NewLogResponseFromBytes(b []byte) Log {
if err != nil {
// try to read in case of some lines which we couldn't parse
// sometimes we're not able to control all stdout messages from libs
- return Log{
+ return &Log{
Time: ts,
Content: err.Error(),
Type: o.Type_,
Error: true,
Version: LogVersionV1,
- Source: JobPodLogSource,
}
}
// pass parsed results for v1
// for new executor it'll be omitted in logs (as looks like we're not using it already)
if o.Type_ == output.TypeResult {
- return Log{
+ return &Log{
Time: ts,
Content: o.Content,
Version: LogVersionV1,
@@ -161,7 +186,7 @@ func NewLogResponseFromBytes(b []byte) Log {
}
}
- return Log{
+ return &Log{
Time: ts,
Content: o.Content,
Version: LogVersionV1,
@@ -170,10 +195,9 @@ func NewLogResponseFromBytes(b []byte) Log {
// END DEPRECATED
// new non-JSON format (just raw lines will be logged)
- return Log{
+ return &Log{
Time: ts,
Content: string(b),
Version: LogVersionV2,
- Source: JobPodLogSource,
}
}
diff --git a/pkg/logs/events_test.go b/pkg/logs/events_test.go
index 6986c0154d6..fefbc3a3b4f 100644
--- a/pkg/logs/events_test.go
+++ b/pkg/logs/events_test.go
@@ -79,8 +79,8 @@ func TestLogs_EventsFlow(t *testing.T) {
assert.NoError(t, err)
// and when data pushed to the log stream
- stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
- stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 2")))
+ stream.Push(ctx, id, events.NewLog("hello 1"))
+ stream.Push(ctx, id, events.NewLog("hello 2"))
// and stop event triggered
_, err = stream.Stop(ctx, id)
@@ -154,7 +154,7 @@ func TestLogs_EventsFlow(t *testing.T) {
for i := 0; i < messagesCount; i++ {
// and when data pushed to the log stream
- err = stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello")))
+ err = stream.Push(ctx, id, events.NewLog("hello"))
assert.NoError(t, err)
}
@@ -226,9 +226,9 @@ func TestLogs_EventsFlow(t *testing.T) {
stats := log.GetConsumersStats(ctx)
assert.Equal(t, 2, stats.Count)
- stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
- stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
- stream.Push(ctx, id, events.NewLogResponse(time.Now(), []byte("hello 1")))
+ stream.Push(ctx, id, events.NewLog("hello 1"))
+ stream.Push(ctx, id, events.NewLog("hello 1"))
+ stream.Push(ctx, id, events.NewLog("hello 1"))
// when stop event triggered
r, err := stream.Stop(ctx, id)
diff --git a/pkg/logs/sidecar/proxy.go b/pkg/logs/sidecar/proxy.go
index d7c9aad161d..351c705ad21 100644
--- a/pkg/logs/sidecar/proxy.go
+++ b/pkg/logs/sidecar/proxy.go
@@ -63,7 +63,7 @@ func (p *Proxy) Run(ctx context.Context) error {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
- logs := make(chan events.Log, logsBuffer)
+ logs := make(chan *events.Log, logsBuffer)
// create stream for incoming logs
_, err := p.logsStream.Init(ctx, p.executionId)
@@ -83,6 +83,7 @@ func (p *Proxy) Run(ctx context.Context) error {
select {
case <-sigs:
p.log.Warn("logs proxy received signal, exiting", "signal", sigs)
+ p.handleError(ErrStopSignalReceived, "context cancelled stopping logs proxy")
return ErrStopSignalReceived
case <-ctx.Done():
p.log.Warn("logs proxy context cancelled, exiting")
@@ -100,7 +101,7 @@ func (p *Proxy) Run(ctx context.Context) error {
return nil
}
-func (p *Proxy) streamLogs(ctx context.Context, logs chan events.Log) (err error) {
+func (p *Proxy) streamLogs(ctx context.Context, logs chan *events.Log) (err error) {
pods, err := executor.GetJobPods(ctx, p.podsClient, p.executionId, 1, 10)
if err != nil {
p.handleError(err, "error getting job pods")
@@ -138,7 +139,7 @@ func (p *Proxy) streamLogs(ctx context.Context, logs chan events.Log) (err error
return
}
-func (p *Proxy) streamLogsFromPod(pod corev1.Pod, logs chan events.Log) (err error) {
+func (p *Proxy) streamLogsFromPod(pod corev1.Pod, logs chan *events.Log) (err error) {
defer close(logs)
var containers []string
@@ -182,7 +183,8 @@ func (p *Proxy) streamLogsFromPod(pod corev1.Pod, logs chan events.Log) (err err
}
// parse log line - also handle old (output.Output) and new format (just unstructured []byte)
- logs <- events.NewLogResponseFromBytes(b)
+ logs <- events.NewLogFromBytes(b).
+ WithSource(events.SourceJobPod)
}
if err != nil {
@@ -239,16 +241,9 @@ func (p *Proxy) getPodContainerStatuses(pod corev1.Pod) (status string) {
// handleError will handle errors and push it as log chunk to logs stream
func (p *Proxy) handleError(err error, title string) {
if err != nil {
- ch := events.Log{
- Error: true,
- Content: err.Error(),
- }
-
p.log.Errorw(title, "error", err)
-
- if err == nil {
- p.logsStream.Push(context.Background(), p.executionId, ch)
- } else {
+ err = p.logsStream.Push(context.Background(), p.executionId, events.NewErrorLog(err))
+ if err != nil {
p.log.Errorw("error pushing error to stream", "title", title, "error", err)
}
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index b64a58ee678..9eb8482b380 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -139,16 +139,14 @@ func (s *Scheduler) handleExecutionStart(ctx context.Context, execution testkube
l := events.NewLog(fmt.Sprintf("starting execution %s (%s)", execution.Name, execution.Id)).
WithType("execution-config").
WithVersion(events.LogVersionV2).
- WithSource("test-scheduler")
-
- // TODO try to store map[strin]any through protobuf for now it'll be map[string]string
- l.WithMetadataEntry("command", strings.Join(execution.Command, " "))
- l.WithMetadataEntry("argsmode", execution.ArgsMode)
- l.WithMetadataEntry("args", strings.Join(execution.Args, " "))
- l.WithMetadataEntry("pre-run", execution.PreRunScript)
- l.WithMetadataEntry("post-run", execution.PostRunScript)
+ WithSource("test-scheduler").
+ WithMetadataEntry("command", strings.Join(execution.Command, " ")).
+ WithMetadataEntry("argsmode", execution.ArgsMode).
+ WithMetadataEntry("args", strings.Join(execution.Args, " ")).
+ WithMetadataEntry("pre-run", execution.PreRunScript).
+ WithMetadataEntry("post-run", execution.PostRunScript)
- s.logsStream.Push(ctx, execution.Id, *l)
+ s.logsStream.Push(ctx, execution.Id, l)
}
}
@@ -160,7 +158,7 @@ func (s *Scheduler) handleExecutionError(ctx context.Context, execution testkube
WithVersion(events.LogVersionV2).
WithSource("test-scheduler")
- s.logsStream.Push(ctx, execution.Id, *l)
+ s.logsStream.Push(ctx, execution.Id, l)
}
From 22f002204ceea5a2f526d40b8a94dcd50e7f3188 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 22 Jan 2024 10:44:10 +0100
Subject: [PATCH 042/234] build: bump github.com/go-playground/locales from
0.14.0 to 0.14.1 (#4869)
Bumps [github.com/go-playground/locales](https://github.com/go-playground/locales) from 0.14.0 to 0.14.1.
- [Release notes](https://github.com/go-playground/locales/releases)
- [Commits](https://github.com/go-playground/locales/compare/v0.14.0...v0.14.1)
---
updated-dependencies:
- dependency-name: github.com/go-playground/locales
dependency-type: indirect
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/go.mod b/go.mod
index 391c1805158..9bab75da059 100644
--- a/go.mod
+++ b/go.mod
@@ -83,7 +83,7 @@ require (
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
- github.com/go-playground/locales v0.14.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
diff --git a/go.sum b/go.sum
index 98d42309225..d28315e82d6 100644
--- a/go.sum
+++ b/go.sum
@@ -185,8 +185,9 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
From 84ba40f216fbf19138f6d3b79d86bd9586ca53c4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 22 Jan 2024 10:44:21 +0100
Subject: [PATCH 043/234] build: bump github.com/itchyny/gojq from 0.12.9 to
0.12.14 (#4870)
Bumps [github.com/itchyny/gojq](https://github.com/itchyny/gojq) from 0.12.9 to 0.12.14.
- [Release notes](https://github.com/itchyny/gojq/releases)
- [Changelog](https://github.com/itchyny/gojq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/itchyny/gojq/compare/v0.12.9...v0.12.14)
---
updated-dependencies:
- dependency-name: github.com/itchyny/gojq
dependency-type: indirect
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 4 ++--
go.sum | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/go.mod b/go.mod
index 9bab75da059..c362b04a51c 100644
--- a/go.mod
+++ b/go.mod
@@ -92,8 +92,8 @@ require (
github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
github.com/henvic/httpretty v0.1.0 // indirect
- github.com/itchyny/gojq v0.12.9 // indirect
- github.com/itchyny/timefmt-go v0.1.4 // indirect
+ github.com/itchyny/gojq v0.12.14 // indirect
+ github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
diff --git a/go.sum b/go.sum
index d28315e82d6..f528988818d 100644
--- a/go.sum
+++ b/go.sum
@@ -313,10 +313,10 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/itchyny/gojq v0.12.9 h1:biKpbKwMxVYhCU1d6mR7qMr3f0Hn9F5k5YykCVb3gmM=
-github.com/itchyny/gojq v0.12.9/go.mod h1:T4Ip7AETUXeGpD+436m+UEl3m3tokRgajd5pRfsR5oE=
-github.com/itchyny/timefmt-go v0.1.4 h1:hFEfWVdwsEi+CY8xY2FtgWHGQaBaC3JeHd+cve0ynVM=
-github.com/itchyny/timefmt-go v0.1.4/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
+github.com/itchyny/gojq v0.12.14 h1:6k8vVtsrhQSYgSGg827AD+PVVaB1NLXEdX+dda2oZCc=
+github.com/itchyny/gojq v0.12.14/go.mod h1:y1G7oO7XkcR1LPZO59KyoCRy08T3j9vDYRV0GgYSS+s=
+github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
+github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
From d1cdc4b4af0b3c2ba302ef278528c3d23ad0317c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 22 Jan 2024 10:44:41 +0100
Subject: [PATCH 044/234] build: bump github.com/charmbracelet/glamour (#4871)
Bumps [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) from 0.5.1-0.20220727184942-e70ff2d969da to 0.6.0.
- [Release notes](https://github.com/charmbracelet/glamour/releases)
- [Commits](https://github.com/charmbracelet/glamour/commits/v0.6.0)
---
updated-dependencies:
- dependency-name: github.com/charmbracelet/glamour
dependency-type: indirect
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 14 ++++++--------
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/go.mod b/go.mod
index c362b04a51c..3319b5c83c6 100644
--- a/go.mod
+++ b/go.mod
@@ -69,7 +69,7 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/briandowns/spinner v1.19.0 // indirect
- github.com/charmbracelet/glamour v0.5.1-0.20220727184942-e70ff2d969da // indirect
+ github.com/charmbracelet/glamour v0.6.0 // indirect
github.com/cli/browser v1.1.0 // indirect
github.com/cli/go-gh v0.1.3-0.20221102170023-e3ec45fb1d1b // indirect
github.com/cli/safeexec v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index f528988818d..830c3359af2 100644
--- a/go.sum
+++ b/go.sum
@@ -80,6 +80,7 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
+github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -95,8 +96,8 @@ github.com/cdevents/sdk-go v0.3.0/go.mod h1:8EFl9VDZkxEmO/sr06Phzr501OiU6B5d04+e
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/charmbracelet/glamour v0.5.1-0.20220727184942-e70ff2d969da h1:FGz53GWQRiKQ/5xUsoCCkewSQIC7u81Scaxx2nUy3nM=
-github.com/charmbracelet/glamour v0.5.1-0.20220727184942-e70ff2d969da/go.mod h1:HXz79SMFnF9arKxqeoHWxmo1BhplAH7wehlRhKQIL94=
+github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
+github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -369,7 +370,6 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -386,7 +386,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
@@ -413,7 +412,7 @@ github.com/moogar0880/problems v0.1.1 h1:bktLhq8NDG/czU2ZziYNigBFksx13RaYe5AVdNm
github.com/moogar0880/problems v0.1.1/go.mod h1:5Dxrk2sD7BfBAgnOzQ1yaTiuCYdGPUh49L8Vhfky62c=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
-github.com/muesli/termenv v0.11.0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
+github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0=
github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -570,8 +569,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
@@ -678,7 +677,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -686,6 +684,7 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
@@ -756,7 +755,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
From da67e43edf282456db118fdd77f17039e69bca3d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 22 Jan 2024 10:45:00 +0100
Subject: [PATCH 045/234] build: bump github.com/prometheus/client_golang from
1.16.0 to 1.18.0 (#4873)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.16.0 to 1.18.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.16.0...v1.18.0)
---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 8 ++++----
go.sum | 16 ++++++++--------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/go.mod b/go.mod
index 3319b5c83c6..0e1a2e19611 100644
--- a/go.mod
+++ b/go.mod
@@ -37,7 +37,7 @@ require (
github.com/onsi/ginkgo/v2 v2.13.2
github.com/onsi/gomega v1.30.0
github.com/otiai10/copy v1.11.0
- github.com/prometheus/client_golang v1.16.0
+ github.com/prometheus/client_golang v1.18.0
github.com/pterm/pterm v0.12.62
github.com/segmentio/analytics-go/v3 v3.2.1
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
@@ -103,6 +103,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
@@ -155,15 +156,14 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/client_model v0.4.0 // indirect
- github.com/prometheus/common v0.44.0 // indirect
+ github.com/prometheus/client_model v0.5.0 // indirect
+ github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/robfig/cron v1.2.0
github.com/rs/xid v1.4.0 // indirect
diff --git a/go.sum b/go.sum
index 830c3359af2..d015bcef8e9 100644
--- a/go.sum
+++ b/go.sum
@@ -381,8 +381,8 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
-github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@@ -454,13 +454,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
-github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
-github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
-github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
-github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
-github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
+github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
From 26ffb0a5f7334c366d7d560744d934b93198d5f4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 22 Jan 2024 10:45:12 +0100
Subject: [PATCH 046/234] build: bump github.com/emicklei/go-restful/v3 from
3.11.0 to 3.11.2 (#4872)
Bumps [github.com/emicklei/go-restful/v3](https://github.com/emicklei/go-restful) from 3.11.0 to 3.11.2.
- [Release notes](https://github.com/emicklei/go-restful/releases)
- [Changelog](https://github.com/emicklei/go-restful/blob/v3/CHANGES.md)
- [Commits](https://github.com/emicklei/go-restful/compare/v3.11.0...v3.11.2)
---
updated-dependencies:
- dependency-name: github.com/emicklei/go-restful/v3
dependency-type: indirect
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 0e1a2e19611..81ed4455878 100644
--- a/go.mod
+++ b/go.mod
@@ -76,7 +76,7 @@ require (
github.com/cli/shurcooL-graphql v0.0.2 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/dlclark/regexp2 v1.8.0 // indirect
- github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+ github.com/emicklei/go-restful/v3 v3.11.2 // indirect
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-errors/errors v1.5.1 // indirect
diff --git a/go.sum b/go.sum
index d015bcef8e9..3503a6a7c5b 100644
--- a/go.sum
+++ b/go.sum
@@ -143,8 +143,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY=
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8=
-github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
-github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU=
+github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
From 56f6d67a66eac57845104c098f695f3d7b51d6f7 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Thu, 25 Jan 2024 15:48:26 +0100
Subject: [PATCH 047/234] fix: added adapter stop (#4948)
---
pkg/logs/events.go | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/pkg/logs/events.go b/pkg/logs/events.go
index 91bb1c59fe4..c8d8ab7512a 100644
--- a/pkg/logs/events.go
+++ b/pkg/logs/events.go
@@ -187,8 +187,15 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
wg.Add(1)
stopped++
consumer := c.(Consumer)
-
go ls.stopConsumer(ctx, &wg, consumer)
+
+ // call adapter stop to handle given id
+ err := adapter.Stop(event.Id)
+ if err != nil {
+ l.Errorw("stop error", "adapter", adapter.Name(), "error", err)
+ continue
+ }
+
}
wg.Wait()
@@ -200,7 +207,6 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
} else {
l.Debugw("no consumers found on this pod to stop")
}
-
}
}
@@ -231,7 +237,9 @@ func (ls *LogsService) stopConsumer(ctx context.Context, wg *sync.WaitGroup, con
// check if there was some messages processed
if nothingToProcess && messagesDelivered {
+ // stop nats consumer
consumer.Context.Stop()
+ // delete nats consumer instance from memory
ls.consumerInstances.Delete(consumer.Name)
l.Infow("stopping and removing consumer", "name", consumer.Name, "consumerSeq", info.Delivered.Consumer, "streamSeq", info.Delivered.Stream, "last", info.Delivered.Last)
return
From f1e726bf00cb10c6ddf15a821b85d8b84b37646c Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Thu, 25 Jan 2024 16:59:00 +0100
Subject: [PATCH 048/234] fix: adapter stop call after consumer stop (#4950)
---
pkg/logs/events.go | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/pkg/logs/events.go b/pkg/logs/events.go
index c8d8ab7512a..cb3ac9a5edf 100644
--- a/pkg/logs/events.go
+++ b/pkg/logs/events.go
@@ -187,14 +187,7 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
wg.Add(1)
stopped++
consumer := c.(Consumer)
- go ls.stopConsumer(ctx, &wg, consumer)
-
- // call adapter stop to handle given id
- err := adapter.Stop(event.Id)
- if err != nil {
- l.Errorw("stop error", "adapter", adapter.Name(), "error", err)
- continue
- }
+ go ls.stopConsumer(ctx, &wg, consumer, adapter, event.Id)
}
@@ -210,7 +203,7 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
}
}
-func (ls *LogsService) stopConsumer(ctx context.Context, wg *sync.WaitGroup, consumer Consumer) {
+func (ls *LogsService) stopConsumer(ctx context.Context, wg *sync.WaitGroup, consumer Consumer, adapter adapter.Adapter, id string) {
defer wg.Done()
var (
@@ -242,6 +235,14 @@ func (ls *LogsService) stopConsumer(ctx context.Context, wg *sync.WaitGroup, con
// delete nats consumer instance from memory
ls.consumerInstances.Delete(consumer.Name)
l.Infow("stopping and removing consumer", "name", consumer.Name, "consumerSeq", info.Delivered.Consumer, "streamSeq", info.Delivered.Stream, "last", info.Delivered.Last)
+
+ // call adapter stop to handle given id
+ err := adapter.Stop(id)
+ if err != nil {
+ l.Errorw("stop error", "adapter", adapter.Name(), "error", err)
+ continue
+ }
+
return
}
From 83fa883a38f4a083c0dc51fc81dac6a23842d327 Mon Sep 17 00:00:00 2001
From: nicufk
Date: Fri, 26 Jan 2024 12:21:41 +0200
Subject: [PATCH 049/234] feat: add minio adapter for logs (#4942)
* feat: add minio adapter for logs
* fix: add all the minio config files
* fix: tests
* fix: parameters in creating
* fix: add more log info
* fix: rename minio adapter
---
cmd/logs/main.go | 19 +++++++++++++++++-
pkg/logs/adapter/dummy.go | 16 ++++++++--------
pkg/logs/adapter/minio.go | 31 +++++++++++++++++-------------
pkg/logs/adapter/minio_test.go | 13 ++++++-------
pkg/logs/config/logs_config.go | 35 ++++++++++++++++++++++------------
pkg/logs/service_test.go | 8 ++++----
6 files changed, 77 insertions(+), 45 deletions(-)
diff --git a/cmd/logs/main.go b/cmd/logs/main.go
index c642ad5d467..d4a6f93a0b8 100644
--- a/cmd/logs/main.go
+++ b/cmd/logs/main.go
@@ -55,7 +55,24 @@ func main() {
WithGrpcAddress(cfg.GrpcAddress)
// TODO - add adapters here
- svc.AddAdapter(adapter.NewDummyAdapter())
+ minioAdapter, err := adapter.NewMinioAdapter(cfg.StorageEndpoint,
+ cfg.StorageAccessKeyID,
+ cfg.StorageSecretAccessKey,
+ cfg.StorageRegion,
+ cfg.StorageToken,
+ cfg.StorageLogsBucket,
+ cfg.StorageSSL,
+ cfg.StorageSkipVerify,
+ cfg.StorageCertFile,
+ cfg.StorageKeyFile,
+ cfg.StorageCAFile)
+ if err != nil {
+ log.Errorw("error creating minio adapter, debug adapter created instead", "error", err)
+ svc.AddAdapter(adapter.NewDebugAdapter())
+ } else {
+ log.Infow("minio adapter created", "bucket", cfg.StorageLogsBucket, "endpoint", cfg.StorageEndpoint)
+ svc.AddAdapter(minioAdapter)
+ }
g.Add(func() error {
err := interrupt(log, ctx)
diff --git a/pkg/logs/adapter/dummy.go b/pkg/logs/adapter/dummy.go
index 993e47254cc..489ee3af206 100644
--- a/pkg/logs/adapter/dummy.go
+++ b/pkg/logs/adapter/dummy.go
@@ -6,27 +6,27 @@ import (
"github.com/kubeshop/testkube/pkg/logs/events"
)
-var _ Adapter = &DummyAdapter{}
+var _ Adapter = &DebugAdapter{}
-// NewS3Subscriber creates new DummySubscriber which will send data to local MinIO bucket
-func NewDummyAdapter() *DummyAdapter {
- return &DummyAdapter{}
+// NewDebugAdapter creates new DebugAdapter which will write logs to stdout
+func NewDebugAdapter() *DebugAdapter {
+ return &DebugAdapter{}
}
-type DummyAdapter struct {
+type DebugAdapter struct {
Bucket string
}
-func (s *DummyAdapter) Notify(id string, e events.Log) error {
+func (s *DebugAdapter) Notify(id string, e events.Log) error {
fmt.Printf("%s %+v\n", id, e)
return nil
}
-func (s *DummyAdapter) Stop(id string) error {
+func (s *DebugAdapter) Stop(id string) error {
fmt.Printf("stopping %s \n", id)
return nil
}
-func (s *DummyAdapter) Name() string {
+func (s *DebugAdapter) Name() string {
return "dummy"
}
diff --git a/pkg/logs/adapter/minio.go b/pkg/logs/adapter/minio.go
index 25d8f15a3f2..63cba09d8b7 100644
--- a/pkg/logs/adapter/minio.go
+++ b/pkg/logs/adapter/minio.go
@@ -21,7 +21,7 @@ const (
defaultWriteSize = 1024 * 80 // 80KB
)
-var _ Adapter = &MinioConsumer{}
+var _ Adapter = &MinioAdapter{}
type ErrMinioConsumerDisconnected struct {
}
@@ -52,9 +52,10 @@ type BufferInfo struct {
}
// MinioConsumer creates new MinioSubscriber which will send data to local MinIO bucket
-func NewMinioConsumer(endpoint, accessKeyID, secretAccessKey, region, token, bucket string, opts ...minioconnecter.Option) (*MinioConsumer, error) {
+func NewMinioAdapter(endpoint, accessKeyID, secretAccessKey, region, token, bucket string, ssl, skipVerify bool, certFile, keyFile, caFile string) (*MinioAdapter, error) {
ctx := context.TODO()
- c := &MinioConsumer{
+ opts := minioconnecter.GetTLSOptions(ssl, skipVerify, certFile, keyFile, caFile)
+ c := &MinioAdapter{
minioConnecter: minioconnecter.NewConnecter(endpoint, accessKeyID, secretAccessKey, region, token, bucket, log.DefaultLogger, opts...),
Log: log.DefaultLogger,
bucket: bucket,
@@ -86,7 +87,7 @@ func NewMinioConsumer(endpoint, accessKeyID, secretAccessKey, region, token, buc
return c, nil
}
-type MinioConsumer struct {
+type MinioAdapter struct {
minioConnecter *minioconnecter.Connecter
minioClient *minio.Client
bucket string
@@ -97,7 +98,8 @@ type MinioConsumer struct {
mapLock sync.RWMutex
}
-func (s *MinioConsumer) Notify(id string, e events.Log) error {
+func (s *MinioAdapter) Notify(id string, e events.Log) error {
+ s.Log.Debugw("minio consumer notify", "id", id, "event", e)
if s.disconnected {
s.Log.Debugw("minio consumer disconnected", "id", id)
return ErrMinioConsumerDisconnected{}
@@ -138,19 +140,20 @@ func (s *MinioConsumer) Notify(id string, e events.Log) error {
return nil
}
-func (s *MinioConsumer) putData(ctx context.Context, name string, buffer *bytes.Buffer) {
+func (s *MinioAdapter) putData(ctx context.Context, name string, buffer *bytes.Buffer) {
if buffer != nil && buffer.Len() != 0 {
_, err := s.minioClient.PutObject(ctx, s.bucket, name, buffer, int64(buffer.Len()), minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
s.Log.Errorw("error putting object", "err", err)
}
+ s.Log.Debugw("put object successfully", "name", name, "s.bucket", s.bucket)
} else {
s.Log.Warn("empty buffer for name: ", name)
}
}
-func (s *MinioConsumer) combineData(ctxt context.Context, minioClient *minio.Client, id string, parts int, deleteIntermediaryData bool) error {
+func (s *MinioAdapter) combineData(ctxt context.Context, minioClient *minio.Client, id string, parts int, deleteIntermediaryData bool) error {
var returnedError []error
returnedError = nil
buffer := bytes.NewBuffer(make([]byte, 0, parts*defaultBufferSize))
@@ -174,6 +177,7 @@ func (s *MinioConsumer) combineData(ctxt context.Context, minioClient *minio.Cli
s.Log.Errorw("error putting object", "err", err)
return err
}
+ s.Log.Debugw("put object successfully", "id", id, "s.bucket", s.bucket, "parts", parts)
if deleteIntermediaryData {
for i := 0; i < parts; i++ {
@@ -194,12 +198,13 @@ func (s *MinioConsumer) combineData(ctxt context.Context, minioClient *minio.Cli
return fmt.Errorf("executed with errors: %v", returnedError)
}
-func (s *MinioConsumer) objectExists(objectName string) bool {
+func (s *MinioAdapter) objectExists(objectName string) bool {
_, err := s.minioClient.StatObject(context.Background(), s.bucket, objectName, minio.StatObjectOptions{})
return err == nil
}
-func (s *MinioConsumer) Stop(id string) error {
+func (s *MinioAdapter) Stop(id string) error {
+ s.Log.Debugw("minio consumer stop", "id", id)
ctx := context.TODO()
buffInfo, ok := s.GetBuffInfo(id)
if !ok {
@@ -212,24 +217,24 @@ func (s *MinioConsumer) Stop(id string) error {
return s.combineData(ctx, s.minioClient, id, parts, true)
}
-func (s *MinioConsumer) Name() string {
+func (s *MinioAdapter) Name() string {
return "minio"
}
-func (s *MinioConsumer) GetBuffInfo(id string) (BufferInfo, bool) {
+func (s *MinioAdapter) GetBuffInfo(id string) (BufferInfo, bool) {
s.mapLock.RLock()
defer s.mapLock.RUnlock()
buffInfo, ok := s.buffInfos[id]
return buffInfo, ok
}
-func (s *MinioConsumer) UpdateBuffInfo(id string, buffInfo BufferInfo) {
+func (s *MinioAdapter) UpdateBuffInfo(id string, buffInfo BufferInfo) {
s.mapLock.Lock()
defer s.mapLock.Unlock()
s.buffInfos[id] = buffInfo
}
-func (s *MinioConsumer) DeleteBuffInfo(id string) {
+func (s *MinioAdapter) DeleteBuffInfo(id string) {
s.mapLock.Lock()
defer s.mapLock.Unlock()
delete(s.buffInfos, id)
diff --git a/pkg/logs/adapter/minio_test.go b/pkg/logs/adapter/minio_test.go
index 307e590b5f7..4c98e7ab1a5 100644
--- a/pkg/logs/adapter/minio_test.go
+++ b/pkg/logs/adapter/minio_test.go
@@ -17,7 +17,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/kubeshop/testkube/pkg/logs/events"
- minioconnecter "github.com/kubeshop/testkube/pkg/storage/minio"
"github.com/kubeshop/testkube/pkg/utils"
)
@@ -39,7 +38,7 @@ func RandString(n int) string {
func TestLogs(t *testing.T) {
t.Skip("skipping test")
- consumer, _ := NewMinioConsumer("localhost:9000", "minio", "minio123", "", "", "test-1", minioconnecter.Insecure())
+ consumer, _ := NewMinioAdapter("localhost:9000", "minio", "minio123", "", "", "test-1", false, false, "", "", "")
id := "test-bla"
for i := 0; i < 1000; i++ {
fmt.Println("sending", i)
@@ -55,7 +54,7 @@ func TestLogs(t *testing.T) {
func BenchmarkLogs(b *testing.B) {
randomString := RandString(5)
bucket := "test-bench"
- consumer, _ := NewMinioConsumer("localhost:9000", "minio", "minio123", "", "", bucket, minioconnecter.Insecure())
+ consumer, _ := NewMinioAdapter("localhost:9000", "minio", "minio123", "", "", bucket, false, false, "", "", "")
id := "test-bench" + "-" + randomString + "-" + strconv.Itoa(b.N)
totalSize := 0
for i := 0; i < b.N; i++ {
@@ -72,7 +71,7 @@ func BenchmarkLogs(b *testing.B) {
func BenchmarkLogs2(b *testing.B) {
bucket := "test-bench"
- consumer, _ := NewMinioConsumer("localhost:9000", "minio", "minio123", "", "", bucket, minioconnecter.Insecure())
+ consumer, _ := NewMinioAdapter("localhost:9000", "minio", "minio123", "", "", bucket, false, false, "", "", "")
idChan := make(chan string, 100)
go verifyConsumer(idChan, bucket, consumer.minioClient)
var counter atomic.Int32
@@ -90,7 +89,7 @@ func BenchmarkLogs2(b *testing.B) {
wg.Wait()
}
-func testOneConsumer(consumer *MinioConsumer, id string) {
+func testOneConsumer(consumer *MinioAdapter, id string) {
fmt.Println("#####starting", id)
totalSize := 0
numberOFLogs := rand.Intn(100000)
@@ -158,14 +157,14 @@ func verifyConsumer(idChan chan string, bucket string, minioClient *minio.Client
func DoRunBenchmark() {
numberOfConsumers := 100
bucket := "test-bench"
- consumer, _ := NewMinioConsumer("testkube-minio-service-testkube:9000", "minio", "minio123", "", "", bucket, minioconnecter.Insecure())
+ consumer, _ := NewMinioAdapter("testkube-minio-service-testkube:9000", "minio", "minio123", "", "", bucket, false, false, "", "", "")
idChan := make(chan string, numberOfConsumers)
DoRunBenchmark2(idChan, numberOfConsumers, consumer)
verifyConsumer(idChan, bucket, consumer.minioClient)
}
-func DoRunBenchmark2(idChan chan string, numberOfConsumers int, consumer *MinioConsumer) {
+func DoRunBenchmark2(idChan chan string, numberOfConsumers int, consumer *MinioAdapter) {
var counter atomic.Int32
var wg sync.WaitGroup
for i := 0; i < numberOfConsumers; i++ {
diff --git a/pkg/logs/config/logs_config.go b/pkg/logs/config/logs_config.go
index d776cdf7181..49c4859e1ec 100644
--- a/pkg/logs/config/logs_config.go
+++ b/pkg/logs/config/logs_config.go
@@ -7,18 +7,29 @@ import (
)
type Config struct {
- NatsURI string `envconfig:"NATS_URI" default:"nats://localhost:4222"`
- NatsSecure bool `envconfig:"NATS_SECURE" default:"false"`
- NatsSkipVerify bool `envconfig:"NATS_SKIP_VERIFY" default:"false"`
- NatsCertFile string `envconfig:"NATS_CERT_FILE" default:""`
- NatsKeyFile string `envconfig:"NATS_KEY_FILE" default:""`
- NatsCAFile string `envconfig:"NATS_CA_FILE" default:""`
- NatsConnectTimeout time.Duration `envconfig:"NATS_CONNECT_TIMEOUT" default:"5s"`
- Namespace string `envconfig:"NAMESPACE" default:"testkube"`
- ExecutionId string `envconfig:"ID" default:""`
- HttpAddress string `envconfig:"HTTP_ADDRESS" default:":8080"`
- GrpcAddress string `envconfig:"GRPC_ADDRESS" default:":9090"`
- KVBucketName string `envconfig:"KV_BUCKET_NAME" default:"logsState"`
+ NatsURI string `envconfig:"NATS_URI" default:"nats://localhost:4222"`
+ NatsSecure bool `envconfig:"NATS_SECURE" default:"false"`
+ NatsSkipVerify bool `envconfig:"NATS_SKIP_VERIFY" default:"false"`
+ NatsCertFile string `envconfig:"NATS_CERT_FILE" default:""`
+ NatsKeyFile string `envconfig:"NATS_KEY_FILE" default:""`
+ NatsCAFile string `envconfig:"NATS_CA_FILE" default:""`
+ NatsConnectTimeout time.Duration `envconfig:"NATS_CONNECT_TIMEOUT" default:"5s"`
+ Namespace string `envconfig:"NAMESPACE" default:"testkube"`
+ ExecutionId string `envconfig:"ID" default:""`
+ HttpAddress string `envconfig:"HTTP_ADDRESS" default:":8080"`
+ GrpcAddress string `envconfig:"GRPC_ADDRESS" default:":9090"`
+ KVBucketName string `envconfig:"KV_BUCKET_NAME" default:"logsState"`
+ StorageEndpoint string `envconfig:"STORAGE_ENDPOINT" default:"testkube-minio-service-testkube:9000"`
+ StorageLogsBucket string `envconfig:"STORAGE_LOGS_BUCKET" default:"testkube-new-logs"`
+ StorageAccessKeyID string `envconfig:"STORAGE_ACCESSKEYID" default:"minio"`
+ StorageSecretAccessKey string `envconfig:"STORAGE_SECRETACCESSKEY" default:"minio123"`
+ StorageRegion string `envconfig:"STORAGE_REGION" default:""`
+ StorageToken string `envconfig:"STORAGE_TOKEN" default:""`
+ StorageSSL bool `envconfig:"STORAGE_SSL" default:"false"`
+ StorageSkipVerify bool `envconfig:"STORAGE_SKIP_VERIFY" default:"false"`
+ StorageCertFile string `envconfig:"STORAGE_CERT_FILE" default:""`
+ StorageKeyFile string `envconfig:"STORAGE_KEY_FILE" default:""`
+ StorageCAFile string `envconfig:"STORAGE_CA_FILE" default:""`
}
func Get() (*Config, error) {
diff --git a/pkg/logs/service_test.go b/pkg/logs/service_test.go
index 8ff1e4f1090..2ba896c6cc9 100644
--- a/pkg/logs/service_test.go
+++ b/pkg/logs/service_test.go
@@ -13,10 +13,10 @@ func TestLogsService_AddAdapter(t *testing.T) {
t.Run("should add adapter", func(t *testing.T) {
svc := LogsService{}
- svc.AddAdapter(adapter.NewDummyAdapter())
- svc.AddAdapter(adapter.NewDummyAdapter())
- svc.AddAdapter(adapter.NewDummyAdapter())
- svc.AddAdapter(adapter.NewDummyAdapter())
+ svc.AddAdapter(adapter.NewDebugAdapter())
+ svc.AddAdapter(adapter.NewDebugAdapter())
+ svc.AddAdapter(adapter.NewDebugAdapter())
+ svc.AddAdapter(adapter.NewDebugAdapter())
assert.Equal(t, 4, len(svc.adapters))
})
From 6f3643a2b57ee221edb0eb903f5e52a347f144dd Mon Sep 17 00:00:00 2001
From: Catalin <20538711+devcatalin@users.noreply.github.com>
Date: Fri, 26 Jan 2024 13:07:21 +0200
Subject: [PATCH 050/234] docs: update Jenkins article (#4949)
* docs: update Jenkins article
* docs: update sidebar
* chore: add jenkins plugin url
* chore: update jenkins docs env vars
---
docs/docs/articles/cicd-overview.md | 1 +
docs/docs/articles/jenkins.md | 93 ++++++++++++-----------------
docs/sidebars.js | 13 ++--
3 files changed, 44 insertions(+), 63 deletions(-)
diff --git a/docs/docs/articles/cicd-overview.md b/docs/docs/articles/cicd-overview.md
index 45142cbd97d..4a4d6c7213f 100644
--- a/docs/docs/articles/cicd-overview.md
+++ b/docs/docs/articles/cicd-overview.md
@@ -8,6 +8,7 @@ We have different tutorials for the options of being CI driven or using GitOps a
- [Github Actions - running Testkube CLI commands with setup-testkube-action](./github-actions.md)
- [Testkube Docker CLI](./testkube-cli-docker.md)
- [Gitlab CI](./gitlab.md)
+- [Jenkins](./jenkins.md)
- [CircleCI](./circleci.md)
- [GitOps Testing](./gitops-overview.md)
- [Flux](./flux-integration.md)
diff --git a/docs/docs/articles/jenkins.md b/docs/docs/articles/jenkins.md
index 19ef0723a7b..30fe6343753 100644
--- a/docs/docs/articles/jenkins.md
+++ b/docs/docs/articles/jenkins.md
@@ -3,6 +3,11 @@
The Testkube Jenkins integration streamlines the installation of Testkube, enabling the execution of any [Testkube CLI](https://docs.testkube.io/cli/testkube) command within Jenkins pipelines. This integration can be effortlessly integrated into your Jenkins setup, enhancing your continuous integration and delivery processes.
This Jenkins integration offers a versatile solution for managing your pipeline workflows and is compatible with Testkube Pro, Testkube Enterprise, and the open-source Testkube platform. It allows Jenkins users to effectively utilize Testkube's capabilities within their CI/CD pipelines, providing a robust and flexible framework for test execution and automation.
+### Testkube CLI Jenkins Plugin
+
+Install the Testkube CLI plugin by searching it in the "Available Plugins" section on Jenkins Plugins, or using the following url:
+[https://plugins.jenkins.io/testkube-cli](https://plugins.jenkins.io/testkube-cli)
+
## Testkube Pro
### How to configure Testkube CLI action for Testkube Pro and run a test
@@ -12,39 +17,31 @@ Then, pass the **organization** and **environment** IDs, along with the **token*
If a test is already created, you can run it using the command `testkube run test test-name -f` . However, if you need to create a test in this workflow, please add a creation command, e.g.: `testkube create test --name test-name --file path_to_file.json`.
-you'll need to create a Jenkinsfile. This Jenkinsfile should define the stages and steps necessary to execute the workflow
+You'll need to create a Jenkinsfile. This Jenkinsfile should define the stages and steps necessary to execute the workflow
```groovy
pipeline {
agent any
+ environment {
+ TK_ORG = credentials("TK_ORG")
+ TK_ENV = credentials("TK_ENV")
+ TK_API_TOKEN = credentials("TK_API_TOKEN")
+ }
stages {
- stage('Setup Testkube') {
+ stage('Example') {
steps {
script {
- // Retrieve credentials
- def apiKey = credentials('TESTKUBE_API_KEY')
- def orgId = credentials('TESTKUBE_ORG_ID')
- def envId = credentials('TESTKUBE_ENV_ID')
-
- // Install Testkube
- sh 'curl -sSLf https://get.testkube.io | sh'
-
- // Initialize Testkube
- sh "testkube set context --api-key ${apiKey} --org ${orgId} --env ${envId}"
+ // Setup the Testkube CLI
+ setupTestkube()
+ // Run testkube commands
+ sh 'testkube run test your-test'
+ sh 'testkube run testsuite your-test-suite --some-arg --other-arg'
}
}
}
-
- stage('Run Testkube Test') {
- steps {
- // Run a Testkube test
- sh 'testkube run test test-name -f'
- }
- }
}
}
-
```
## Testkube OSS
@@ -61,28 +58,18 @@ you'll need to create a Jenkinsfile. This Jenkinsfile should define the stages a
pipeline {
agent any
+ environment {
+ TK_NAMESPACE = 'custom-testkube-namespace'
+ }
stages {
- stage('Setup Testkube') {
+ stage('Example') {
steps {
script {
- // Retrieve credentials
- def namespace='custom-testkube'
-
- // Install Testkube
- sh 'curl -sSLf https://get.testkube.io | sh'
-
- // Initialize Testkube
- sh "testkube set context --kubeconfig --namespace ${namespace}"
+ setupTestkube()
+ sh 'testkube run test your-test'
}
}
}
-
- stage('Run Testkube Test') {
- steps {
- // Run a Testkube test
- sh 'testkube run test test-name -f'
- }
- }
}
}
```
@@ -104,6 +91,11 @@ you'll need to create a Jenkinsfile. This Jenkinsfile should define the stages a
pipeline {
agent any
+ environment {
+ TK_ORG = credentials("TK_ORG")
+ TK_ENV = credentials("TK_ENV")
+ TK_API_TOKEN = credentials("TK_API_TOKEN")
+ }
stages {
stage('Setup Testkube') {
steps {
@@ -120,17 +112,8 @@ pipeline {
sh 'aws eks update-kubeconfig --name $EKS_CLUSTER_NAME --region $AWS_REGION'
}
- // Installing Testkube
- sh 'curl -sSLf https://get.testkube.io | sh'
-
- // Initializing Testkube
- withCredentials([
- string(credentialsId: 'TestkubeApiKey', variable: 'TESTKUBE_API_KEY'),
- string(credentialsId: 'TestkubeOrgId', variable: 'TESTKUBE_ORG_ID'),
- string(credentialsId: 'TestkubeEnvId', variable: 'TESTKUBE_ENV_ID')
- ]) {
- sh 'testkube set context --api-key $TESTKUBE_API_KEY --org $TESTKUBE_ORG_ID --env $TESTKUBE_ENV_ID'
- }
+ // Installing and configuring Testkube based on env vars
+ setupTestkube()
// Running Testkube test
sh 'testkube run test test-name -f'
@@ -152,6 +135,11 @@ you'll need to create a Jenkinsfile. This Jenkinsfile should define the stages a
pipeline {
agent any
+ environment {
+ TK_ORG = credentials("TK_ORG")
+ TK_ENV = credentials("TK_ENV")
+ TK_API_TOKEN = credentials("TK_API_TOKEN")
+ }
stages {
stage('Deploy to GKE') {
steps {
@@ -177,15 +165,8 @@ pipeline {
sh 'gcloud container clusters get-credentials $GKE_CLUSTER_NAME --zone $GKE_ZONE'
}
- // Installing and initializing Testkube
- withCredentials([
- string(credentialsId: 'TESTKUBE_API_KEY', variable: 'TESTKUBE_API_KEY'),
- string(credentialsId: 'TESTKUBE_ORG_ID', variable: 'TESTKUBE_ORG_ID'),
- string(credentialsId: 'TESTKUBE_ENV_ID', variable: 'TESTKUBE_ENV_ID')
- ]) {
- sh 'curl -sSLf https://get.testkube.io | sh'
- sh 'testkube set context --api-key $TESTKUBE_API_KEY --org $TESTKUBE_ORG_ID --env $TESTKUBE_ENV_ID'
- }
+ // Installing and configuring Testkube based on env vars
+ setupTestkube()
// Running Testkube test
sh 'testkube run test test-name -f'
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 2409cbc7ab0..9aeab705d98 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -72,7 +72,7 @@ const sidebars = {
"articles/webhooks",
"articles/test-sources",
"articles/test-executions",
- "articles/templates",
+ "articles/templates",
],
},
{
@@ -103,6 +103,8 @@ const sidebars = {
items: [
"articles/github-actions",
"articles/gitlab",
+ "articles/jenkins",
+ "articles/circleci",
"articles/run-tests-with-github-actions",
"articles/testkube-cli-docker",
{
@@ -133,7 +135,7 @@ const sidebars = {
"articles/generate-test-crds",
"articles/logging",
"articles/install-cli",
- "articles/uninstall"
+ "articles/uninstall",
],
},
{
@@ -161,7 +163,7 @@ const sidebars = {
"test-types/executor-tracetest",
"test-types/executor-zap",
"test-types/prebuilt-executor",
- "test-types/container-executor",
+ "test-types/container-executor",
"test-types/executor-distributed-jmeter",
],
},
@@ -190,10 +192,7 @@ const sidebars = {
{
type: "category",
label: "Testkube Enterprise",
- items: [
- "testkube-enterprise/articles/usage-guide",
- "testkube-enterprise/articles/auth"
- ],
+ items: ["testkube-enterprise/articles/usage-guide", "testkube-enterprise/articles/auth"],
},
"articles/testkube-oss",
{
From d3961602b1d0e959e8fcf3fd1eb66aca25e405d6 Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Mon, 29 Jan 2024 13:31:55 +0100
Subject: [PATCH 051/234] fix: use test start stop event (#4954)
* fix: use test start stop event
* fix: rename event
* fix: handle test start and stop events already present
* fix: tests
---
api/v1/testkube.yaml | 68 +++++++-------
cmd/logs/main.go | 6 +-
.../pkg/runner/runner_integration_test.go | 3 -
pkg/api/v1/testkube/model_event.go | 5 +-
pkg/api/v1/testkube/model_event_extended.go | 32 +++++++
.../v1/testkube/model_event_extended_test.go | 5 ++
pkg/logs/adapter/minio.go | 3 +-
pkg/logs/client/mock_client.go | 50 +++++++++++
pkg/logs/client/stream.go | 2 +-
pkg/logs/client/stream_test.go | 2 +-
pkg/logs/config/logs_config.go | 1 +
pkg/logs/events.go | 90 ++++++++++++-------
pkg/logs/events/events.go | 25 ++++--
pkg/logs/events_test.go | 79 ++++++++++++++++
pkg/logs/service.go | 9 +-
pkg/scheduler/test_scheduler.go | 10 ---
16 files changed, 294 insertions(+), 96 deletions(-)
create mode 100644 pkg/logs/client/mock_client.go
diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml
index caad2352fae..9aa2e52aa3b 100644
--- a/api/v1/testkube.yaml
+++ b/api/v1/testkube.yaml
@@ -1132,7 +1132,7 @@ paths:
- $ref: "#/components/parameters/Namespace"
- $ref: "#/components/parameters/Selector"
- $ref: "#/components/parameters/ExecutionSelector"
- - $ref: "#/components/parameters/ConcurrencyLevel"
+ - $ref: "#/components/parameters/ConcurrencyLevel"
tags:
- api
- tests
@@ -2318,7 +2318,7 @@ paths:
$ref: "#/components/schemas/WebhookCreateRequest"
text/yaml:
schema:
- type: string
+ type: string
responses:
200:
description: "successful operation"
@@ -2468,7 +2468,7 @@ paths:
$ref: "#/components/schemas/WebhookUpdateRequest"
text/yaml:
schema:
- type: string
+ type: string
responses:
200:
description: "successful operation"
@@ -2555,7 +2555,7 @@ paths:
$ref: "#/components/schemas/TemplateCreateRequest"
text/yaml:
schema:
- type: string
+ type: string
responses:
200:
description: "successful operation"
@@ -2705,7 +2705,7 @@ paths:
$ref: "#/components/schemas/TemplateUpdateRequest"
text/yaml:
schema:
- type: string
+ type: string
responses:
200:
description: "successful operation"
@@ -3546,7 +3546,7 @@ components:
type: array
description: previous test names
items:
- type: string
+ type: string
TestSuiteStep:
type: object
@@ -3732,14 +3732,14 @@ components:
description: object name and namespace
execution:
$ref: "#/components/schemas/Execution"
- description: test step execution
+ description: test step execution
TestSuiteBatchStepExecutionResult:
description: execution result returned from executor
type: object
properties:
step:
- $ref: "#/components/schemas/TestSuiteBatchStep"
+ $ref: "#/components/schemas/TestSuiteBatchStep"
execute:
type: array
items:
@@ -3900,7 +3900,7 @@ components:
description:
type: string
description: test description
- example: "this test is used for that purpose"
+ example: "this test is used for that purpose"
type:
type: string
description: test type
@@ -3975,7 +3975,7 @@ components:
properties:
type:
type: string
- description: |
+ description: |
type of sources a runner can get data from.
string: String content (e.g. Postman JSON file).
file-uri: content stored on the webserver.
@@ -4267,7 +4267,7 @@ components:
example: "sleep 30"
executePostRunScriptBeforeScraping:
type: boolean
- description: execute post run script before scraping (prebuilt executor only)
+ description: execute post run script before scraping (prebuilt executor only)
runningContext:
$ref: "#/components/schemas/RunningContext"
description: running context for the test execution
@@ -4290,7 +4290,7 @@ components:
type: string
slavePodRequest:
$ref: "#/components/schemas/PodRequest"
- description: configuration parameters for executed slave pods
+ description: configuration parameters for executed slave pods
Artifact:
type: object
@@ -4538,7 +4538,7 @@ components:
dashboardUri:
type: string
description: dashboard uri
- example: "http://localhost:8080"
+ example: "http://localhost:8080"
Repository:
description: repository representation for tests in git repositories
@@ -4672,13 +4672,13 @@ components:
properties:
resources:
$ref: "#/components/schemas/PodResourcesRequest"
- description: pod resources request parameters
+ description: pod resources request parameters
podTemplate:
type: string
description: pod template extensions
podTemplateReference:
type: string
- description: name of the template resource
+ description: name of the template resource
PodUpdateRequest:
description: pod request update body
@@ -4696,7 +4696,7 @@ components:
description: pod resources requests
limits:
$ref: "#/components/schemas/ResourceRequest"
- description: pod resources limits
+ description: pod resources limits
PodResourcesUpdateRequest:
description: pod resources update request specification
@@ -4797,7 +4797,7 @@ components:
description: usage mode for arguments
enum:
- append
- - override
+ - override
image:
type: string
description: container image, executor will run inside this image
@@ -4868,7 +4868,7 @@ components:
description: job template extensions
jobTemplateReference:
type: string
- description: name of the template resource
+ description: name of the template resource
cronJobTemplate:
type: string
description: cron job template extensions
@@ -4929,7 +4929,7 @@ components:
type: string
slavePodRequest:
$ref: "#/components/schemas/PodRequest"
- description: configuration parameters for executed slave pods
+ description: configuration parameters for executed slave pods
ExecutionUpdateRequest:
description: test execution request update body
@@ -5210,7 +5210,7 @@ components:
- junit-report
meta:
$ref: "#/components/schemas/ExecutorMeta"
- useDataDirAsWorkingDir:
+ useDataDirAsWorkingDir:
type: boolean
description: use data dir as working dir for executor
@@ -5283,12 +5283,12 @@ components:
description: Slave data for executing tests in distributed environment
type: object
properties:
- image:
+ image:
description: slave image
type: string
example: kubeshop/ex-slaves-image:latest
required:
- - image
+ - image
RunningContext:
description: running context for test or test suite execution
@@ -5382,14 +5382,14 @@ components:
$ref: "#/components/schemas/TestSuiteExecution"
clusterName:
type: string
- description: cluster name of event
+ description: cluster name of event
envs:
type: object
description: "environment variables"
additionalProperties:
type: string
example:
- WEBHOOK_PARAMETER: "any value"
+ WEBHOOK_PARAMETER: "any value"
EventResource:
type: string
@@ -5564,7 +5564,7 @@ components:
conditionSpec:
$ref: "#/components/schemas/TestTriggerConditionSpec"
probeSpec:
- $ref: "#/components/schemas/TestTriggerProbeSpec"
+ $ref: "#/components/schemas/TestTriggerProbeSpec"
action:
$ref: "#/components/schemas/TestTriggerActions"
execution:
@@ -5611,7 +5611,7 @@ components:
nameRegex:
type: string
description: kubernetes resource name regex
- example: nginx.*
+ example: nginx.*
namespace:
type: string
description: resource namespace
@@ -5663,7 +5663,7 @@ components:
type: integer
format: int32
description: duration in seconds the test trigger waits between condition checks
- example: 1
+ example: 1
TestTriggerCondition:
description: supported condition for test triggers
@@ -5685,8 +5685,8 @@ components:
ttl:
type: integer
format: int32
- description: duration in seconds in the past from current time when the condition is still valid
- example: 1
+ description: duration in seconds in the past from current time when the condition is still valid
+ example: 1
TestTriggerConditionStatuses:
description: supported kubernetes condition statuses for test triggers
@@ -5713,7 +5713,7 @@ components:
type: integer
format: int32
description: duration in seconds the test trigger waits between probes
- example: 1
+ example: 1
TestTriggerProbe:
description: supported probe for test triggers
@@ -5722,7 +5722,7 @@ components:
scheme:
type: string
description: test trigger condition probe scheme to connect to host, default is http
- example: http
+ example: http
host:
type: string
description: test trigger condition probe host, default is pod ip or service name
@@ -5735,7 +5735,7 @@ components:
type: integer
format: int32
description: test trigger condition probe port to connect
- example: 80
+ example: 80
headers:
type: object
description: test trigger condition probe headers to submit
@@ -5869,7 +5869,7 @@ components:
body:
type: string
description: template body to use
- example: "{\"id\": \"{{ .Id }}\"}"
+ example: '{"id": "{{ .Id }}"}'
labels:
type: object
description: "template labels"
@@ -6091,7 +6091,7 @@ components:
name: testSuiteExecutionName
schema:
type: string
- description: test suite execution name stated the test suite execution
+ description: test suite execution name stated the test suite execution
Namespace:
in: query
name: namespace
diff --git a/cmd/logs/main.go b/cmd/logs/main.go
index d4a6f93a0b8..bba5a9f32bd 100644
--- a/cmd/logs/main.go
+++ b/cmd/logs/main.go
@@ -66,9 +66,13 @@ func main() {
cfg.StorageCertFile,
cfg.StorageKeyFile,
cfg.StorageCAFile)
+
+ if cfg.Debug {
+ svc.AddAdapter(adapter.NewDebugAdapter())
+ }
+
if err != nil {
log.Errorw("error creating minio adapter, debug adapter created instead", "error", err)
- svc.AddAdapter(adapter.NewDebugAdapter())
} else {
log.Infow("minio adapter created", "bucket", cfg.StorageLogsBucket, "endpoint", cfg.StorageEndpoint)
svc.AddAdapter(minioAdapter)
diff --git a/contrib/executor/gradle/pkg/runner/runner_integration_test.go b/contrib/executor/gradle/pkg/runner/runner_integration_test.go
index 06db2507ede..df2cf97dfc2 100644
--- a/contrib/executor/gradle/pkg/runner/runner_integration_test.go
+++ b/contrib/executor/gradle/pkg/runner/runner_integration_test.go
@@ -3,7 +3,6 @@ package runner
import (
"context"
- "fmt"
"os"
"path/filepath"
"testing"
@@ -88,8 +87,6 @@ func TestRunGradle_Integration(t *testing.T) {
// when
result, err := runner.Run(ctx, *execution)
- fmt.Printf("%+v\n", result)
-
// then
assert.NoError(t, err)
assert.Equal(t, result.Status, testkube.ExecutionStatusPassed)
diff --git a/pkg/api/v1/testkube/model_event.go b/pkg/api/v1/testkube/model_event.go
index 3a31df3d205..63d6ee5e984 100644
--- a/pkg/api/v1/testkube/model_event.go
+++ b/pkg/api/v1/testkube/model_event.go
@@ -12,8 +12,9 @@ package testkube
// Event data
type Event struct {
// UUID of event
- Id string `json:"id"`
- Resource *EventResource `json:"resource"`
+ Id string `json:"id"`
+ StreamTopic string `json:"topic"`
+ Resource *EventResource `json:"resource"`
// ID of resource
ResourceId string `json:"resourceId"`
Type_ *EventType `json:"type"`
diff --git a/pkg/api/v1/testkube/model_event_extended.go b/pkg/api/v1/testkube/model_event_extended.go
index a01edfbb288..945a8ffbbba 100644
--- a/pkg/api/v1/testkube/model_event_extended.go
+++ b/pkg/api/v1/testkube/model_event_extended.go
@@ -7,6 +7,19 @@ import (
"k8s.io/apimachinery/pkg/labels"
)
+const (
+ TestStartSubject = "events.test.start"
+ TestStopSubject = "events.test.stop"
+)
+
+// check if Event implements model generic event type
+var _ Trigger = Event{}
+
+// Trigger for generic events
+type Trigger interface {
+ GetResourceId() string
+}
+
func NewEvent(t *EventType, resource *EventResource, id string) Event {
return Event{
Id: uuid.NewString(),
@@ -21,6 +34,8 @@ func NewEventStartTest(execution *Execution) Event {
Id: uuid.NewString(),
Type_: EventStartTest,
TestExecution: execution,
+ StreamTopic: TestStartSubject,
+ ResourceId: execution.Id,
}
}
@@ -29,6 +44,8 @@ func NewEventEndTestSuccess(execution *Execution) Event {
Id: uuid.NewString(),
Type_: EventEndTestSuccess,
TestExecution: execution,
+ StreamTopic: TestStopSubject,
+ ResourceId: execution.Id,
}
}
@@ -37,6 +54,8 @@ func NewEventEndTestFailed(execution *Execution) Event {
Id: uuid.NewString(),
Type_: EventEndTestFailed,
TestExecution: execution,
+ StreamTopic: TestStopSubject,
+ ResourceId: execution.Id,
}
}
@@ -45,6 +64,8 @@ func NewEventEndTestAborted(execution *Execution) Event {
Id: uuid.NewString(),
Type_: EventEndTestAborted,
TestExecution: execution,
+ StreamTopic: TestStopSubject,
+ ResourceId: execution.Id,
}
}
@@ -53,6 +74,8 @@ func NewEventEndTestTimeout(execution *Execution) Event {
Id: uuid.NewString(),
Type_: EventEndTestTimeout,
TestExecution: execution,
+ StreamTopic: TestStopSubject,
+ ResourceId: execution.Id,
}
}
@@ -184,6 +207,10 @@ func (e Event) Valid(selector string, types []EventType) (valid bool) {
// Topic returns topic for event based on resource and resource id
// or fallback to global "events" topic
func (e Event) Topic() string {
+ if e.StreamTopic != "" {
+ return e.StreamTopic
+ }
+
if e.Resource == nil {
return "events.all"
}
@@ -194,3 +221,8 @@ func (e Event) Topic() string {
return "events." + string(*e.Resource) + "." + e.ResourceId
}
+
+// GetResourceId implmenents generic event trigger
+func (e Event) GetResourceId() string {
+ return e.ResourceId
+}
diff --git a/pkg/api/v1/testkube/model_event_extended_test.go b/pkg/api/v1/testkube/model_event_extended_test.go
index 8e0741beab1..b7853bdbe68 100644
--- a/pkg/api/v1/testkube/model_event_extended_test.go
+++ b/pkg/api/v1/testkube/model_event_extended_test.go
@@ -116,6 +116,11 @@ func TestEvent_IsSuccess(t *testing.T) {
func TestEvent_Topic(t *testing.T) {
+ t.Run("should return events topic if explicitly set", func(t *testing.T) {
+ evt := Event{Type_: EventStartTest, StreamTopic: "topic"}
+ assert.Equal(t, "topic", evt.Topic())
+ })
+
t.Run("should return events topic if not resource set", func(t *testing.T) {
evt := Event{Type_: EventStartTest, Resource: nil}
assert.Equal(t, "events.all", evt.Topic())
diff --git a/pkg/logs/adapter/minio.go b/pkg/logs/adapter/minio.go
index 63cba09d8b7..d3afbb9fade 100644
--- a/pkg/logs/adapter/minio.go
+++ b/pkg/logs/adapter/minio.go
@@ -177,7 +177,8 @@ func (s *MinioAdapter) combineData(ctxt context.Context, minioClient *minio.Clie
s.Log.Errorw("error putting object", "err", err)
return err
}
- s.Log.Debugw("put object successfully", "id", id, "s.bucket", s.bucket, "parts", parts)
+
+ s.Log.Debugw("data combined", "id", id, "s.bucket", s.bucket, "parts", parts)
if deleteIntermediaryData {
for i := 0; i < parts; i++ {
diff --git a/pkg/logs/client/mock_client.go b/pkg/logs/client/mock_client.go
new file mode 100644
index 00000000000..a87031ea1af
--- /dev/null
+++ b/pkg/logs/client/mock_client.go
@@ -0,0 +1,50 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/kubeshop/testkube/pkg/logs/client (interfaces: Client)
+
+// Package client is a generated GoMock package.
+package client
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ events "github.com/kubeshop/testkube/pkg/logs/events"
+)
+
+// MockClient is a mock of Client interface.
+type MockClient struct {
+ ctrl *gomock.Controller
+ recorder *MockClientMockRecorder
+}
+
+// MockClientMockRecorder is the mock recorder for MockClient.
+type MockClientMockRecorder struct {
+ mock *MockClient
+}
+
+// NewMockClient creates a new mock instance.
+func NewMockClient(ctrl *gomock.Controller) *MockClient {
+ mock := &MockClient{ctrl: ctrl}
+ mock.recorder = &MockClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockClient) EXPECT() *MockClientMockRecorder {
+ return m.recorder
+}
+
+// Get mocks base method.
+func (m *MockClient) Get(arg0 context.Context, arg1 string) chan events.LogResponse {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Get", arg0, arg1)
+ ret0, _ := ret[0].(chan events.LogResponse)
+ return ret0
+}
+
+// Get indicates an expected call of Get.
+func (mr *MockClientMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), arg0, arg1)
+}
diff --git a/pkg/logs/client/stream.go b/pkg/logs/client/stream.go
index 9e20732d78f..08a23efc445 100644
--- a/pkg/logs/client/stream.go
+++ b/pkg/logs/client/stream.go
@@ -122,7 +122,7 @@ func (c NatsLogStream) Get(ctx context.Context, id string) (chan events.LogRespo
// syncCall sends request to given subject and waits for response
func (c NatsLogStream) syncCall(ctx context.Context, subject, id string) (resp StreamResponse, err error) {
- b, err := json.Marshal(events.Trigger{Id: id})
+ b, err := json.Marshal(events.NewTrigger(id))
if err != nil {
return resp, err
}
diff --git a/pkg/logs/client/stream_test.go b/pkg/logs/client/stream_test.go
index 7d3ed1a8845..03cafc96eac 100644
--- a/pkg/logs/client/stream_test.go
+++ b/pkg/logs/client/stream_test.go
@@ -25,7 +25,7 @@ func TestStream_StartStop(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, StreamPrefix+id, meta.Name)
- err = client.PushBytes(ctx, id, []byte(`{"content":"hello 1"}`))
+ err = client.PushBytes(ctx, id, []byte(`{"resourceId":"hello 1"}`))
assert.NoError(t, err)
var startReceived, stopReceived bool
diff --git a/pkg/logs/config/logs_config.go b/pkg/logs/config/logs_config.go
index 49c4859e1ec..51fe916a5a5 100644
--- a/pkg/logs/config/logs_config.go
+++ b/pkg/logs/config/logs_config.go
@@ -7,6 +7,7 @@ import (
)
type Config struct {
+ Debug bool `envconfig:"DEBUG" default:"false"`
NatsURI string `envconfig:"NATS_URI" default:"nats://localhost:4222"`
NatsSecure bool `envconfig:"NATS_SECURE" default:"false"`
NatsSkipVerify bool `envconfig:"NATS_SKIP_VERIFY" default:"false"`
diff --git a/pkg/logs/events.go b/pkg/logs/events.go
index cb3ac9a5edf..cc47db43c79 100644
--- a/pkg/logs/events.go
+++ b/pkg/logs/events.go
@@ -10,6 +10,7 @@ import (
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
+ "github.com/kubeshop/testkube/pkg/api/v1/testkube"
"github.com/kubeshop/testkube/pkg/logs/adapter"
"github.com/kubeshop/testkube/pkg/logs/events"
"github.com/kubeshop/testkube/pkg/logs/state"
@@ -21,11 +22,23 @@ const (
StreamPrefix = "log"
- StartSubject = "events.logs.start"
- StopSubject = "events.logs.stop"
-
StartQueue = "logsstart"
StopQueue = "logsstop"
+
+ LogStartSubject = "events.logs.start"
+ LogStopSubject = "events.logs.stop"
+)
+
+var (
+ StartSubjects = map[string]string{
+ "test": testkube.TestStartSubject,
+ "generic": LogStartSubject,
+ }
+
+ StopSubjects = map[string]string{
+ "test": testkube.TestStopSubject,
+ "generic": LogStopSubject,
+ }
)
type Consumer struct {
@@ -47,9 +60,9 @@ func (ls *LogsService) initConsumer(ctx context.Context, a adapter.Adapter, stre
})
}
-func (ls *LogsService) createStream(ctx context.Context, event events.Trigger) (jetstream.Stream, error) {
+func (ls *LogsService) createStream(ctx context.Context, id string) (jetstream.Stream, error) {
// create stream for incoming logs
- streamName := StreamPrefix + event.Id
+ streamName := StreamPrefix + id
return ls.js.CreateOrUpdateStream(ctx, jetstream.StreamConfig{
Name: streamName,
Storage: jetstream.FileStorage, // durable stream as we can hit mem limit
@@ -57,8 +70,8 @@ func (ls *LogsService) createStream(ctx context.Context, event events.Trigger) (
}
// handleMessage will handle incoming message from logs stream and proxy it to given adapter
-func (ls *LogsService) handleMessage(a adapter.Adapter, event events.Trigger) func(msg jetstream.Msg) {
- log := ls.log.With("id", event.Id, "consumer", a.Name())
+func (ls *LogsService) handleMessage(a adapter.Adapter, id string) func(msg jetstream.Msg) {
+ log := ls.log.With("id", id, "adapter", a.Name())
return func(msg jetstream.Msg) {
log.Debugw("got message", "data", string(msg.Data()))
@@ -74,7 +87,7 @@ func (ls *LogsService) handleMessage(a adapter.Adapter, event events.Trigger) fu
return
}
- err = a.Notify(event.Id, logChunk)
+ err = a.Notify(id, logChunk)
if err != nil {
if err := msg.Nak(); err != nil {
log.Errorw("error nacking message", "error", err)
@@ -90,7 +103,7 @@ func (ls *LogsService) handleMessage(a adapter.Adapter, event events.Trigger) fu
}
// handleStart will handle start event and create logs consumers, also manage state of given (execution) id
-func (ls *LogsService) handleStart(ctx context.Context) func(msg *nats.Msg) {
+func (ls *LogsService) handleStart(ctx context.Context, subject string) func(msg *nats.Msg) {
return func(msg *nats.Msg) {
event := events.Trigger{}
err := json.Unmarshal(msg.Data, &event)
@@ -98,23 +111,24 @@ func (ls *LogsService) handleStart(ctx context.Context) func(msg *nats.Msg) {
ls.log.Errorw("can't handle start event", "error", err)
return
}
- log := ls.log.With("id", event.Id, "event", "start")
+ id := event.ResourceId
+ log := ls.log.With("id", id, "event", "start")
- ls.state.Put(ctx, event.Id, state.LogStatePending)
- s, err := ls.createStream(ctx, event)
+ ls.state.Put(ctx, id, state.LogStatePending)
+ s, err := ls.createStream(ctx, id)
if err != nil {
- ls.log.Errorw("error creating stream", "error", err, "id", event.Id)
+ ls.log.Errorw("error creating stream", "error", err, "id", id)
return
}
log.Infow("stream created", "stream", s)
- streamName := StreamPrefix + event.Id
+ streamName := StreamPrefix + id
// for each adapter create NATS consumer and consume stream from it e.g. cloud s3 or others
for i, adapter := range ls.adapters {
l := log.With("adapter", adapter.Name())
- c, err := ls.initConsumer(ctx, adapter, streamName, event.Id, i)
+ c, err := ls.initConsumer(ctx, adapter, streamName, id, i)
if err != nil {
log.Errorw("error creating consumer", "error", err)
return
@@ -122,34 +136,38 @@ func (ls *LogsService) handleStart(ctx context.Context) func(msg *nats.Msg) {
// handle message per each adapter
l.Infow("consumer created", "consumer", c.CachedInfo(), "stream", streamName)
- cons, err := c.Consume(ls.handleMessage(adapter, event))
+ cons, err := c.Consume(ls.handleMessage(adapter, id))
if err != nil {
log.Errorw("error creating consumer", "error", err, "consumer", c.CachedInfo())
continue
}
+ consumerName := id + "_" + adapter.Name() + "_" + subject
// store consumer instance so we can stop it later in StopSubject handler
- ls.consumerInstances.Store(event.Id+"_"+adapter.Name(), Consumer{
- Name: event.Id + "_" + adapter.Name(),
+ ls.consumerInstances.Store(consumerName, Consumer{
+ Name: consumerName,
Context: cons,
Instance: c,
})
- l.Infow("consumer started", "consumer", adapter.Name(), "id", event.Id, "stream", streamName)
+ l.Infow("consumer started", "adapter", adapter.Name(), "id", id, "stream", streamName)
}
- // reply to start event that everything was initialized correctly
- err = msg.Respond([]byte("ok"))
- if err != nil {
- log.Errorw("error responding to start event", "error", err)
- return
+ // confirm when reply is set
+ if msg.Reply != "" {
+ // reply to start event that everything was initialized correctly
+ err = msg.Respond([]byte("ok"))
+ if err != nil {
+ log.Errorw("error responding to start event", "error", err)
+ return
+ }
}
}
}
// handleStop will handle stop event and stop logs consumers, also clean consumers state
-func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
+func (ls *LogsService) handleStop(ctx context.Context, group string) func(msg *nats.Msg) {
return func(msg *nats.Msg) {
var (
wg sync.WaitGroup
@@ -165,29 +183,33 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
return
}
- l := ls.log.With("id", event.Id, "event", "stop")
+ id := event.ResourceId
- err = msg.Respond([]byte("stop-queued"))
- if err != nil {
- l.Errorw("error responding to stop event", "error", err)
+ l := ls.log.With("id", id, "event", "stop")
+
+ if msg.Reply != "" {
+ err = msg.Respond([]byte("stop-queued"))
+ if err != nil {
+ l.Errorw("error responding to stop event", "error", err)
+ }
}
for _, adapter := range ls.adapters {
- consumerName := event.Id + "_" + adapter.Name()
+ consumerName := id + "_" + adapter.Name() + "_" + group
// locate consumer on this pod
c, found := ls.consumerInstances.Load(consumerName)
- l.Debugw("consumer instance", "c", c, "found", found, "name", consumerName)
if !found {
l.Debugw("consumer not found on this pod", "found", found, "name", consumerName)
continue
}
+ l.Debugw("consumer instance found", "c", c, "found", found, "name", consumerName)
// stop consumer
wg.Add(1)
stopped++
consumer := c.(Consumer)
- go ls.stopConsumer(ctx, &wg, consumer, adapter, event.Id)
+ go ls.stopConsumer(ctx, &wg, consumer, adapter, id)
}
@@ -195,8 +217,8 @@ func (ls *LogsService) handleStop(ctx context.Context) func(msg *nats.Msg) {
l.Debugw("wait completed")
if stopped > 0 {
- ls.state.Put(ctx, event.Id, state.LogStateFinished)
- l.Infow("execution logs consumers stopped", "id", event.Id, "stopped", stopped)
+ ls.state.Put(ctx, event.ResourceId, state.LogStateFinished)
+ l.Infow("execution logs consumers stopped", "id", event.ResourceId, "stopped", stopped)
} else {
l.Debugw("no consumers found on this pod to stop")
}
diff --git a/pkg/logs/events/events.go b/pkg/logs/events/events.go
index 0febcf09c4c..26e5ef8c697 100644
--- a/pkg/logs/events/events.go
+++ b/pkg/logs/events/events.go
@@ -9,13 +9,6 @@ import (
"github.com/kubeshop/testkube/pkg/executor/output"
)
-// Generic event like log-start log-end
-type Trigger struct {
- Id string `json:"id,omitempty"`
- Type string `json:"type,omitempty"`
- Metadata map[string]string `json:"metadata,omitempty"`
-}
-
type LogVersion string
const (
@@ -30,6 +23,24 @@ const (
SourceJobExecutor = "job-executor"
)
+// check if trigger implements model generic event type
+var _ testkube.Trigger = Trigger{}
+
+// NewTrigger returns Trigger instance
+func NewTrigger(id string) Trigger {
+ return Trigger{ResourceId: id}
+}
+
+// Generic event like log-start log-end with resource id
+type Trigger struct {
+ ResourceId string `json:"resourceId,omitempty"`
+}
+
+// GetResourceId implements testkube.Trigger interface
+func (t Trigger) GetResourceId() string {
+ return t.ResourceId
+}
+
type LogResponse struct {
Log Log
Error error
diff --git a/pkg/logs/events_test.go b/pkg/logs/events_test.go
index fefbc3a3b4f..3ee58009605 100644
--- a/pkg/logs/events_test.go
+++ b/pkg/logs/events_test.go
@@ -7,9 +7,12 @@ import (
"testing"
"time"
+ "github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
+ "github.com/kubeshop/testkube/pkg/api/v1/testkube"
+ "github.com/kubeshop/testkube/pkg/event"
"github.com/kubeshop/testkube/pkg/event/bus"
"github.com/kubeshop/testkube/pkg/logs/adapter"
"github.com/kubeshop/testkube/pkg/logs/client"
@@ -93,6 +96,82 @@ func TestLogs_EventsFlow(t *testing.T) {
assert.Equal(t, 0, log.GetConsumersStats(ctx).Count)
})
+ t.Run("should start and stop on test event", func(t *testing.T) {
+ // given context with 1s deadline
+ ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
+ defer cancel()
+
+ // and NATS test server with connection
+ ns, nc := bus.TestServerWithConnection()
+ defer ns.Shutdown()
+
+ id := "id1"
+
+ // and jetstream configured
+ js, err := jetstream.New(nc)
+ assert.NoError(t, err)
+
+ // and KV store
+ kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "start-stop-on-test"})
+ assert.NoError(t, err)
+ assert.NotNil(t, kv)
+
+ // and logs state manager
+ state := state.NewState(kv)
+
+ // and initialized log service
+ log := NewLogsService(nc, js, state).
+ WithRandomPort()
+
+ // given example adapter
+ a := NewMockAdapter()
+
+ messagesCount := 10000
+
+ // with 4 adapters (the same adapter is added 4 times so it'll receive 4 times more messages)
+ log.AddAdapter(a)
+
+ // and log service running
+ go func() {
+ log.Run(ctx)
+ }()
+
+ // and test event emitter
+ ec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
+ assert.NoError(t, err)
+ eventBus := bus.NewNATSBus(ec)
+ emitter := event.NewEmitter(eventBus, "test-cluster", map[string]string{})
+
+ // and stream client
+ stream, err := client.NewNatsLogStream(nc)
+ assert.NoError(t, err)
+
+ // and initialized log stream for given ID
+ meta, err := stream.Init(ctx, id)
+ assert.NotEmpty(t, meta.Name)
+ assert.NoError(t, err)
+
+ // and ready to get messages
+ <-log.Ready
+
+ // when start event triggered
+ emitter.Notify(testkube.NewEventStartTest(&testkube.Execution{Id: "id1"}))
+
+ for i := 0; i < messagesCount; i++ {
+ // and when data pushed to the log stream
+ err = stream.Push(ctx, id, events.NewLog("hello"))
+ assert.NoError(t, err)
+ }
+
+ // and wait for message to be propagated
+ emitter.Notify(testkube.NewEventEndTestFailed(&testkube.Execution{Id: "id1"}))
+
+ time.Sleep(time.Second)
+
+ assertMessagesCount(t, a, messagesCount)
+
+ })
+
t.Run("should react on new message and pass data to adapter", func(t *testing.T) {
// given context with 1s deadline
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
diff --git a/pkg/logs/service.go b/pkg/logs/service.go
index 12eff7a0dff..ddea90df5a1 100644
--- a/pkg/logs/service.go
+++ b/pkg/logs/service.go
@@ -95,11 +95,16 @@ func (ls *LogsService) Run(ctx context.Context) (err error) {
// For start event we must build stream for given execution id and start consuming it
// this one will must follow a queue group each pod will get it's own bunch of executions to handle
// Start event will be triggered by logs process controller (scheduler)
- ls.nats.QueueSubscribe(StartSubject, StartQueue, ls.handleStart(ctx))
+ // group is common name for both start and stop subjects
+ for group, subject := range StartSubjects {
+ ls.nats.QueueSubscribe(subject, StartQueue, ls.handleStart(ctx, group))
+ }
// listen on all pods as we don't control which one will have given consumer
// Stop event will be triggered by logs process controller (scheduler)
- ls.nats.Subscribe(StopSubject, ls.handleStop(ctx))
+ for group, subject := range StopSubjects {
+ ls.nats.Subscribe(subject, ls.handleStop(ctx, group))
+ }
// Send ready signal
ls.Ready <- struct{}{}
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index 9eb8482b380..cc71410a442 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -79,18 +79,8 @@ func (s *Scheduler) executeTest(ctx context.Context, test testkube.Test, request
execution = newExecutionFromExecutionOptions(options)
options.ID = execution.Id
- // TODO consider using single event for test start and logs
s.events.Notify(testkube.NewEventStartTest(&execution))
- // for logs.v2 service trigger start / stop events
- if s.featureFlags.LogsV2 {
- err := s.triggerLogsStartEvent(ctx, execution.Id)
- if err != nil {
- return execution, err
- }
- defer s.triggerLogsStopEvent(ctx, execution.Id)
- }
-
if err := s.createSecretsReferences(&execution); err != nil {
return s.handleExecutionError(ctx, execution, "can't create secret variables `Secret` references: %w", err)
}
From 58df8aeeb2da517f003334c869a7718c6c82857a Mon Sep 17 00:00:00 2001
From: Jacek Wysocki
Date: Tue, 30 Jan 2024 14:51:04 +0100
Subject: [PATCH 052/234] fix: dummy adapter use structured logging (#4957)
* fix: dummy adapter use structured logging
* fix: golang ci fixes
---
pkg/logs/adapter/dummy.go | 13 ++++++-----
pkg/logs/adapter/minio.go | 6 ++++--
pkg/scheduler/test_scheduler.go | 38 ---------------------------------
3 files changed, 12 insertions(+), 45 deletions(-)
diff --git a/pkg/logs/adapter/dummy.go b/pkg/logs/adapter/dummy.go
index 489ee3af206..4366740092a 100644
--- a/pkg/logs/adapter/dummy.go
+++ b/pkg/logs/adapter/dummy.go
@@ -1,8 +1,9 @@
package adapter
import (
- "fmt"
+ "go.uber.org/zap"
+ "github.com/kubeshop/testkube/pkg/log"
"github.com/kubeshop/testkube/pkg/logs/events"
)
@@ -10,20 +11,22 @@ var _ Adapter = &DebugAdapter{}
// NewDebugAdapter creates new DebugAdapter which will write logs to stdout
func NewDebugAdapter() *DebugAdapter {
- return &DebugAdapter{}
+ return &DebugAdapter{
+ l: log.DefaultLogger,
+ }
}
type DebugAdapter struct {
- Bucket string
+ l *zap.SugaredLogger
}
func (s *DebugAdapter) Notify(id string, e events.Log) error {
- fmt.Printf("%s %+v\n", id, e)
+ s.l.Debugw("got event", "id", id, "event", e)
return nil
}
func (s *DebugAdapter) Stop(id string) error {
- fmt.Printf("stopping %s \n", id)
+ s.l.Debugw("Stopping", "id", id)
return nil
}
diff --git a/pkg/logs/adapter/minio.go b/pkg/logs/adapter/minio.go
index d3afbb9fade..77f7c9715d8 100644
--- a/pkg/logs/adapter/minio.go
+++ b/pkg/logs/adapter/minio.go
@@ -172,13 +172,14 @@ func (s *MinioAdapter) combineData(ctxt context.Context, minioClient *minio.Clie
}
}
}
- _, err := minioClient.PutObject(ctxt, s.bucket, id, buffer, int64(buffer.Len()), minio.PutObjectOptions{ContentType: "application/octet-stream"})
+
+ info, err := minioClient.PutObject(ctxt, s.bucket, id, buffer, int64(buffer.Len()), minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
s.Log.Errorw("error putting object", "err", err)
return err
}
- s.Log.Debugw("data combined", "id", id, "s.bucket", s.bucket, "parts", parts)
+ s.Log.Debugw("data combined", "id", id, "s.bucket", s.bucket, "parts", parts, "uploadinfo", info)
if deleteIntermediaryData {
for i := 0; i < parts; i++ {
@@ -192,6 +193,7 @@ func (s *MinioAdapter) combineData(ctxt context.Context, minioClient *minio.Clie
}
}
}
+
buffer.Reset()
if len(returnedError) == 0 {
return nil
diff --git a/pkg/scheduler/test_scheduler.go b/pkg/scheduler/test_scheduler.go
index cc71410a442..26c31ff84f3 100644
--- a/pkg/scheduler/test_scheduler.go
+++ b/pkg/scheduler/test_scheduler.go
@@ -831,41 +831,3 @@ func mergeSlavePodRequests(podBase *testkube.PodRequest, podAdjust *testkube.Pod
return podBase
}
-
-func (s *Scheduler) triggerLogsStartEvent(ctx context.Context, id string) error {
- if s.featureFlags.LogsV2 {
- r, err := s.logsStream.Start(ctx, id)
- if err != nil {
- return err
- }
-
- if r.Error {
- return errors.New(string(r.Message))
- }
-
- s.logger.Infow("triggering logs start event", "id", id)
- }
-
- return nil
-}
-
-func (s *Scheduler) triggerLogsStopEvent(ctx context.Context, id string) error {
- if s.featureFlags.LogsV2 {
- // as Stop is synchro
- go func() {
- r, err := s.logsStream.Stop(ctx, id)
- if err != nil {
- s.logger.Errorw("can't send stop event for logs", "id", id, "error", err)
- return
- }
-
- if r.Error {
- s.logger.Errorw("received invalid response from log stream on stop event", "id", id, "response", r)
- return
- }
-
- s.logger.Infow("triggering logs stop event", "id", id, "response", string(r.Message))
- }()
- }
- return nil
-}
From bb695bc569b75df9685153b18fca9aabf0d38506 Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 31 Jan 2024 12:09:32 +0200
Subject: [PATCH 053/234] test logs sidecar
---
.../workflows/release-dev-log-sidecar.yaml | 1 +
.github/workflows/release-log-sidecar.yaml | 1 -
.../.goreleaser-docker-build-logs-server.yml | 20 +++++++++++--------
3 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/release-dev-log-sidecar.yaml b/.github/workflows/release-dev-log-sidecar.yaml
index 474987cd81e..2c0c0feea89 100644
--- a/.github/workflows/release-dev-log-sidecar.yaml
+++ b/.github/workflows/release-dev-log-sidecar.yaml
@@ -4,6 +4,7 @@ on:
push:
branches:
- develop
+ - ci/logs-service-fix
permissions:
id-token: write
diff --git a/.github/workflows/release-log-sidecar.yaml b/.github/workflows/release-log-sidecar.yaml
index c2f36907536..1523e522d8b 100644
--- a/.github/workflows/release-log-sidecar.yaml
+++ b/.github/workflows/release-log-sidecar.yaml
@@ -68,7 +68,6 @@ jobs:
DOCKER_BUILDX_CACHE_FROM: "type=gha"
DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max"
ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }}
- DOCKER_IMAGE_TAG: ${{ steps.github_sha.outputs.sha_short }}
- name: Push Docker images
run: |
diff --git a/goreleaser_files/.goreleaser-docker-build-logs-server.yml b/goreleaser_files/.goreleaser-docker-build-logs-server.yml
index 69754ae1a37..122f40df07a 100644
--- a/goreleaser_files/.goreleaser-docker-build-logs-server.yml
+++ b/goreleaser_files/.goreleaser-docker-build-logs-server.yml
@@ -5,12 +5,14 @@ env:
# https://github.com/goreleaser/goreleaser/pull/3199
# To use a builder other than "default", set this variable.
# Necessary for, e.g., GitHub actions cache integration.
+ - DOCKER_REPO={{ if index .Env "DOCKER_REPO" }}{{ .Env.DOCKER_REPO }}{{ else }}kubeshop{{ end }}
- DOCKER_BUILDX_BUILDER={{ if index .Env "DOCKER_BUILDX_BUILDER" }}{{ .Env.DOCKER_BUILDX_BUILDER }}{{ else }}default{{ end }}
# Setup to enable Docker to use, e.g., the GitHub actions cache; see
# https://docs.docker.com/build/building/cache/backends/
# https://github.com/moby/buildkit#export-cache
- DOCKER_BUILDX_CACHE_FROM={{ if index .Env "DOCKER_BUILDX_CACHE_FROM" }}{{ .Env.DOCKER_BUILDX_CACHE_FROM }}{{ else }}type=registry{{ end }}
- DOCKER_BUILDX_CACHE_TO={{ if index .Env "DOCKER_BUILDX_CACHE_TO" }}{{ .Env.DOCKER_BUILDX_CACHE_TO }}{{ else }}type=inline{{ end }}
+ - DOCKER_IMAGE_TAG={{ if index .Env "DOCKER_IMAGE_TAG" }}{{ .Env.DOCKER_IMAGE_TAG }}{{ else }}{{ end }}
builds:
- id: "linux"
main: ./cmd/logs
@@ -31,7 +33,8 @@ dockers:
goos: linux
goarch: amd64
image_templates:
- - "kubeshop/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-amd64"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Version }}-amd64{{ end }}"
+ - "{{ if .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-amd64{{ end }}"
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.title={{ .ProjectName }}"
@@ -48,7 +51,8 @@ dockers:
goos: linux
goarch: arm64
image_templates:
- - "kubeshop/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Version }}-arm64v8{{ end }}"
+ - "{{ if .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8{{ end }}"
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--label=org.opencontainers.image.created={{ .Date }}"
@@ -61,14 +65,14 @@ dockers:
- "--build-arg=ALPINE_IMAGE={{ .Env.ALPINE_IMAGE }}"
docker_manifests:
- - name_template: kubeshop/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}
+ - name_template: "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Version }}{{ end }}"
image_templates:
- - kubeshop/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-amd64
- - kubeshop/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8
- - name_template: kubeshop/testkube-logs-server:latest
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Version }}-amd64{{ end }}"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Version }}-arm64v8{{ end }}"
+ - name_template: "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:latest{{ end }}"
image_templates:
- - kubeshop/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-amd64
- - kubeshop/testkube-logs-server:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Version }}-amd64{{ end }}"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-server:{{ .Version }}-arm64v8{{ end }}"
release:
disable: true
From 0e89c424465323f3b19ccc4d9ec5ce3a43c3bbab Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 31 Jan 2024 12:18:32 +0200
Subject: [PATCH 054/234] test logs release
---
.github/workflows/release-dev-log-server.yaml | 1 +
.github/workflows/release-log-server.yaml | 25 +++++++++----------
.github/workflows/release-log-sidecar.yaml | 22 ++++++++--------
.../.goreleaser-docker-build-logs-sidecar.yml | 21 ++++++++++------
4 files changed, 37 insertions(+), 32 deletions(-)
diff --git a/.github/workflows/release-dev-log-server.yaml b/.github/workflows/release-dev-log-server.yaml
index 9192c4b7e1b..024a3dfb077 100644
--- a/.github/workflows/release-dev-log-server.yaml
+++ b/.github/workflows/release-dev-log-server.yaml
@@ -4,6 +4,7 @@ on:
push:
branches:
- develop
+ - ci/logs-service-fix
permissions:
id-token: write
diff --git a/.github/workflows/release-log-server.yaml b/.github/workflows/release-log-server.yaml
index 4688b1e589d..382cc9d1464 100644
--- a/.github/workflows/release-log-server.yaml
+++ b/.github/workflows/release-log-server.yaml
@@ -3,7 +3,7 @@ name: Release logs server
on:
push:
tags:
- - "v[0-9]+.[0-9]+.[0-9]+"
+ - "v[0-9]+.[0-9]+.[0-9]+-*"
permissions:
id-token: write
@@ -68,19 +68,18 @@ jobs:
DOCKER_BUILDX_CACHE_FROM: "type=gha"
DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max"
ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }}
- DOCKER_IMAGE_TAG: ${{ steps.github_sha.outputs.sha_short }}
- - name: Push Docker images
- run: |
- docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64
- # adding the docker manifest for the latest image tag
- docker manifest create kubeshop/testkube-logs-server:latest \
- kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64 \
- kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker manifest annotate kubeshop/testkube-logs-server:latest kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64 --arch amd64
- docker manifest annotate kubeshop/testkube-logs-server:latest kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --arch arm64 --variant v8
- docker manifest push kubeshop/testkube-logs-server:latest
+# - name: Push Docker images
+# run: |
+# docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
+# docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64
+# # adding the docker manifest for the latest image tag
+# docker manifest create kubeshop/testkube-logs-server:latest \
+# kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64 \
+# kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
+# docker manifest annotate kubeshop/testkube-logs-server:latest kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64 --arch amd64
+# docker manifest annotate kubeshop/testkube-logs-server:latest kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --arch arm64 --variant v8
+# docker manifest push kubeshop/testkube-logs-server:latest
- name: Push README to Dockerhub
uses: christian-korneck/update-container-description-action@v1
diff --git a/.github/workflows/release-log-sidecar.yaml b/.github/workflows/release-log-sidecar.yaml
index 1523e522d8b..cd7ad6c1d19 100644
--- a/.github/workflows/release-log-sidecar.yaml
+++ b/.github/workflows/release-log-sidecar.yaml
@@ -69,17 +69,17 @@ jobs:
DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max"
ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }}
- - name: Push Docker images
- run: |
- docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64
- # adding the docker manifest for the latest image tag
- docker manifest create kubeshop/testkube-logs-sidecar:latest \
- kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64 \
- kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
- docker manifest annotate kubeshop/testkube-logs-sidecar:latest kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64 --arch amd64
- docker manifest annotate kubeshop/testkube-logs-sidecar:latest kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --arch arm64 --variant v8
- docker manifest push kubeshop/testkube-logs-sidecar:latest
+# - name: Push Docker images
+# run: |
+# docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
+# docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64
+# # adding the docker manifest for the latest image tag
+# docker manifest create kubeshop/testkube-logs-sidecar:latest \
+# kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64 \
+# kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
+# docker manifest annotate kubeshop/testkube-logs-sidecar:latest kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64 --arch amd64
+# docker manifest annotate kubeshop/testkube-logs-sidecar:latest kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --arch arm64 --variant v8
+# docker manifest push kubeshop/testkube-logs-sidecar:latest
- name: Push README to Dockerhub
uses: christian-korneck/update-container-description-action@v1
diff --git a/goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml b/goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml
index a11a8b20e35..4979267192e 100644
--- a/goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml
+++ b/goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml
@@ -5,12 +5,14 @@ env:
# https://github.com/goreleaser/goreleaser/pull/3199
# To use a builder other than "default", set this variable.
# Necessary for, e.g., GitHub actions cache integration.
+ - DOCKER_REPO={{ if index .Env "DOCKER_REPO" }}{{ .Env.DOCKER_REPO }}{{ else }}kubeshop{{ end }}
- DOCKER_BUILDX_BUILDER={{ if index .Env "DOCKER_BUILDX_BUILDER" }}{{ .Env.DOCKER_BUILDX_BUILDER }}{{ else }}default{{ end }}
# Setup to enable Docker to use, e.g., the GitHub actions cache; see
# https://docs.docker.com/build/building/cache/backends/
# https://github.com/moby/buildkit#export-cache
- DOCKER_BUILDX_CACHE_FROM={{ if index .Env "DOCKER_BUILDX_CACHE_FROM" }}{{ .Env.DOCKER_BUILDX_CACHE_FROM }}{{ else }}type=registry{{ end }}
- DOCKER_BUILDX_CACHE_TO={{ if index .Env "DOCKER_BUILDX_CACHE_TO" }}{{ .Env.DOCKER_BUILDX_CACHE_TO }}{{ else }}type=inline{{ end }}
+ - DOCKER_IMAGE_TAG={{ if index .Env "DOCKER_IMAGE_TAG" }}{{ .Env.DOCKER_IMAGE_TAG }}{{ else }}{{ end }}
builds:
- id: "linux"
main: ./cmd/sidecar
@@ -31,7 +33,8 @@ dockers:
goos: linux
goarch: amd64
image_templates:
- - "kubeshop/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-amd64"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Version }}-amd64{{ end }}"
+ - "{{ if .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-amd64{{ end }}"
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.title={{ .ProjectName }}"
@@ -48,7 +51,8 @@ dockers:
goos: linux
goarch: arm64
image_templates:
- - "kubeshop/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Version }}-arm64v8{{ end }}"
+ - "{{ if .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8{{ end }}"
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--label=org.opencontainers.image.created={{ .Date }}"
@@ -61,14 +65,15 @@ dockers:
- "--build-arg=ALPINE_IMAGE={{ .Env.ALPINE_IMAGE }}"
docker_manifests:
- - name_template: kubeshop/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}
+ - name_template: "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Version }}{{ end }}"
image_templates:
- - kubeshop/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-amd64
- - kubeshop/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8
- - name_template: kubeshop/testkube-logs-sidecar:latest
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Version }}-amd64{{ end }}"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Version }}-arm64v8{{ end }}"
+ - name_template: "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:latest{{ end }}"
image_templates:
- - kubeshop/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-amd64
- - kubeshop/testkube-logs-sidecar:{{ .Env.DOCKER_IMAGE_TAG }}-arm64v8
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Version }}-amd64{{ end }}"
+ - "{{ if not .Env.DOCKER_IMAGE_TAG }}{{ .Env.DOCKER_REPO }}/testkube-logs-sidecar:{{ .Version }}-arm64v8{{ end }}"
+
release:
disable: true
From 64683c2e51b9aafe03b051476909cf6722ea5434 Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 31 Jan 2024 12:23:04 +0200
Subject: [PATCH 055/234] release logs service
---
.github/workflows/release-log-server.yaml | 2 +-
.github/workflows/release-log-sidecar.yaml | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/release-log-server.yaml b/.github/workflows/release-log-server.yaml
index 382cc9d1464..fc0480b0424 100644
--- a/.github/workflows/release-log-server.yaml
+++ b/.github/workflows/release-log-server.yaml
@@ -59,7 +59,7 @@ jobs:
with:
distribution: goreleaser-pro
version: latest
- args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-server.yml --skip-publish
+ args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-server.yml
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
diff --git a/.github/workflows/release-log-sidecar.yaml b/.github/workflows/release-log-sidecar.yaml
index cd7ad6c1d19..d152e0f9bcc 100644
--- a/.github/workflows/release-log-sidecar.yaml
+++ b/.github/workflows/release-log-sidecar.yaml
@@ -3,7 +3,7 @@ name: Release logs sidecar
on:
push:
tags:
- - "v[0-9]+.[0-9]+.[0-9]+"
+ - "v[0-9]+.[0-9]+.[0-9]+-*"
permissions:
id-token: write
@@ -59,7 +59,7 @@ jobs:
with:
distribution: goreleaser-pro
version: latest
- args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml --skip-publish
+ args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
From 74353289eb5458b9d81cf28868286586d65e15a3 Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 31 Jan 2024 12:27:42 +0200
Subject: [PATCH 056/234] add cosign
---
.github/workflows/release-log-server.yaml | 3 +++
.github/workflows/release-log-sidecar.yaml | 3 +++
2 files changed, 6 insertions(+)
diff --git a/.github/workflows/release-log-server.yaml b/.github/workflows/release-log-server.yaml
index fc0480b0424..7046560501a 100644
--- a/.github/workflows/release-log-server.yaml
+++ b/.github/workflows/release-log-server.yaml
@@ -29,6 +29,9 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v1
+ - uses: sigstore/cosign-installer@v3.0.5
+ - uses: anchore/sbom-action/download-syft@v0.14.2
+
- name: Set up Go
uses: actions/setup-go@v2
with:
diff --git a/.github/workflows/release-log-sidecar.yaml b/.github/workflows/release-log-sidecar.yaml
index d152e0f9bcc..c31fec31015 100644
--- a/.github/workflows/release-log-sidecar.yaml
+++ b/.github/workflows/release-log-sidecar.yaml
@@ -29,6 +29,9 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v1
+ - uses: sigstore/cosign-installer@v3.0.5
+ - uses: anchore/sbom-action/download-syft@v0.14.2
+
- name: Set up Go
uses: actions/setup-go@v2
with:
From 94d2ab0a9e553d44a4f3d9e3d60b2a864e8499cd Mon Sep 17 00:00:00 2001
From: ypoplavs
Date: Wed, 31 Jan 2024 12:32:05 +0200
Subject: [PATCH 057/234] remove testing
---
.github/workflows/release-dev-log-server.yaml | 2 --
.github/workflows/release-dev-log-sidecar.yaml | 2 --
.github/workflows/release-log-server.yaml | 15 +--------------
.github/workflows/release-log-sidecar.yaml | 15 +--------------
4 files changed, 2 insertions(+), 32 deletions(-)
diff --git a/.github/workflows/release-dev-log-server.yaml b/.github/workflows/release-dev-log-server.yaml
index 024a3dfb077..8abeab84322 100644
--- a/.github/workflows/release-dev-log-server.yaml
+++ b/.github/workflows/release-dev-log-server.yaml
@@ -4,7 +4,6 @@ on:
push:
branches:
- develop
- - ci/logs-service-fix
permissions:
id-token: write
@@ -63,7 +62,6 @@ jobs:
args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-server.yml --snapshot
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
- # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
DOCKER_BUILDX_BUILDER: "${{ steps.buildx.outputs.name }}"
DOCKER_BUILDX_CACHE_FROM: "type=gha"
diff --git a/.github/workflows/release-dev-log-sidecar.yaml b/.github/workflows/release-dev-log-sidecar.yaml
index 2c0c0feea89..489dfc8c24e 100644
--- a/.github/workflows/release-dev-log-sidecar.yaml
+++ b/.github/workflows/release-dev-log-sidecar.yaml
@@ -4,7 +4,6 @@ on:
push:
branches:
- develop
- - ci/logs-service-fix
permissions:
id-token: write
@@ -63,7 +62,6 @@ jobs:
args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml --snapshot
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
- # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
DOCKER_BUILDX_BUILDER: "${{ steps.buildx.outputs.name }}"
DOCKER_BUILDX_CACHE_FROM: "type=gha"
diff --git a/.github/workflows/release-log-server.yaml b/.github/workflows/release-log-server.yaml
index 7046560501a..53ad4ff6801 100644
--- a/.github/workflows/release-log-server.yaml
+++ b/.github/workflows/release-log-server.yaml
@@ -3,7 +3,7 @@ name: Release logs server
on:
push:
tags:
- - "v[0-9]+.[0-9]+.[0-9]+-*"
+ - "v[0-9]+.[0-9]+.[0-9]+"
permissions:
id-token: write
@@ -65,25 +65,12 @@ jobs:
args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-server.yml
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
- # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
DOCKER_BUILDX_BUILDER: "${{ steps.buildx.outputs.name }}"
DOCKER_BUILDX_CACHE_FROM: "type=gha"
DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max"
ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }}
-# - name: Push Docker images
-# run: |
-# docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
-# docker push kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64
-# # adding the docker manifest for the latest image tag
-# docker manifest create kubeshop/testkube-logs-server:latest \
-# kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64 \
-# kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8
-# docker manifest annotate kubeshop/testkube-logs-server:latest kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-amd64 --arch amd64
-# docker manifest annotate kubeshop/testkube-logs-server:latest kubeshop/testkube-logs-server:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --arch arm64 --variant v8
-# docker manifest push kubeshop/testkube-logs-server:latest
-
- name: Push README to Dockerhub
uses: christian-korneck/update-container-description-action@v1
env:
diff --git a/.github/workflows/release-log-sidecar.yaml b/.github/workflows/release-log-sidecar.yaml
index c31fec31015..bc35a3a288c 100644
--- a/.github/workflows/release-log-sidecar.yaml
+++ b/.github/workflows/release-log-sidecar.yaml
@@ -3,7 +3,7 @@ name: Release logs sidecar
on:
push:
tags:
- - "v[0-9]+.[0-9]+.[0-9]+-*"
+ - "v[0-9]+.[0-9]+.[0-9]+"
permissions:
id-token: write
@@ -65,25 +65,12 @@ jobs:
args: release -f ./goreleaser_files/.goreleaser-docker-build-logs-sidecar.yml
env:
GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
- # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
DOCKER_BUILDX_BUILDER: "${{ steps.buildx.outputs.name }}"
DOCKER_BUILDX_CACHE_FROM: "type=gha"
DOCKER_BUILDX_CACHE_TO: "type=gha,mode=max"
ALPINE_IMAGE: ${{ env.ALPINE_IMAGE }}
-# - name: Push Docker images
-# run: |
-# docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
-# docker push kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64
-# # adding the docker manifest for the latest image tag
-# docker manifest create kubeshop/testkube-logs-sidecar:latest \
-# kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64 \
-# kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8
-# docker manifest annotate kubeshop/testkube-logs-sidecar:latest kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-amd64 --arch amd64
-# docker manifest annotate kubeshop/testkube-logs-sidecar:latest kubeshop/testkube-logs-sidecar:${{ steps.github_sha.outputs.sha_short }}-arm64v8 --arch arm64 --variant v8
-# docker manifest push kubeshop/testkube-logs-sidecar:latest
-
- name: Push README to Dockerhub
uses: christian-korneck/update-container-description-action@v1
env:
From b9fa14cc0e73cb0cb2936c7810384a495267ebdf Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Wed, 31 Jan 2024 15:33:09 +0300
Subject: [PATCH 058/234] fix: duplicate merged args
---
contrib/executor/jmeterd/pkg/runner/runner.go | 34 ++++-
.../jmeterd/pkg/runner/runner_test.go | 139 ++++++++++++------
2 files changed, 123 insertions(+), 50 deletions(-)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner.go b/contrib/executor/jmeterd/pkg/runner/runner.go
index a871d59c90b..ccd924b9810 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner.go
@@ -138,6 +138,8 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
reportPath := filepath.Join(outputDir, "report")
jmeterLogPath := filepath.Join(outputDir, "jmeter.log")
args := execution.Args
+ args = removeDuplicatedArgs(args)
+ args, params := mergeDuplicatedArgs(args)
hasJunit, hasReport, args := prepareArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
if mode == jmeterModeDistributed {
@@ -154,7 +156,7 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
args = append(args, fmt.Sprintf("-R %v", slaveMeta.ToIPString()))
}
- args = injectAndExpandEnvVars(args, nil)
+ args = injectAndExpandEnvVars(args, params["-e"])
output.PrintLogf("%s Using arguments: %v", ui.IconWorld, envManager.ObfuscateStringSlice(args))
// TODO: this is a workaround, the check should be ideally performed in the getTestPathAndWorkingDir function
@@ -257,7 +259,7 @@ func checkIfTestFileExists(fs filesystem.FileSystem, args []string) error {
return nil
}
-func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool, result []string) {
+func removeDuplicatedArgs(args []string) []string {
counters := make(map[string]int)
duplicates := make(map[string]string)
for _, arg := range args {
@@ -288,6 +290,34 @@ func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string)
}
}
+ return args
+}
+
+func mergeDuplicatedArgs(args []string) ([]string, map[string][]string) {
+ allowed := map[string]string{
+ "-e": "",
+ }
+
+ duplicates := make(map[string][]string)
+ for i := len(args) - 1; i >= 0; i-- {
+ if arg, ok := allowed[args[i]]; ok {
+ if i+1 >= len(args) {
+ continue
+ }
+
+ if args[i+1] == arg {
+ continue
+ }
+
+ duplicates[args[i]] = append(duplicates[args[i]], args[i+1])
+ args = append(args[:i], args[i+2:]...)
+ }
+ }
+
+ return args, duplicates
+}
+
+func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool, result []string) {
for i, arg := range args {
switch arg {
case "":
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index 190b4eb9a35..08699aafae9 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -81,7 +81,7 @@ func TestCheckIfTestFileExists(t *testing.T) {
}
}
-func TestPrepareArgsReplacements(t *testing.T) {
+func TestPrepareArgs(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -135,64 +135,48 @@ func TestPrepareArgsReplacements(t *testing.T) {
}
}
-func TestPrepareArgsDuplication(t *testing.T) {
+func TestRemoveDuplicatedArgs(t *testing.T) {
t.Parallel()
tests := []struct {
- name string
- args []string
- expectedArgs []string
- expectedJunit bool
- expectedReport bool
+ name string
+ args []string
+ expectedArgs []string
}{
{
- name: "Duplicated args",
- args: []string{"-t", "", "-t", "path", "-l"},
- expectedArgs: []string{"-t", "path", "-l"},
- expectedJunit: true,
- expectedReport: false,
+ name: "Duplicated args",
+ args: []string{"-t", "", "-t", "path", "-l"},
+ expectedArgs: []string{"-t", "path", "-l"},
},
{
- name: "Multiple duplicated args",
- args: []string{"-t", "", "-o", "", "-t", "path", "-o", "output", "-l"},
- expectedArgs: []string{"-t", "path", "-o", "output", "-l"},
- expectedJunit: true,
- expectedReport: false,
+ name: "Multiple duplicated args",
+ args: []string{"-t", "", "-o", "", "-t", "path", "-o", "output", "-l"},
+ expectedArgs: []string{"-t", "path", "-o", "output", "-l"},
},
{
- name: "Non duplicated args",
- args: []string{"-t", "path", "-l"},
- expectedArgs: []string{"-t", "path", "-l"},
- expectedJunit: true,
- expectedReport: false,
+ name: "Non duplicated args",
+ args: []string{"-t", "path", "-l"},
+ expectedArgs: []string{"-t", "path", "-l"},
},
{
- name: "Wrong arg order",
- args: []string{"", "-t", "-t", "path", "-l"},
- expectedArgs: []string{"-t", "-t", "path", "-l"},
- expectedJunit: true,
- expectedReport: false,
+ name: "Wrong arg order",
+ args: []string{"", "-t", "-t", "path", "-l"},
+ expectedArgs: []string{"-t", "-t", "path", "-l"},
},
{
- name: "Missed template arg",
- args: []string{"-t", "-t", "path", "-l"},
- expectedArgs: []string{"-t", "-t", "path", "-l"},
- expectedJunit: true,
- expectedReport: false,
+ name: "Missed template arg",
+ args: []string{"-t", "-t", "path", "-l"},
+ expectedArgs: []string{"-t", "-t", "path", "-l"},
},
{
- name: "Wrong arg before template",
- args: []string{"-d", "-o", "", "-t", "-t", "path", "-l"},
- expectedArgs: []string{"-d", "-o", "-t", "-t", "path", "-l"},
- expectedJunit: true,
- expectedReport: false,
+ name: "Wrong arg before template",
+ args: []string{"-d", "-o", "", "-t", "-t", "path", "-l"},
+ expectedArgs: []string{"-d", "-o", "-t", "-t", "path", "-l"},
},
{
- name: "Duplicated not template args",
- args: []string{"-t", "first", "-t", "second", "-l"},
- expectedArgs: []string{"-t", "first", "-t", "second", "-l"},
- expectedJunit: true,
- expectedReport: false,
+ name: "Duplicated not template args",
+ args: []string{"-t", "first", "-t", "second", "-l"},
+ expectedArgs: []string{"-t", "first", "-t", "second", "-l"},
},
}
@@ -201,13 +185,72 @@ func TestPrepareArgsDuplication(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- hasJunit, hasReport, args := prepareArgs(tt.args, "", "", "", "")
+ args := removeDuplicatedArgs(tt.args)
- for i, arg := range args {
- assert.Equal(t, tt.expectedArgs[i], arg)
+ assert.Equal(t, len(args), len(tt.expectedArgs))
+ for j, arg := range args {
+ assert.Equal(t, tt.expectedArgs[j], arg)
+ }
+ })
+ }
+}
+
+func TestMergeDuplicatedArgs(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ args []string
+ expectedArgs []string
+ arg string
+ params []string
+ }{
+ {
+ name: "Duplicated args",
+ args: []string{"-e", "", "-e", "var", "-l"},
+ expectedArgs: []string{"-e", "", "-l"},
+ arg: "-e",
+ params: []string{"var"},
+ },
+ {
+ name: "Multiple duplicated args",
+ args: []string{"-e", "", "-e", "var 1", "-e", "var 2", "-l"},
+ expectedArgs: []string{"-e", "", "-l"},
+ arg: "-e",
+ params: []string{"var 2", "var 1"},
+ },
+ {
+ name: "Non duplicated args",
+ args: []string{"-e", "", "-l"},
+ expectedArgs: []string{"-e", "", "-l"},
+ arg: "-e",
+ params: []string{},
+ },
+ {
+ name: "Wrong arg order",
+ args: []string{"-e", "", "var", "-e"},
+ expectedArgs: []string{"-e", "", "var", "-e"},
+ arg: "-e",
+ params: []string{},
+ },
+ }
+
+ for i := range tests {
+ tt := tests[i]
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ args, params := mergeDuplicatedArgs(tt.args)
+
+ assert.Equal(t, len(args), len(tt.expectedArgs))
+ for j, arg := range args {
+ assert.Equal(t, tt.expectedArgs[j], arg)
+ }
+
+ assert.Equal(t, len(params[tt.arg]), len(tt.params))
+ for j, arg := range params[tt.arg] {
+ assert.Equal(t, tt.params[j], arg)
}
- assert.Equal(t, tt.expectedJunit, hasJunit)
- assert.Equal(t, tt.expectedReport, hasReport)
})
}
}
@@ -272,7 +315,7 @@ func TestJMeterDRunner_Local_Integration(t *testing.T) {
TestNamespace: "testkube",
Name: "test1",
Command: []string{"jmeter"},
- Args: []string{"-n", "-j", "", "-t", "", "-l", "", "-e", "-o", "", ""},
+ Args: []string{"-n", "-j", "", "-t", "", "-l", "", "-o", "", "-e", ""},
Content: &testkube.TestContent{
Type_: string(testkube.TestContentTypeString),
Data: tt.jmxContent,
From 9b5b4a9dfc9427185af84e03f25c9ae725800ca0 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Wed, 31 Jan 2024 15:39:41 +0300
Subject: [PATCH 059/234] fix: remove unused parameter
---
contrib/executor/jmeterd/pkg/runner/runner.go | 6 +++---
contrib/executor/jmeterd/pkg/runner/runner_test.go | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner.go b/contrib/executor/jmeterd/pkg/runner/runner.go
index ccd924b9810..b0276946204 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner.go
@@ -140,7 +140,7 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
args := execution.Args
args = removeDuplicatedArgs(args)
args, params := mergeDuplicatedArgs(args)
- hasJunit, hasReport, args := prepareArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
+ hasJunit, hasReport := prepareArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
if mode == jmeterModeDistributed {
clientSet, err := k8sclient.ConnectToK8s()
@@ -317,7 +317,7 @@ func mergeDuplicatedArgs(args []string) ([]string, map[string][]string) {
return args, duplicates
}
-func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool, result []string) {
+func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool) {
for i, arg := range args {
switch arg {
case "":
@@ -333,7 +333,7 @@ func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string)
hasJunit = true
}
}
- return hasJunit, hasReport, args
+ return hasJunit, hasReport
}
func getEntryPoint() (entrypoint string) {
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index 08699aafae9..c33b3e98790 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -124,9 +124,9 @@ func TestPrepareArgs(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- hasJunit, hasReport, args := prepareArgs(tt.args, tt.path, tt.jtlPath, tt.reportPath, tt.jmeterLogPath)
+ hasJunit, hasReport := prepareArgs(tt.args, tt.path, tt.jtlPath, tt.reportPath, tt.jmeterLogPath)
- for i, arg := range args {
+ for i, arg := range tt.args {
assert.Equal(t, tt.expectedArgs[i], arg)
}
assert.Equal(t, tt.expectedJunit, hasJunit)
From f13c9471504b75038841d69ed38907709c063e80 Mon Sep 17 00:00:00 2001
From: Julianne Fermi
Date: Wed, 31 Jan 2024 06:52:20 -0800
Subject: [PATCH 060/234] Add selected webhooks blog content to webhooks doc.
(#4958)
---
docs/docs/articles/webhooks.mdx | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/docs/docs/articles/webhooks.mdx b/docs/docs/articles/webhooks.mdx
index 0c8be2837a1..bde0198b352 100644
--- a/docs/docs/articles/webhooks.mdx
+++ b/docs/docs/articles/webhooks.mdx
@@ -5,6 +5,25 @@ import TabItem from "@theme/TabItem";
Webhooks allow you to integrate Testkube with external systems by sending HTTP POST payloads containing information about Testkube executions and their current state when specific events occur. To set up webhooks in Testkube, you'll need to have an HTTPS endpoint to receive the events and a payload template to be sent along with the data.
+:::note
+Please visit our Blog, [Empowering Kubernetes Tests with Webhooks](https://testkube.io/blog/empowering-kubernetes-tests-with-webhooks) for a tutorial on setting up webhooks for Slack and Grafana Dashboard.
+:::
+
+## Benefits of using Webhooks in Testkube
+
+Testkube uses webhooks to integrate with external systems, allowing you to effortlessly synchronize your testing workflows with other tools and platforms. These webhooks are designed to carry critical information regarding your tests as HTTP POST payloads. The information can include the execution and real-time status depending on how you configure it.
+
+To leverage webhooks, you need to ensure that the platform that you want to send information to has an HTTPS endpoint to receive the events. Testkube also allows you to customize the payloads.
+
+You can create a webhook from the dashboard, use the CLI, or create it as a custom resource. Before we show how it’s done, let’s understand a few scenarios where Webhooks in Testkube shine:
+
+- Incident Management & Response: Webhooks can be used to create incidents and alert on-call teams when a critical test fails. This ensures a timely response and avoids any potential disruption due to failures and bugs. With Testkube, you can configure incident management tools like PagerDuty and OpsGenie to receive alerts based on critical events for your tests.
+
+- Communication and Collaboration: You can configure Webhooks in Testkube to send alerts to your teams in your communication tool. This will notify your team of any critical event that needs attention and attend to it before the issue escalates. Some of the popular communications tools like Slack and MS Teams can be configured to receive alerts from Testkube.
+
+- Monitoring and Observability: Webhooks can also be used to send alerts and notifications to your monitoring and observability tools like Prometheus and Grafana. This provides visibility into your tests, alerts you, and ensures that timely corrective actions can be taken.
+
+
## Creating a Webhook
The webhook can be created using the Dashboard, CLI, or a Custom Resource.
From 2aa20020088af4a7518a16d0f2aa15e376ac9760 Mon Sep 17 00:00:00 2001
From: Vladislav Sukhin
Date: Thu, 1 Feb 2024 14:18:33 +0300
Subject: [PATCH 061/234] fix: refactor merge args
---
contrib/executor/jmeterd/pkg/runner/runner.go | 27 +++++++----------
.../jmeterd/pkg/runner/runner_test.go | 30 ++++---------------
2 files changed, 15 insertions(+), 42 deletions(-)
diff --git a/contrib/executor/jmeterd/pkg/runner/runner.go b/contrib/executor/jmeterd/pkg/runner/runner.go
index b0276946204..c0a56b3bfdb 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner.go
@@ -137,9 +137,7 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
jtlPath := filepath.Join(outputDir, "report.jtl")
reportPath := filepath.Join(outputDir, "report")
jmeterLogPath := filepath.Join(outputDir, "jmeter.log")
- args := execution.Args
- args = removeDuplicatedArgs(args)
- args, params := mergeDuplicatedArgs(args)
+ args := mergeDuplicatedArgs(removeDuplicatedArgs(execution.Args))
hasJunit, hasReport := prepareArgs(args, testPath, jtlPath, reportPath, jmeterLogPath)
if mode == jmeterModeDistributed {
@@ -156,7 +154,7 @@ func (r *JMeterDRunner) Run(ctx context.Context, execution testkube.Execution) (
args = append(args, fmt.Sprintf("-R %v", slaveMeta.ToIPString()))
}
- args = injectAndExpandEnvVars(args, params["-e"])
+ args = injectAndExpandEnvVars(args, nil)
output.PrintLogf("%s Using arguments: %v", ui.IconWorld, envManager.ObfuscateStringSlice(args))
// TODO: this is a workaround, the check should be ideally performed in the getTestPathAndWorkingDir function
@@ -293,28 +291,23 @@ func removeDuplicatedArgs(args []string) []string {
return args
}
-func mergeDuplicatedArgs(args []string) ([]string, map[string][]string) {
- allowed := map[string]string{
- "-e": "",
+func mergeDuplicatedArgs(args []string) []string {
+ allowed := map[string]int{
+ "-e": 0,
}
- duplicates := make(map[string][]string)
for i := len(args) - 1; i >= 0; i-- {
- if arg, ok := allowed[args[i]]; ok {
- if i+1 >= len(args) {
+ if counter, ok := allowed[args[i]]; ok {
+ allowed[args[i]]++
+ if counter == 0 {
continue
}
- if args[i+1] == arg {
- continue
- }
-
- duplicates[args[i]] = append(duplicates[args[i]], args[i+1])
- args = append(args[:i], args[i+2:]...)
+ args = append(args[:i], args[i+1:]...)
}
}
- return args, duplicates
+ return args
}
func prepareArgs(args []string, path, jtlPath, reportPath, jmeterLogPath string) (hasJunit, hasReport bool) {
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index c33b3e98790..d0f57159454 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -202,36 +202,21 @@ func TestMergeDuplicatedArgs(t *testing.T) {
name string
args []string
expectedArgs []string
- arg string
- params []string
}{
{
name: "Duplicated args",
- args: []string{"-e", "", "-e", "var", "-l"},
- expectedArgs: []string{"-e", "", "-l"},
- arg: "-e",
- params: []string{"var"},
+ args: []string{"-e", "", "-e"},
+ expectedArgs: []string{"", "-e"},
},
{
name: "Multiple duplicated args",
- args: []string{"-e", "", "-e", "var 1", "-e", "var 2", "-l"},
- expectedArgs: []string{"-e", "", "-l"},
- arg: "-e",
- params: []string{"var 2", "var 1"},
+ args: []string{"", "-e", "-e", "-l"},
+ expectedArgs: []string{"", "-e", "-l"},
},
{
name: "Non duplicated args",
args: []string{"-e", "", "-l"},
expectedArgs: []string{"-e", "", "-l"},
- arg: "-e",
- params: []string{},
- },
- {
- name: "Wrong arg order",
- args: []string{"-e", "", "var", "-e"},
- expectedArgs: []string{"-e", "", "var", "-e"},
- arg: "-e",
- params: []string{},
},
}
@@ -240,17 +225,12 @@ func TestMergeDuplicatedArgs(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- args, params := mergeDuplicatedArgs(tt.args)
+ args := mergeDuplicatedArgs(tt.args)
assert.Equal(t, len(args), len(tt.expectedArgs))
for j, arg := range args {
assert.Equal(t, tt.expectedArgs[j], arg)
}
-
- assert.Equal(t, len(params[tt.arg]), len(tt.params))
- for j, arg := range params[tt.arg] {
- assert.Equal(t, tt.params[j], arg)
- }
})
}
}
From b80ce7858412d35a9cce104e8589d0a6164c8c1e Mon Sep 17 00:00:00 2001
From: Dejan Zele Pejchev
Date: Thu, 1 Feb 2024 16:54:46 +0100
Subject: [PATCH 062/234] fix: remove custom assertion logic in jmeterd
executor (#4966)
* fix: remove custom assertion logic in jmeterd executor
* fix: fix failign jmeterd integration test
---
contrib/executor/jmeterd/pkg/parser/mapper.go | 22 ++--
.../jmeterd/pkg/runner/runner_test.go | 106 +++++++++++-------
2 files changed, 79 insertions(+), 49 deletions(-)
diff --git a/contrib/executor/jmeterd/pkg/parser/mapper.go b/contrib/executor/jmeterd/pkg/parser/mapper.go
index bc077431bc0..aaaf4a47cd7 100644
--- a/contrib/executor/jmeterd/pkg/parser/mapper.go
+++ b/contrib/executor/jmeterd/pkg/parser/mapper.go
@@ -8,10 +8,11 @@ import (
func mapCSVResultsToExecutionResults(out []byte, results CSVResults) (result testkube.ExecutionResult) {
result = MakeSuccessExecution(out)
- if results.HasError {
- result.Status = testkube.ExecutionStatusFailed
- result.ErrorMessage = results.LastErrorMessage
- }
+ // TODO: Is it enough to just disable it here?
+ //if results.HasError {
+ // result.Status = testkube.ExecutionStatusFailed
+ // result.ErrorMessage = results.LastErrorMessage
+ //}
for _, r := range results.Results {
result.Steps = append(
@@ -43,12 +44,13 @@ func mapXMLResultsToExecutionResults(out []byte, results XMLResults) (result tes
samples := append(results.HTTPSamples, results.Samples...)
for _, r := range samples {
- if !r.Success {
- result.Status = testkube.ExecutionStatusFailed
- if r.AssertionResult != nil {
- result.ErrorMessage = r.AssertionResult.FailureMessage
- }
- }
+ // TODO: Is it enough to disable it here?
+ //if !r.Success {
+ // result.Status = testkube.ExecutionStatusFailed
+ // if r.AssertionResult != nil {
+ // result.ErrorMessage = r.AssertionResult.FailureMessage
+ // }
+ //}
result.Steps = append(
result.Steps,
diff --git a/contrib/executor/jmeterd/pkg/runner/runner_test.go b/contrib/executor/jmeterd/pkg/runner/runner_test.go
index d0f57159454..d165cc1cc18 100644
--- a/contrib/executor/jmeterd/pkg/runner/runner_test.go
+++ b/contrib/executor/jmeterd/pkg/runner/runner_test.go
@@ -6,13 +6,13 @@ import (
"path/filepath"
"testing"
+ "github.com/kubeshop/testkube/pkg/utils/test"
+
"github.com/golang/mock/gomock"
"github.com/pkg/errors"
"github.com/kubeshop/testkube/pkg/filesystem"
- "github.com/kubeshop/testkube/pkg/utils/test"
-
"github.com/stretchr/testify/assert"
"github.com/kubeshop/testkube/pkg/api/v1/testkube"
@@ -535,54 +535,41 @@ log.info("=================================");
const failureJMX = `
-
- Kubeshop site simple perf test
+
+
false
- true
false
-
-
- PATH
- /pricing
- =
-
-
+
- continue
+ stopthread
false
1
1
1
+ 1668426657000
+ 1668426657000
false
true
-
+
-
-
- false
- $PATH
- =
- true
- PATH
-
-
+
- testkube.io
- 80
- https
+ testkube.kubeshop.io
+
+
- https://testkube.io
+
GET
true
false
@@ -592,18 +579,59 @@ const failureJMX = `
-
-
-
- SOME_NONExisting_String
-
-
- Assertion.response_data
- false
- 16
-
-
-
+
+
+
+ 418
+
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+ true
+
+
+
+
+
+
+ groovy
+
+
+ true
+ println "\nJMeter negative test - failing Thread Group in purpose with System.exit(1)\n";
+System.exit(1);
+
+
From 70ed08cd48ce76f6c439c1cdd56bfaf411fc042b Mon Sep 17 00:00:00 2001
From: Catalin <20538711+devcatalin@users.noreply.github.com>
Date: Mon, 5 Feb 2024 16:08:42 +0200
Subject: [PATCH 063/234] docs: instructions for using Jenkins Plugin via the
UI (#4956)
* docs: update Jenkins article
* docs: update sidebar
* chore: add jenkins plugin url
* chore: update jenkins docs env vars
* docs: Jenkins via UI articles
* Update docs/docs/articles/jenkins-ui.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/jenkins-ui.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/jenkins-ui.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/jenkins-ui.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/jenkins-ui.md
Co-authored-by: Julianne Fermi
* Update docs/docs/articles/jenkins-ui.md
Co-authored-by: Julianne Fermi
---------
Co-authored-by: Julianne Fermi
---
docs/docs/articles/cicd-overview.md | 3 ++-
docs/docs/articles/jenkins-ui.md | 33 ++++++++++++++++++++++++
docs/docs/articles/jenkins.md | 2 +-
docs/docs/img/jenkins-build-step.png | Bin 0 -> 27077 bytes
docs/docs/img/jenkins-environment.png | Bin 0 -> 108955 bytes
docs/docs/img/jenkins-execute-shell.png | Bin 0 -> 27205 bytes
docs/sidebars.js | 1 +
7 files changed, 37 insertions(+), 2 deletions(-)
create mode 100644 docs/docs/articles/jenkins-ui.md
create mode 100644 docs/docs/img/jenkins-build-step.png
create mode 100644 docs/docs/img/jenkins-environment.png
create mode 100644 docs/docs/img/jenkins-execute-shell.png
diff --git a/docs/docs/articles/cicd-overview.md b/docs/docs/articles/cicd-overview.md
index 4a4d6c7213f..9d267067bf5 100644
--- a/docs/docs/articles/cicd-overview.md
+++ b/docs/docs/articles/cicd-overview.md
@@ -8,7 +8,8 @@ We have different tutorials for the options of being CI driven or using GitOps a
- [Github Actions - running Testkube CLI commands with setup-testkube-action](./github-actions.md)
- [Testkube Docker CLI](./testkube-cli-docker.md)
- [Gitlab CI](./gitlab.md)
-- [Jenkins](./jenkins.md)
+- [Jenkins Pipelines](./jenkins.md)
+- [Jenkins UI](./jenkins-ui.md)
- [CircleCI](./circleci.md)
- [GitOps Testing](./gitops-overview.md)
- [Flux](./flux-integration.md)
diff --git a/docs/docs/articles/jenkins-ui.md b/docs/docs/articles/jenkins-ui.md
new file mode 100644
index 00000000000..8dde9179bca
--- /dev/null
+++ b/docs/docs/articles/jenkins-ui.md
@@ -0,0 +1,33 @@
+# Testkube Jenkins UI
+
+The Testkube Jenkins integration streamlines the installation of Testkube, enabling the execution of any [Testkube CLI](https://docs.testkube.io/cli/testkube) command within Jenkins Pipelines or Freestyle Projects.
+
+If you are using Pipelines and Groovy scripts, then look at examples from [Testkube Jenkins Pipelines](./jenkins.md).
+
+### Testkube CLI Jenkins Plugin
+
+Install the Testkube CLI plugin by searching for it in the "Available Plugins" section on Jenkins Plugins, or using the following url:
+[https://plugins.jenkins.io/testkube-cli](https://plugins.jenkins.io/testkube-cli)
+
+## Testkube Pro
+
+To use Jenkins CI/CD for [Testkube Pro](https://app.testkube.io/), you need to create an [API token](https://docs.testkube.io/testkube-pro/articles/organization-management/#api-tokens).
+
+
+### How to set up a Freestyle Project to run tests on Testkube Pro
+
+1. Create a new Freestyle Project.
+2. In General settings, configure the environment variables:
+ - TK_ORG
+ - TK_ENV
+ - TK_API_TOKEN
+
+![jenkins environment variables configuration](../img/jenkins-environment.png)
+
+3. Click on "Add Build Step" and select "Testkube Setup".
+![jenkins testkube setup build step](../img/jenkins-build-step.png)
+
+4. Specify a Testkube CLI version or leave it empty to use the latest version.
+
+5. Add a new "Execute Shell" Build Step and run one or multiple Testkube CLI commands.
+![jenkins execute shell](../img/jenkins-execute-shell.png)
diff --git a/docs/docs/articles/jenkins.md b/docs/docs/articles/jenkins.md
index 30fe6343753..3b6861e1e96 100644
--- a/docs/docs/articles/jenkins.md
+++ b/docs/docs/articles/jenkins.md
@@ -1,4 +1,4 @@
-# Testkube Jenkins
+# Testkube Jenkins Pipelines
The Testkube Jenkins integration streamlines the installation of Testkube, enabling the execution of any [Testkube CLI](https://docs.testkube.io/cli/testkube) command within Jenkins pipelines. This integration can be effortlessly integrated into your Jenkins setup, enhancing your continuous integration and delivery processes.
This Jenkins integration offers a versatile solution for managing your pipeline workflows and is compatible with Testkube Pro, Testkube Enterprise, and the open-source Testkube platform. It allows Jenkins users to effectively utilize Testkube's capabilities within their CI/CD pipelines, providing a robust and flexible framework for test execution and automation.
diff --git a/docs/docs/img/jenkins-build-step.png b/docs/docs/img/jenkins-build-step.png
new file mode 100644
index 0000000000000000000000000000000000000000..ed1820af2150d1ca0e12a48488a022616aa1ccce
GIT binary patch
literal 27077
zcmdSBg;yL=w>{WRBf&`su7Lo--5r8kaF^ij?h-<nV%mi!%uMmMNw8DcLzz*!9
zA|(c@7$x2ZPK?d8WXu&5Ky<+ID-bx$3IzX>1lS0H4FrP!^%aB!?B4*}rz|iC0_?#r
z_p{*s@04JsEXe;H69D%?qN?IDGQeKd#M#Wu-o?_v6;80s4ahiarKaVor6A8|;$X-0
z+0?<^O+v@>)4OzvrCYwyD6DM<036nwz(%VlN?^8X}pwGpJyQustJ
z?%-@j&c(#S#6ls2LQYOD;B0Enr}9zqzq12pf)tjnu8w@n%pM*dOdjk^4$c+|5eEUUCu`{7ZYbIM^`Hcd-9iZKN~x^xe8KHyj1i*
zfB)4^Gf%7kQI
z_)q%(*PZ`U>Gyk3Wf8YG?i~`Ir
zPyWAp;=j82KUaZ%7D5qV{-1Xygd&LPBMbtGfMh<3s(FHs^^x1uRq+f*ilt3UIL3-O
z3iF!ZN>B~O^+@$caL7a;#!)DLqlo_cUiozn76sMo6zq5CDTn@toi|GsO}ESY?LKSm
zx9#l#otHOtH;XovwYSe@?W0~EyJt@1*inctKNV>Rud1{LWywU~%g?e4l$iWV5+Vtq
zEd)dKXg!9bx?au@oBk5B5V8OPC22iZQI95MF`A~<;!0bhQI3K`>+C&^!s`oVQLoSogFT!Idmq;q
zPZuc2r3gN_wLF|W?DwGBj8EpHid_(MSmL)s|G6GaZ0Zp5yQ@hr9xr)JN|E`A+02&Z
zQ%ZeTxjs-YmWl0G%a_gnaV($z(g8a*9@RSi?e5P9GK_HuqcAu*dw(Jg;xSG*i8-KdREcl9k
zsGKj0la}{+%4N4hcAj)+@FyjMo%bu)-7Z-R>|WRO(Cd08z1U_>F^=28pkXIr!ey*o
zIs^$5@36w4ov+7#dx#l%*6eV0WNIqi$^>{hSP=Bz#7%@8MN*muUd>dC(cJEZDV)qX
z0naOPnSM9Tpt(1{v{jMY*B*CYs`kL`k;9dQ$@;<1-6dsSvZ{rCN)a@iLw3=t1NbHZxf{0+9Pk%XOXa9ZGR4^k1ZD>Jj|PLhaf*?EA1gq3&cT
z>e}WxNg*y{%0)v{&Y2VJ4))-N_E@@qyB1J*u|16KneajRuZ;1eBxFGm!ivNcfW9-D
z9tPZ@>GSR`H+-hN-e(US74B?8ZECPzVcO1`>*Dl0>#g%R74p7anWM#be7f7Ux1K41
zFrVma++H8fMGv;{d!A+0Ep!HSUa2;nS>eVg=YD<7^hHTnpSEUyh}UJ6TyIQ(T`}-*
zZz9)qgi{bcjT8N!MtRCqX=MlblH%6GEt%Na3JIUO1Suv?7c)Swgjn+K-I7pA+@G3s0)+Raz#h`e=1$3Oeg#^`pzZj~Ek
zn-6YNkCO1b_!HidBO&KyFlVqd+-NeOSei;DC-k;Or=C#TOxpM1n2?s)pe;w;phC}$
z_TgfM+A5k|Hx>e5djF@I;c=Dqip(VLpn@{^RW
zxyI)oA0KY)=EWW(8gnD9&i`Rr&6HFkGvlrY82`xMpDG+oV3v02X*7pwpy4raS*#;V
zAYnX82|qtZ4u{Qj`03IRh>HOsBdRLohHkTDm{1OoeWXP+Bk45OjVn?{&39Hev6!
zB9hrume*d+p{6RQhu4?cv1yPGBXmpb~npy^Ta
zs=kEwF7aM%#g4Hg`IEyqpwpzDMjssHd@h$pAM_vP`+=4KqqS>0zYSO(WY2d-bbsG|
z!Bl-+C4G({uu4I})0z7#_!Vq#jGoJGkpYzyMn___{RNfGAIkdB2rJO3x7Hf<9h_^s
zqk6Q{R3c9RSUpA`=wD}www?71tLWv+n(=MNsTSETO#T|c
zMag6#H*MtNwrp_U2ir<#rYJyMr$BXuf*$
zD9@1A%l9G+!odDz&3}o36$TPOefvS=C?Lmd*f#RbCgcnqpM?Vym+9|aqyClgdP+%{
z=8>r>tK@g&!jSjY&w<~QL#f=DGv&H+27G(ZB3-E(z*oAa6w&tby(A$>%k`wJAwo;<
z%<1zXLg~RMzeBvinS)4QWTq4}BQkvg-UzyzDaf?`O^w)BU)XZouVxUece&fy>+nwz
z8x;YBOr~7qcwDc)P?D^;=6gw^E27ZX2=Ca}s9J^fv*VYLWfU8f
zS@Uc$1TX#-sloL%xTFmt7GFHXKwze-8|U}b*o=2I%%=G&ECQp!@?~7+Z=}+YoR#C2
zeM@yX>CPn5W9ZY0sx@!_8`;LK%apps4hQZ3ia{c>(t10sK>2;+5?Eq|M^bMSKr_XK
zxc>rJ2}d!`qdy0gsullw6po0*S9_=qz2W5FT5B}@5(+)4Rf>4jNvhDVj8h^8QB>h>
z{Fd5VZl=`rlUis=qgM0_?(FYPC4p4ki(%*!1MQ<
z2z(yB=j`7UAnElz#j&3LuxzM5
zis6A~xsGA9-ji1g&?EH2NJVl9Z6p5mDHmY6j;uN0
zb?in}Z!fp|H2St@W;pSBCD%=eXpD>W1kuxfU`=6#LNqz-(92&C)}+XC`laN=3o?_0
zRL=%hHyM{tusoiL!8v@Y=KLD63jYG4_Bf3K>AlmFabHp)&jv2gQ9}`vPF}!R&K!WY
z_T0>M@X_(n-)jLl?Ts%){`Mu|gbje@uK3s)U(nT?G5`f4cYq6g{=W(YSHQ1fFh{=
z|MXbRxejlNJoS
zdLsNr;JER}x*=@8^R;$!{?x_L&?bk;umJjR_$DJe0x92}l{&Q}B3i+L7TwPrjGt#G+hhgGEZ?WTfw(31$NKs>em-1BY$F#!mD#LYVAI
zH88=^ve*6aO>A;nQ!y_CdIH3OHFXM%VwKA;-|Ho@$HtbY{x&0NKN(iM!O%0Ouo(Tw
z)o#j+{zXSq8$8YCSNbZHk6AFi!C_5C`01*46fs&ULqJCSD_U;7&79Zdmy)&SOAo77
zpIiH}h7FW8FN@bJZkoc8w!gnAUu`EEYI|8w#1Sj<#c~l2+M@H!kxdrkv!OTD)wIRA
zLeD;1PwlW-uiQKHd7k~i3s?Vo8f4(-hHJOlHbUn@eRN+wYAo}7IYw&L>T%i;*dmSc
zotRfF41=VuL+XW2+~4g+XDEpO*;wdOhHk}T?_bZYImadCO^7^lU|HMsSI$K_eMfb)
zlHl3=WXz;}@$UJT2w(#-%BX
z%yLJKOX2gc6&|k(&HNvoAo@-cnkDL`?^c|o4DWB(+Er=;p8Z>({}QhH@Jh?ff#H<5
zWZ$ZLF;zI01pw655*O+?V)2ln{X7ZpzKF5?nqfZGnUeawNjd(>$^79jc{X#0mc3sy
zEn$xc6sV?RT2)5x^u4xcTsK0|0XlP4nAu^VtL|5tP>~cV
z0c9jK2re5%n=kb2HDV?|{UHg67yhz-3rN%XXdVM!_i$9rOI|OKs
z`tPGPz7yjb
zlHUJ~r{|72#cl2?cHWjn(mak}DTJt@NDLm)eh!v^7
z;qETSR@Ab^yed?&4f5mRwV>|pbS|@{ZyqZ9^CNKRwL$>^ir2MRXT=`Rpxr~~LG8+K
zk<5-RM@z`7$bZa{!m@$f!v&1uLS(J$12;;2ml5@z*-l|7h*cvnp51n_t`ClBH)$x2
zwl6R#(*MCu^>`)~#>kyog<=T}%Bam(#poT-cB3T6r3+%G~+f5gD-i43EQXzz`^;
z)eWGb9GVPAU7Zx%bp*fQ{x53Nfr5$T?K(tUNqumGR|0<-K&XeClW=BR?xM=~HF)_k
ztG+HmIE*@RjbiM@0nZ)IBk2!x=`N!S0E#8o2SE520R79BYWOL1!dk-pL~`@>E>SgN
z{5hh(U*oTD-UH0W3YY|KS~OMStu3~isJIN$yBR(h0TE}^b_+G?AT|DxZgC_U;q>p4
z7+M~Q`m-{`7xS~6R?gK`{kX3mK|E{zt&*l)7C6BC%t2|ZOV)ktK|
zI+afS@)1~qu%6cewgB)~(w|78(S&&Ldm_V!WZbs%pBjQ;+kBX3aM7cw^BGg&=UJU2&{$KvCd4Tao;
zh5K+K+~=#_)tQgzv~2yF5v7c*XgaI)!51q`ewYywB^4jc(kboc*cDp7DCLr!u$LX1QXas*C74T%Sb859w4pVx6bu*(+#0RVn
zcvmv(l!ISCKeD%GXzV-e?XIL|Y
z6M38_*kIWh9w8*iw&!JiOOmP<7(=f!4)=>6qR%O
zPwR1Y_k&RJ?0G`(Xgs&Ga<7?vgOI+7p`gaR?)z>qHq4Ds-mRHrQwTbb>1HjGYE(mA
zAW-uVi@)%zajwgYOnz2cq`w0x!l8*PVs#f)3hx8t_!%oEdq
z0J5#wQpCl3*SCvypYXl-ks@sq^%{<_0c&6c6fxHCtp8uc2}b_F5b}{I7-OjSG7>(`
z`D51rqeNNvFkFSD$S@)v0t2ioA3Va?!oNG#t51|vyYH3`<}43ai@4n7*6o{LVJ7;K
zCeRxaTY!P2k`1EM(_IN88Fh@oo$=g7Sj>`9L@
zP);U%;%6jTU`0t3^Efzkciw(u#bI`Dc;`$d=HzAlKcGouUr@3Z1POODC4wR756FFy?g1#4b
zdFhbpFEvO~8I)<{3)HfUtmuf-7?P4xUH+ct$3ho2T2XMki9k{9UBAT_uDKswIJ8hB
zv7UpTyesPvHiSCRJ&+d;Ic0Y>SxfAr9e{Ky;OWI{8$?^)~$B|!*%^{71Mn>_P
zyhG&QVD)fS6J}{OO(hK~Ht{l})Ei~#)%*He{sgJ3l|81hA0bJlxDij@g~xZ{M%YEOJ2?o8bS1+8QXlzryAXmk5W(P5x2k
zD?~VkRPg7Gn)W5`re3V_j-GW05*zkkyd)()z~
z7MS+b8+<*1Wk(uhr%uw#*={yfDj!aGaGH?h(`5Lq_Pw>zDO
zXr#Zz9nB)fOZRz%$Qk*lpE5NrWf7l{S~x^#G(wu@ud*)3T*6byqqWJ?Yw^-=>`QGR
zCU}mtU5+6k0m&YuM!7#wu0d5M1K%UZhF8NEeeJMR%_0hMM@!}Bcui6>Tiav%^RV;5
zP=Gc_@V~>Ra-h38>__JQuzxA;eM9_=8X)O2a;P5?sEC?nQu_mpaov(@yU7ulI%!CRy0~3D<
zz!f6vRiYBLZj+WQ%IkSS;^1G{gGXE-rgpo*KiIl=0y(eMhCds>FwrO*(BWHQYQRv<
z#-x~MhZEn2ATpI@0ZD=rjGwb&j1<aW8LodM4-cN&O**Z{o{PmvKSVbfu(d_6O>0S!C!^hoT
zAwObQ3)~9YmUwBk-~!g+_IQH+CVLdbpUQ$shpHLXSQkq<{Oj2bJK7Mdvfcm&9u*M?
zs0#2aumzGR)ixbDv@Sc~&ori5ODnQC7bp`kotDQruJ?7hiDfJ4JUAxc4sux1lDh=s
zHbhzve=`IRrgB#{sufJXQ0-#QKyg!mjZm-WGcY5F*dzIwuH(ECgBk6VM|8P!r5_>`
zAyTN7*4Zgr(aU$fp1z)^p)3c(yRcUk`Z!DC*
zBD>gAlje3RFBNNxLgv{@+%V6AL-R$^a;FkvzpE3#C_Fd9OMdE9G`dXWIr9;D=c^~L
z;?ix{cDupyynOJZX925<2#OU*(Yf9X?aIPu1M+nzlnS;@pbXa4QyfIK|J?jjU0aJQ
z@aF=B`4&@BgCsK>7Z~ROZPfQ|la=!Mc(xcMLU-M~_U1RzJVFwz%@eKQ
z-3Ip5?i$3}ThJJTi#cC>Xzu7ne!X*{*Y+WA1LbADYZWSIYFNh?yIb`sOB&dbjCyf%
zGk%G<7XC!<3Qwu$$sRHPd?d_G6Uz={9z-GR;`6BR35B3QrlCjNQ7GKYxeddQpx$f)1C%
zUqcNg*Msgq;AjP6%7ye9p`|H5Z|*6E?QuxsWL{wBQJ2rte&|9GP0V(Am7%H5gXpb6YXN$ua%%4qn@cfD;kv)&uY13^szX>w;_H-9cwq
zX;z_B-JL~JWQrbdb@l97CbRNPR)VhHr_<7-UJo4vlM_mCs=GRE_7CYC%pR8%bt7dp
zx*xxq6v#Q`{c+zFBwlARX2AFS=;B51`wOr?N&tpWlZM`)5;iKp6sNU&(=unXiuiV#
z_QSX71KCbEasPi6q3}MTC4^gw1%86`ocpJONxk3547i!x&8$Qw7rgvCRs6Y^@XP&3s>dtGjYPe<@`un-p$B;|
z)8p;qXnJe0TJa?t+AJ6@2@aS#;~*9hlBihr!iJ7G*4#janXnqOG!T-(J~GC-N;-Wk
zdKriGg4J=<9LavAm75Ty697T?_X{(#=qvUl9XbX189c#t6>T-GbymyJoDZ#|q5yFI
zU8?}Jn(bA_p?27F%JW8;VbMoqlOQj;!MEmK=v{%`Egl(*t`~W*1q(YXQFK1L0RwWg
zkfhq^M#A)^J{G`5WPmchjEgtq+U!BIPa7Ij;&WU2J7Tfb!|)SsQAj>uZ%!;~Ob|^j
zP(NC{<_73Y3#bP0G=yoCt88cb-=jn3D{)8qqjmRUl{yMygmfeh=bjVH)87CNtEAzM
z9Kbp|ezCpgc~PH~^aYqlmLQQ_rckh~DG_E|cWza80FPziz8hV8^)*SNc~KIQj&6~r
zQNEAV(OUzj!M(Mglwr#C*6Q;q2e@%FIGTEt99hwBJWl_3AfrD)4H6M)lIo=i@fRta
z^vcO>eN^pEn;MH9Pf8^!`3$<@IQx~IH{$@Sr*`vXBN6cY2C~JWi_05|ABI2UL;x0|
zJn&NB1pY=0SaE#PDw^O%dJ5IJBp|0Q;BJECLqBXiKisX6u(uN4TTITTAiNolos8*AVO}?U=yAv$7
zELqvZstaS}Qcw+PTv+NZBYtWo2jETYqT&ffKk2y?Y1g`6K{q$|0dofmcAjP#G*}1*
z6@*iegiAXg4Sep4;Cj4Wdv`Z-lFt8}2QYQHl-{A9r9puE>sTOLcSR<2W^)rtbD@JE
z&S#A}iUIM6tPh89>%p)((ZUtGVuseZ))D)M_+R=kf2(YSJkHg=7O
zeo!mQ^2H6Z%bDZC!c7LgAlh;eLL{uEOtwAF&${~f^$iVih*h>?e$?4TYWy?~t$LRm
zjVUK$RB?Eo$ov)K-%tqsn!QX{mT3#O5|7s^^aK95V^w~M#NkgB4txqtKMTu86ROOY
z6HcVetf`>>I|%qxu2LFJLXm^+s~G9c*5Os)nKkGC&4?xD-!-MB7tv8A!LcdW4t!{2
z9`EP-`1Nn~2`7i}W31Ie4HaPOQn&~gKVDRaDi|Z^)b7ySErWNba2PyMW{Oo6K%*Va
z8x9}$GIA>wB%dUNQKl&FII{3s4W1ij^3Bv|6K|Y~Rmu>SxW6{Qj@yPf2D;|2J0nm6
zySgxjdQcE;&vk!zPl*4bRW-i}v%d<^e2*)v2f`+rR9$+GRYX$c~^r-E=%@DQlJB4cH
z!%vOGm`>#j7+x}(%#J0RY5&~C;p|h_vpmUh=PeI@A1y&;DmJ~-NCT@z3Wu-z$uiaR
zNi4gCqEi@ozqsxIU9??JOBI!x)wQFh*WSdcY>t7f9`;N{qHL!ynx538yP*APFooW1A+!&m1|j`
zToA!d8XOgZ*k~QP9+!v}Xe#Aw?9lo5tOn~eoZ4VP5IS{sqxPPuEg%vA{5;4NWwo_D5X2!S>e^=3AL?e&$T8e^15&+W0aVoGra5qE*#T
zqz5ea{#6F;?P9rG@5j=swSpq$Ugy$yIHVvO`z`!yvO2zi=G8HrZ)99I7X-t^1np{J
zB(kvA)@UMVa`FQWTPWB^&)Ri1u0yTS&eJw2M%CAcSTJ~5me2%g$~~>^tF^xfj4P$#
zwGD_@!`WqmLu$nO>)vxsSu7Bq*MEmlG)UV?8Ft%hi*Y~3i;k}+rq2H9%X@(fH7SVG
z-k9H!Q0S+NLQF!0p$m8GcFQTQpv0AVg*O6tRy5tbNrbsUcpp#7XQwO+|T-5
zLEl)bVXUJ_>{NuT?_;ly3~WUqQUD^MO6j_|W{&Xwz@ngGkj9Q8)>P_*2%8PLvM1J=
z1}h&`_v7V6#Eea@jd{^a^(rcmd}vXJ=_@!rE^EZKFvOIv5xCpY*6J~y41aE3Tm23u
z>dBi3yi=oaYBWBaC9z6h(#o!W2VF!g=abi%%8FJ*B8#@(Rd>+VGs+8&A4edq&Izwj
zAiR~HN3}9tbMbtYxSsP33EzS@RySaS#?JBjL#F25x!IS1vkV1v4y=`y0dL2UgmMY>
zLa!1Z^Tg)iV-4wMU%j8maoVWZPo^4%_W60tiMAKisRH)p#uW>fBl>vSOjl60QDHV(
zG@KLhs;mPx_sn)LUED;E`V_qlJ`4?q4Daqb>!E1GnheC?4UwXsISSs6J!VdK9_#+-
z$NoJM6sGSvV19@@;_tl4-Hc^_Y4@8ZFy?CDow)Cv_nBi*n`=3{fMUDn(7&6c>^TjAs!A(qc3e(4qj-FRAvP>B6mjl{D%1z{j~
zP5!GvG5Lt{yI7-Z1F}MLhRz3JuV9z5Y`o3{e($rfsEGroFQnohWPbVhX1^aRace5v
z@67D3e*@C%mI;|_DrZk+GJ*49X5wtVhOd8Mqs<4mL&Xm%VfUy3r|qZATcc3Z0ch|g
zW;u~6kpsHvBkbO9ARpno?S;+3IxCc?ay`y~^W0F+pgpB#-cn@kc753~AY^Ux>^Q_l
zgERXs%*O73y#l=4d-X+nv9If2gkFm@yfX&Jq+NEcLXm6TQ`ox-jG5yK#zr)%ut(Y@AjqpM5uIJS-iN*MzRxhbftVjjC;RY5xUagB??D0hSReqLbCbrGzS=Ftv34&
ziRUV>4l6qm2tqQ}C;IH~j>5}mTp2dk0-i$v36(t~?%8VlgTrEtS!(G~WhX5AMI2$~
z(D}r);{PD{`tYYz*L`&*U_8!P^|lEr{i_{Qr;AiQ%xu^ZEE$DwcaG`2na9(X!CJ5m
zJ`Th1`y%x0tL~VC#c7#2)AJ{m2C!ZOW>|$ADCfTqrPG{|C`fb_pY;D_Wqhkq+HifaUdqM$`gZBAnN|g
zOXLFZmgEE4xaLKzFQmwVOeCGRt19<^3Q6F@rs36Q43Xu@GI#m+>89gqT^!LjCIdn^
z>JQ26yOUqhg^@OJJG2fN1k9{Iftm^we4@D=R(}z~S$rDG12aYCT8tffu~jrw%#><2
zeJ!NX&8seT`bfaQ@c>{6AMfWUstPs>zoILuM2-Rgc_#Y9ymIS=-H-5fUl~rqo{;vI
z96A*?W$LVF=Fn#GTr!FPK_7?T{iP^uKu((CKKB~?wG(kTxm4cgKff@ouC4d_Fed66
zb(7WH$3;}YnjOA(0Dx6=(JQlr#kT|j0?z5fIcDJ*X=Aabtbltf1nZRw&
z5l7!qRR3V+(0#Gmq|ogYMC!3J6JtGN{SlprD;IFYnP>|DUPpGphoeiYI-z^$rqX^+
zWD2&(4&X+k6qq@O7hj;s^rdz^oGmXTvTq6njIVO~EqZd9{s)MfD^ACY^+lhugW=>q
z^yYlQQR(#8?`c2*w><;pSTCx9=Z2X(=>(K-N4VFVJ4Y-ha{Ab25z$j7v6Z4|1hm~e
z&-<*F8_J`O?{*C*`!oasU}6XJ)%1YCGquI7HN4~Ax_){ji8U3VQ>hZ%pL^aD7HtEaM_2~m_w&-eM!7;H$oD)sq4@#e!P^Mnc{jq!(ElpQJwO98sIUk
z_d-94eeVc!8tsP7SBmt2h);%zJ5)NkDzu+=>CK7U5({B1+&uCwD
zBaxl^IY`FS%@lm@mDIt#KJ7gH7j}KDVo
za9BCkxCOWJq8vS#Av$Z)(RCT382lHW2gd{rWwE-tz8vfPcJFi5lz>4d#1QMS*5P+`
z)=RnHa@e9gkz;}4xa{K)(4fILb+l0V>14Tif6>MV1)6mp&Alos{8aaoT7huP|7Q?~
z#pof&=bn!ntuDJv_kcbrO{1W@I|OOU_mdEyu;p8wtH^K3%xE4@_|x@e?I^o`_RlwC7>2hF!y@-(wqufmGv@|!r+@70=e=ln?qngr!F$Ki{byIPa-SxrgtE`epS~r0Z^;`^tC|Ah67*T6_J&?f<-fQ`
zz0bN|yRLTyi0wWjVTR8cq>9G~-h8+|@rx92q*m}Yz@=;W8HpDlq9nvCStc8G*^Ly2
z8mXL1S}>U}ha3oy>yLC=RUk&)8`c$190
zYsi_n5j{FV4s_p}1n@1xgSW^$vzQ0Q-hd$Mp@G#mCo5sy%{UI~hS|n_5yjcLip{V*$=r?!PhKAycfv#r=8E9s~z%yCMt`7ePvQvXxY>97@NgR7+v@SW{jeFcB7}&)21tn?pno<^ENWvWgnRLXfBN=CsGr5tYxcI-
zx9oF8o<`sp-j514mQ|`2+W3+18yxdH!~2omuK4?q8SakGRj2)M%RFsdYk)@JGH&bE
zTh}{m_AC67)E)n)(|+%>R%4ba+aeR&&=E#T_{WM$Soeb%CP`KlUh!#8q>k2gWe4Aq
zW}BzP>_F%8oIx|N${nS$Y-mSHUWy5LU8t>v;&J`ryYaX)@O5g5q3JkHa~Zmyr#;;)
zY&r!zbTT$R%eTO~b!R=-k#srWN9K2xJa{p}ZSy{cZ5o~AzG%y#d7Ft$mreuFJCM9-
zU-4KoqTTHK&@#DcPJX3cqBeYcRLfj*^8KDpqiuX3;p?*8-s7qd64dj;Wyz%DV%z3^
z)oa(UdDkLSzn|Ol%<+Zf-UI4t(ktV@H(snX9Qtb=5I@_WdD}|R`9>PNoy9;f<{cm|
z>`+JKYalNZ*g9Rl)A^IhFqtlwZCtVABh+bPDvnk)KTABUq9a4l=g_*~^w4IeBqW}U
z`^1V&SMb;K)l_^Aq=A%bY@NodL4_*D=`Fp+>VQVli~$XPmv1yt<1gNz8dj5xt7-`v
z6H!v{zP8ymK#ekRdq}G$1HD<|R1pq<@wa?nVx8qT4Z;Y5bV1hP&cHn2Hjq6?IFM3H
zt_x?`WgYDd@%9&4guvYQA>U0BjIp04zQW%N^=Y6P2lQwD|43L
zM_<1`DYx{^?d)dBg;99?U!UAItzM@;wq~nBD%YiMTW4OG@13*83Id*5{p19-|NXv2
zwOEziRFK142jzytGWxHXsJJK@lWTsB2^l^owAHhKL>#_c?+!uOt1}x=FaDgB)b?XS
zOydQ_)@3=Dqx^~G>5yLoL~|`}hgxocN9t!`4O3vecA+9;dVU(OOEr$VY}pua5a9-i
zj1m3XNQhUhK9j~6kyxbP%C$V;Pur{E@$a{j?Suf88!$d{bnTnN85c_#J4Qcl^n@;m
z2@cSeXjO|X>MGVVehm#he%l*XPg3{o>CKN$`;|ovU^Q*_YMoylqI4SNwb1q2L&VJq
zSeNM4I67@ZXRuKZYA_iXejnqb(^yensMqWqPReZ?=%5lxA)z=Y6P%HaN3y;;u4}7b
zurC_VFQ%c3;lX0_B_1K?
zkWVCB7lX!;!0JDRJV00c$}&hZ5?2A)pD$2psvw%v=leu*uDDcmK#l32OZ~tN
zRfAR67q@ptU`QKQ3YQ(l(){+YaPH1X>L3?xqgr$8Wfh1i^lbmhG&8dx*eH_KV74=PQ_Sziq*T#gO
zZNZJD*_4}GLB_WHU>KA`;t-Ds48jE=-M`AGK{`yL2G;;$C~9VNE?el=?I5LE|Brqk
zo-v+W>BS^9Y+v)(bTRor)L5!qyg1lyX23a7X;3zZr*ss9CZ<0f=FhMnNcg7YnA<5X
zG>SeM_!0xkvTTLahAQSw&gNThhBFUaK=!>24a(e+>MKMLo@O7D|4e@>d_(*6VRX}D
z)r)x#x`8C_7l}Klp9ehs=y%><|JgQxT-T|h`x39UB*
zcSnR>KHB+rLjq_Vp5~pN69x3r1dP+a=KDNCSEkS
z?q|aYimOb$`WKvFxiB~c#f{V&jkMJH`}X?-RM-3c!b=Y&kD(<31{R*E^WR5v4q3M7
zp9_Sc>!3p!VV51bqqoPy4qiI1mNd$Bs%9Kx^3JC_Pg-Ws%=Nd5eMdO;^=3bYVI1F;
zuRdJ#zgZHn&CLja5qKvVVsve!@_X33!wl)Sb)l>DNJ)l5w=U8
zO;9IM&H>E(W~jio`PJP`DN?7rqdj_MTMgvUER5%D<4YLA%~9=UpRokeyg9gOq0&fO
zXq<)=AhzL0#A`P=m%Z;Olz?K^Cx#tzVsTY0_9UHTHi>B&lcwlorE
z<^vc=x!u2#6UJ2m?maxIw3z-+Gs*|Ke^zo7W{`Rd4Lg)f8&2#JXMJzYt|x`gE|a
zJd_D!5RL{Sb+>&ZBBa%IgPxh3$Gra5m`t?MGm=QgsIc^JZ?(@hezOzaAKayIn5w~qVo
z_7O@hsoibJ-#nS3Epr4mKe+9pH8o#C1k|#tGW&kNVgO3d*k=FaAp+O#mTs93&mXV~3s}~jdy_9@1~Eh$eetWD(IvnXiAcgn5fZA?QHXw`h=^qVjL`i)ITav$@5(wj$5U12Li
zXty>|1g+YU2wN1`6t1AdZ*=fh@8t?6$1es91ODWpQp%x#6PvYP%*v7wSqN>7I@(hm
zx*z+*tk3W__j{T2BD>rZ;VXPLoz&M)W_71pb(o9+J9{j+n=Asx`9W{1n#7bA2j9NV
zkfNXt*tsI`Kl-`P;-|LJkAE^J)myKWsWz$+J`Nphvw;9
zx)&lDb?SCQ_@5IVAMa4_^jb=x3~epbwY$f@A_t1J17yTuZoBDEMH=OjzV`a;gsjF<
zX+=?ePQB>dm)ap?x^+B#cYu-OkCP$KNB5n-yuKp)=CluhOgR)Flh>0K)-!z5`NQ>B
z`-SvnH?VL#!?b>>g~ptrc;-AnOfA~bt(f9Yc0wX8^Tt1z~Z(kgX(&T=;*eBHUR>xhxm*
zWGKBiLM^tYcagD4cU8k$YZ2-~=`*CJ5*4DB>1Cc)N;)tRLwxr`D;c1kc?-3>ugiGTY91GDT%d9PNP!?;
zeJhta{YK?ym^k$J=1(Tl;y7d^*^L8Lt1`)1s112>AnHIB#Ip@9a2H@49AUyz`WJsXEX4U!S+SKXo%B^q(uAsfLo=
zi*yn7siEB)AjAs7vdkL7x?M9%wq+3Xvg4pj4ynJDF(OTSzR1|dx4qw*H{XDs%fx1&
zSyl)a0sxYqY{S20x|bQz@q_w-F96>YhwtG)_ejLTF`)<+LS^-Ur6x?H(qLZ+_s0P1
z76fTk3W9i*e|^Y34p>$90Uo?_bpX<_5bX{|_&rI)l11haR_UZ6!}rF9HA@)|l?_8D
zTa+d@D{)9g}IIVa_UC9Q=Gcvoijt~GsCo{E{p(>>F=`RA%HY0)Y
z;Y-jSKOhxH1qfa*CqV=rv!rgLJrEueV3fI_3{%Ukw#A_VROo=pe_(0i`cWk-@@n6D
zF!}CsEt|fwP14sKv_#0sL~hGpErEsy6x7#Ij`vDPDDcQ6Mo>n-)j(-noDKnkQtaBO
zA~%~?({$E=m570W@;1vbK%ZWh+V;u7?^=Qnc(27unfwbAun(Me-mDMpd?+8TkNE@L
zPhoNlrATDNBD>NXz57+`_W?pmr+>?8~iA^Aoo$w6L0r0S)B;#F!L`U7M~Qa
z+|7Z)GRmLwmmo)VXn*uQrCo@NLVC*CCMLR0F}n(-mAN&5am=lk
zuHsqHc?=ug%fFg(`pfYnf0eFZo}M(SZX0VKZv2Y0tVTi^dT!B?pMR?Fx$u8=be3UF
z{cRj4q!}Pe$3O+7m6UEpx;sWmcgJ9Wh%`ty{OJxUk(Q9|P`V@qMmNvT=jC=?XD`mq
zcJBKdpHthzF1I5A>Qhq}V#g+4)7Bup}V+>G!Kr*S$KA4IJL`Xa6p&)oN^
zooD3x9yR@^9g)?vgi^?)$%U?SYDTau86zZYQL46mljU`FwlHulsN7PyPA?GzH^^mj
z@krCq4Jgk&bQfl9TupW7jfJfYG>7v(mxn;C23dLYdDUznFHoIA{E02$@-Kug^(B|h
zL?{&Pc!)|czo0bH*_>z2{X$K7o*8N6hMZAux$=$bPp|+p37P=~^z-WfIS_E3*qp|2
zBitFS*ogOOFMqXE5{WZhRIzSJqvjs9!HVouV9FN`QNb6DcH9-&R
z!q@O?Tfc6d%lL9B92d~VY2M1ZKmFTiD;d|5PtaHNuVMXql_c_gv1o6k_ZN<~CSLe$
z4ZN%A5BfJ1th5z+*Tx&5i9CmP`?gaPmzxhP+>dXq$0%1vkK$F5smDKtynr})6Fw8j
zJON#=qHnItv0tfLH%9~uuHV!HbsvX3fg|F6Rsv1u8vzqec~pyeU-U8tkJDNcxA~v;
zb2d;ogP#mHjvOco_EO5qt@zwumAFl7&W;0JK(5}_SRvWShoKeuNzl<5^zElwyoI{P
z4FwlKeU6gHD#hGH5p?b08f99~Z5EmsqR3yTDP>e!yHj;LwztNPirC2mvnSbrZ^z+gI6qi}C|C_CB{rWLMSOC``9``-`6fF~!n`
zch|%JMp3_LGtN?tl7?dxFIUs(vBh9Vz}r^T9W+e}t0;E0To{>&}9t?CDvr
z52i|l*p1tstvNU|Zh%snfxe_2VU75xU|Dew77TgxBX1)sH+!%0d#VR;a!!|<-LYys
zc#)pV6&9I13>nE`2xm|nOl8roR0N_-ucZacqLk;^E^h%4z*UktW|6HW@)wKzvX^pF
zec6C9k>`JhDfb@Oi(_*&%JSD>vWvUXD^vi@wqgb-i|`Mdv-XF@VOR6Wg({cLQaodw
zDR6WOA=3V*YcUD@bljy>|AJV+jT}P%qG)+|@G+;rzs$JCh
z_x^liHUW*G^xX|%E^ui+-=<{0Y%TF6Xye<9{K6u_*W>cgHOtHHvl6=E9;FIvcZ)aLL;b>)%HesMhr%8
ziYk121Z!RiDy@yJx%+;-JF@VHM6{ci3`*qtBpX)2nD1a*x1H)&y{FTEe;Cku3HvSa
zrPq3rv*n0OdCXV9|E>5TaDens@Ssaat7hbGzmHHGm67BZ#O6Ia^KqDn;YH7jBHPNH
zA<|`5mzj0m*_RaQyeBjApy`I;gpvXbpDnBoe1SNs6_f{!7r92QzwyHru?ZnGABKGl
zFQ=-E-;vcQI8LnfK*uhTG2`GCO^0l-31qtX_*Q4f>867%XLl3Y
zs(~gTzM2H0x?OhcZc}fc;)EagT*RRT9mH<
zG;kcYUUd?-k_ke0vn|-p5Q4h7=DGPJ`qACL1j0-jUIiZPugoO
ztjNocyTIXiqiFk|v6xj%#r@$?B8Oo(A*(~zZ5BOs1V8W)t^pp#>y}&k0nDxmpQlh+GAvHiKwXFQXgz(ohiuia0%Wf;(LFF3@i~nC%n4Mw_1h4RF(
zQluzfKYYI7?-A%(4fln1`51vF@u`dtuhrNPzuWe6g@7)8pVK$SziP=YqoHCP-lwW{
zR?N%5Z|SgL^3ygLANG8>lX@|%%m)=?mht!Z!CM?iEXyaTQ!i}kKI47y0M3;B9UiB`
z3376_(Y&84q7slrME(CeNqK#6*EXEqi2HZeIKXxs0p*yI%Hm%(fK5-Ad&k9OA8Gmj
z{e33+^~10rhY*K#!&`G<^5?--3Gn+X7HDu@BxHS(%#A>>LC=qW*)KG|KC+3w$?G)^
z;Zl;U$Z40d$NP5VrwKf<5W=at1%hJDULxa`MCo*f5
zhC+|oGRE^lCw&ju3kYa_58_Pw>}j_7>6VQGQ9PWRR#}S%v%sEJK9Qv)V*BnQIb5pI
zVUbabf_QGgz{>;zBBz)|=4Sdk``ShH>h(5>1@D1;UtS3Eo{!}>fW&NeKfMzzbgknq
zJCx2ZHINyi*DL7WV&S%HG5$=Svs$Gl>W9a>P$2F~Z|2#~HzeBit;Y7jJI}!aq(7)%_IOQBe1#!=_b3mL#)yj!j5bf3$4A=$}BVzEIB^jXWud!d(3oD<1Xb8LYT(yL7
z^ht&}BTWeA8m>Xy;A5I*Lw|3fwIYEEy))h$8qu{;45KfVpfSpT1ZJCTwh}}<@Ed^`
zM2sSzcK)+~t5Ff~eug_PNHbjqnD_Z@<^GM`6+4%od;TyLjs=M!v~Da^iYN6L697;J
z68GqG;Ic%LK|YCSY_aL}E!Dt3P7xmqzsOB_V%T~Xu0)5W8W|Z6Z8;61e(OvT9
zouxaLrT>PBS0pJdM#g~Y%B=s*g<@61S=ivukhhd9iU&%mOa$qZr2|tuJQPWl^?`K(
zjtVpS$h8=UKGKTIMS$j!tI*?3uJGNN&tv=@na^GCNgC$mIBM#rV$~n
zpO|3)oZpa|XzyNCHUH;Mt$p_obU!S7FIhh;z~rDF8+j^S_)o5pbjK)Tok5K+GyFbt
z?8d2A|G3_C%qg
z{+y4uj@MMNbKohoAIiRt1@&WVtHHn{?H@#ddab-`Kr4?`&Z$d3LsU;u?>sa}CNTN2
z)5knG4=NrBfwzvLQ3RR#MbqT@fqtUEYQvP_NyIV?KC80X8hYJ?3-l>p_59-rurXwyXLJDn+nQYwaN3ItAGTW%P2}-qB49!`6m-qflcVz*D`pE
z#804jN2O(zbqxt#rK73bhKwLtJUsV!DV63>)2i!ZA6kluFG?TFjkVS9ItpQ=AhE)=
zD4aG2kcw)*y1!IzNZ;JqTPBab5rrQlG%C&-xaMhqgg~{zfKp~NGO0Ywj)@MBG?D=D
z0pjf%=h~&(Z;x3yqb+wpSYmqG#H=W?K#rMWwfa*?PH?i(3QD%1J8_M1Xkcq|OF@O<
zcgA*W+p=_-r^$fA70?*cSWkx38dwtD=>^~?1d3+}F0XkmK$IzHzmNROji46CE7J`f
z$*ih&GcMI2adunw_vM^z;<0aPvY8%OL-Ylkoo?4Wb1YJ#nbT_tTP`+w$@vIKFJxba
zA^dJz6`S%Jpnh#yu`Wt)HQST&`bWu!;oYf*zlp-yrTtP4|C+NukblyBKU;+I_wOIB
zHkQqfRkb_(7f!;tL8dr;mFxPk(+(y}po{J_vBQHgmaF=aK7}3cnKUH7T7E9>rDLZX&T%y*
z>~5~}-By;q5`s7aJDwuq*>ieIip;xPu%5S&;}ytrDv2J{*&}nP;Tga)cKCwrYP7z#
zrJ6z{i|`K1VYeT`5_W14AIuh8iZ;#i+?$c{dWm;J>OXn+7(IZ3`3F?TVIPrHk)ydS
zn?tMbO9MmwGn5*P`}Ac_gc`>Zh%s$H^{KC-{?hBy8RC*j5c%4CYzHe)PQJwb`97E8
zUe!zevU^_UtkItvhycckSRqdiGUj84F&9mF#=IO1#_^xq33RQ9wT;IdnQ@{MLL339
zJg}W(t3T-FD-U;vv{x+*08DrMdWXI7)G3hk5-nk+9hWpC2P4htq{?i*)wJCbsUOM%IPZ_t*Y-T^_Kq@*HwQ?9
z&$BXFzVxhdbtK|^9kDermuyU(lj%h~&&XLlORl;9aYD#sa$cfLx+15$<#(RS@*)49
zCVmf$fz7JN@&6Dovj4y_a)4X+hzGnnnicT~Q|k@G%o)EG?v+{Rs`3~%Qu;{RuWsH5Yc
za@=n|y-NNJ*keDd-u*1PQ7XCQ|5oeqDnc~vhU%U^_BH&|7Bs4^U$Lysoaufv%-|Rb
z{pMn!07}O8g=l2=QEOn-#n;n5$)_syqfhEuvjygHGnAABo>mNY$MC8n@CF3D
zj;z++*}nJ(xj6!LL(9v|Sj)+ePkU*aszrv#tt>F)8CWCf=}#uz(!xL|tj9+&goxKk
zX7A@mW!B$(f#LF-A=v}9SW96;9Q~IIIPA_XSD^}eYSjqh#_mhWeM^@ty9J;b6;zd!I(qR$i=j*g+N
z{Zi<03PWBtEaP{5u68vmQSRxS#o=
ze#d09Fn&%o=uW|z$?en`gtIWYhNpiM;o+F(gj+N3t8S{ILOim3vsbm4C4F_gnS637
z%sQzGwnVmj(>H|KI&jUY3J~ZUwp^?KqaHN1BcRHCWQ$nu=W|&2S#)n#2!?z05L}WC
z&+)sTwbFb>Pu{3)zG!}s-BiGAcG}}>iad9~zFegVO7vC7Vue&Uq2Y|H(p6(j6tr)E
zrDHR1Kh&42Sig|~dMEnhD~G}ACkWN{W^&QwSgzFNMccGQ&R}u?BNk&mR+CY{og!+A
z4$ADo;`b}rtMA)@?~?PMOvG_&%=fE}6iUC!b!mY{&V@Nr4ukGKv%BuClQYOWo82f(
zE>-jz)IujTU%{;(ytuf9tCT)d!Lyp$?yJieMhx1(gzaA~5EOIYJ)y05#@coGsr~AY
zpFod5EotgIUhiW(@X5=7n#d*UbZQ2YnuI$bYIout!zulu8*E9Jp8H~vpZUh6Yce=@
zDCGaUQH~iZ8Dos|f#4W8xDv|R0Zuj+GEVj8YHWhR54Nw8yEn0EBif;!8D+`4@hG}{
z_ty!j&*kdlT6WA~(3dic%ULF6|9#|qX#9OZT)ojjYIi)3Df@ZTOlfeKh)LIEOQzju
zXnzAyTVJ$y02#V%iD?h6Usr>m9xaoiaynn)sHaBXL@;xCX{F(JWME^|cC?UFJV1b$xkpIBh$_>AE)!rIK8Z*Rf)H;gpMxy-mtRy7;y)&+|}d(|i}C
zk&$R^QN7VL%0LYfPieV5=J`jkvARA@_F8T47R@u>uAlv&3jC~{8GXmcY44ykoqg?|
z9`51mV~*ed642@sh*o&zbc2i>E7ms~$?U@yP@`tWk@r&;)t>=}vyaI!>@gaaVd1mNKO2hcYwu9sTK&g>uV>e>sXn)0G5=VqeVXa1rzKFoiAeGQdM=8(}W
zD~+$xuDmr6Cl|*Q9|Ry6fJtFR#ja{^6GGQ=72L{j-7YE0$_I&p&up=p*$a^)PHn3o
z^h$pRu2*fP3f&ej<|qp8@KOAhPk>t~WxCb8!9|f5cxpo@q7!Fi=X}-FA>FEqVv2cU
zo6B)dKZoM6KS!_Cogu&tVy(w6^|qeO=(9p@JtQ;~Jm<$)u1pMVM_p+xycw1_SajmW
zY6V=`QeK6c{vdu)Bkd%<4Gf2MLHpm&aKAL|*Lt|ENAb(DXY5p?f$LonfKkoH(wN+j
z2I+4P0~)IX?8#Bz+>Ks+2k49|*h~-_b#=JPa+C9OO5WYasQHacX`Gy>uikzPM2F@8
zY{xP9OCc{V{c60gAdvXY=L|2OHgxC{-kTh^+sm4XLm4vOjrfk)ao@w%Q92Lzrb5+O
ztnb;X+_=B#PK6@_9waaZKak`e1eZqiwN@kWhJFMmwK3hJVLTDl@qVQ;b!*RhxRuQ>
zN8jLf{l-^^7%WpH@<-7^4rHu5y;Q!(j`*7<^7$0{EEgwNAzOMwulS0Nli04rcVWXD
zqmIzN%AJflx~WZBGvA`UQPJ&t;g30yAU#VpO}g|oPE0h1bgoeNCyvI&hii(HB>U_=%CL3Nb|e>PM}9>Sw5*m
znE-xt-j!1Ck#s3So>{!KirM=$^C5Bj6CXsb_OD3n;6i=nR{MN`?k{Wh7KQ)1SPF?o
zYV+e_>A?{4-{T4;bju-U^yZVEC!b^v7CSSnRSv!nvT{@%m5Swdd(5Um!XHxNQR4MR)
z-fNh^4elWe-meHo78Lgq@s=ADQn@r5>?nNJ!kDh~tJ-@?zug?PJ;LJc+8&RI$i~L2
z1)L<)UmK$V;Zr269$DQ`|E9=)T;ccY%JnwW-S6DMsW{O(F!^1*%2dK?+(s?b(nwqm
zJw4n18ehRD_}XzPeLwwL+RymMr>IzaZIDgMZ?+gD^|QOtxCU|O9;BH6?WEfeh{&&p
zr7U_blF_8yQA}r)tF0I)-aPk6_
zYOr6_9uFr4
z4zEn_hn7kmwzsErmM9|v@Pyw*`DiS?
z^w@EMXjU0Th~exo-a(+E3eEd-NSCBC{+k9&?omDb>L0%Z0^%*`>m8Ss+HNF;5L8`b
zxxv=MY4!L6_MoT!dd)LFyiSZkK8rIut;Cuvhc_`mOT9sHh!;v6s2D
zo(cdRrt1ch?MHxMCo$R);?%(tteX7nyxRWraWWtXt7qNa#K`K8%4z4eZaS0PSR7rU
zWwjJ#{Kv*($;-xq$k3)SmllV=K&49pz@WjRKhv1mamA4ps>D5$G2hoR(L;lqM3kZThLe%zcw)9lbZ?Q!C%0L`XFt6zlzcU#
z^=FA;`({eLb*mSA)fP0Xjk`5jPJu>!_tqQJA%AmgXNUwQ$ukG!!zX=OtjZ=2=3GWt
z_n5g*#@T{iyU*O*B)b{I%fLnur;;b6%9S3z2Q6b$&G_NrQOQ)+meCt5KrX
z6Ps_D;rw%El7k3)?vINmfpSKfTs0Msk*beB?
zU!^kNMh-H4{yh5FWfQmVgVPsm|Agg_-L^SqeFRh0Cc)i!rZ2#74_oxXD0iir;=%Ol
zDXmxB8->_sbdF#-uIFzxhU4asjoI5XWB%6oajq(IS8cB$ffLEJ)*s(T(RAD_;7Z`w
zjPHa|%s`J}F_1tC)sqxdepl2xAs0GfIwjqndu|EBvq&C1oeH>N)gj3%bZhq=J#j@R-
zQ|wRWTY8W3KfUOs>~OZ|9MS6*e0S7`lIa-{BtT19jCSa{vDa1Kt{%A%g`OI*opkhb
zb=fiqBjeW}XbaY^aw5se?uQROaXqFAzdVh)C4FmFSL=;<#m;}7TzRFXk=vH@*xYVBB^l+t{ERjHIZ@ANN;ESN5Ko5T8-$9T9
zb{7$ilB8T_UrqlHhAPLb@zNOK@)TNufK{m1Q#Vg)mqye3yX*;kocq{qMK{;%CYRSP
z-wmJGGh7y8qRol&7@9v*W7(gA6Zn7CY>f?ySMus5*b>IB=U)1SZQ((avd14O;459yt=r#SFy;?*pP_T*>x1T$xyt6dBaX@n5+kF8)X
zU&^??qhnVv(hMAJvb7y{cp!6b(GN?CO0(a496J@}vfZ|62rCmipIiRF1iUke;-RK9
zdp+n??g^2@Z9$;!qA6WsRdEHRoo-CFt*a@Hhh0DIYIv&AH#hLhB1eKYXlp1e;0E=$
z^e?S{CQ2}y&Z|r910{*CmGzs{3=_1T)7dYmwx)##dvRsEa?z`+v(A3^im2(!l)Gb>
zo90g!3}yy9Czp7H8@)1|ll}gjP`U;MO2jIcue_*unE|z{qx~OhMK6
z?rfgrznW`)+JHfM-kBkZV2KR#N4?1>zpvHKn3Vq{NUR6fKa;^dX&AdP#=gav=-4*S
zYWA+Enxc^qX5b$!_p;%Kx2%4~f|WpdI>c1Xv@L?;NlVMFyMA{d
z{lWs#3R+xkIi1SU-)BS8hy;RC$VWBOtV7K;8AD0P1(v;EhCuthTD162c%7&$YLo3|?ytCH>+u60K
zHNUx7_WG2b$KZ7oOy=~=OMj;p^F&`!7xT@tIvZV!y|u&hZ)O1Wowe?mDPla)N9zAb
z_p!7Pl;WfH^vgTJ+gJt;3~3Z9du!C{Sj_EWF2pCF@5L3biew50>EZ$?g;8W*Y7>|F
z!G6EbvsnxnAF0uNB!wVu!gL_hv?Q8nXofkPh@-Gf*+$^elyx$%sB5KNAD^#uN-1DP
zD4^4SRC6<`nF=xpl9P2c?6!ZrV4+p;k0VY2*~W-DD*%)x?y3pOm-{+Gb0p$cE2_4-
zy2^aA%(TDP&fO{a-MlK1&Hl5dOL
zsnV4Otxmq~Nfw-@pktVOwzwj;BqbDh59@ip(0Z%N*0+>S!f?VH*X2n;62pEEmo`m;0*d~{8SsE3P
z{LIO$4S$+rMdYM!Sp2nuO(w>@VjdA%Im`9(KJbp^{h6_4D~G(}es$B>x7r3b{0PiU
z473!zQ#m54@&uxvc}8%S+;YyN&U2!b{0eq_%FUPR`tpl0X!KOwPmLdh