diff --git a/.github/workflows/dockerbuild-nightly.yaml b/.github/workflows/dockerbuild-nightly.yaml
index cb7907c6..36bcb0a6 100644
--- a/.github/workflows/dockerbuild-nightly.yaml
+++ b/.github/workflows/dockerbuild-nightly.yaml
@@ -26,10 +26,10 @@ jobs:
path: backend
version: nightly
experimental: true
- - app: orborus
- path: functions/onprem/orborus
- version: nightly
- experimental: true
+ #- app: orborus
+ # path: functions/onprem/orborus
+ # version: nightly
+ # experimental: true
- app: worker
path: functions/onprem/worker
version: nightly
diff --git a/backend/Dockerfile b/backend/Dockerfile
index 2ee3bcf0..43c9744c 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.24 as builder
+FROM golang:1.25 as builder
# Add files
RUN mkdir /app
diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go
index fc3cbdd6..4c55cce8 100755
--- a/backend/go-app/docker.go
+++ b/backend/go-app/docker.go
@@ -389,6 +389,10 @@ func buildImage(tags []string, dockerfileLocation string) error {
backendNodeName := backendPodList.Items[0].Spec.NodeName
log.Printf("[INFO] Backend running on: %s", backendNodeName)
+ kanikoImage := os.Getenv("SHUFFLE_BUILDER_IMAGE")
+ if len(kanikoImage) == 0 {
+ kanikoImage = "gcr.io/kaniko-project/executor:latest"
+ }
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
@@ -400,7 +404,7 @@ func buildImage(tags []string, dockerfileLocation string) error {
Containers: []corev1.Container{
{
Name: "kaniko",
- Image: "gcr.io/kaniko-project/executor:latest",
+ Image: kanikoImage,
Args: []string{
"--verbosity=debug",
"--log-format=text",
diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod
index c0078c08..c2d729a7 100644
--- a/backend/go-app/go.mod
+++ b/backend/go-app/go.mod
@@ -1,8 +1,6 @@
module shuffle
-go 1.24.0
-
-toolchain go1.24.3
+go 1.25.0
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
@@ -26,11 +24,11 @@ require (
github.com/gorilla/mux v1.8.1
github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.9.91
- github.com/shuffle/singul v0.0.26
- golang.org/x/crypto v0.45.0
+ github.com/shuffle/shuffle-shared v1.2.50
+ github.com/shuffle/singul v0.0.32
+ golang.org/x/crypto v0.48.0
google.golang.org/api v0.236.0
- google.golang.org/grpc v1.72.2
+ google.golang.org/grpc v1.79.3
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.34.2
k8s.io/apimachinery v0.34.2
@@ -38,17 +36,17 @@ require (
)
require (
- cel.dev/expr v0.20.0 // indirect
+ cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.121.1 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
- cloud.google.com/go/compute/metadata v0.7.0 // indirect
+ cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/scheduler v1.11.7 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
+ github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
@@ -56,16 +54,16 @@ require (
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/adrg/strutil v0.3.1 // indirect
github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect
+ github.com/andybalholm/brotli v1.2.0 // indirect
github.com/bitly/go-simplejson v0.5.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
- github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
+ github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
- github.com/coreos/go-oidc/v3 v3.17.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
@@ -73,15 +71,16 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
- github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
- github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
+ github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
+ github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/frikky/schemaless v0.0.28 // indirect
+ github.com/frikky/schemaless v0.0.34 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
- github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
@@ -102,6 +101,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
@@ -123,49 +123,54 @@ require (
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sashabaranov/go-openai v1.40.5 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
+ github.com/shirou/gopsutil/v3 v3.24.5 // indirect
+ github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shuffle/opensearch-go/v4 v4.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/spf13/pflag v1.0.6 // indirect
- github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
+ github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
+ github.com/tklauser/go-sysconf v0.3.12 // indirect
+ github.com/tklauser/numcpus v0.6.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
- github.com/zeebo/errs v1.4.0 // indirect
- go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
- go.opentelemetry.io/otel v1.36.0 // indirect
- go.opentelemetry.io/otel/metric v1.36.0 // indirect
- go.opentelemetry.io/otel/sdk v1.36.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
- go.opentelemetry.io/otel/trace v1.36.0 // indirect
+ go.opentelemetry.io/otel v1.42.0 // indirect
+ go.opentelemetry.io/otel/metric v1.42.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.42.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
+ go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
- golang.org/x/net v0.47.0 // indirect
- golang.org/x/oauth2 v0.30.0 // indirect
- golang.org/x/sync v0.18.0 // indirect
- golang.org/x/sys v0.38.0 // indirect
- golang.org/x/term v0.37.0 // indirect
- golang.org/x/text v0.31.0 // indirect
+ golang.org/x/net v0.51.0 // indirect
+ golang.org/x/oauth2 v0.34.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/sys v0.41.0 // indirect
+ golang.org/x/term v0.40.0 // indirect
+ golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
- google.golang.org/protobuf v1.36.6 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
+ google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum
index 0e38239a..74d86a55 100644
--- a/backend/go-app/go.sum
+++ b/backend/go-app/go.sum
@@ -1,5 +1,5 @@
-cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
-cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
+cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
+cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -17,8 +17,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
-cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
+cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
+cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM=
cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew=
@@ -49,8 +49,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
@@ -68,6 +68,8 @@ github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk=
github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk=
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -93,16 +95,14 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
-github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
-github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
+github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
-github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -127,21 +127,21 @@ github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
-github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
+github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
+github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
-github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
+github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
+github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0=
github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4=
-github.com/frikky/schemaless v0.0.28 h1:gdurMqBwtvY4Y/5pcxn8bdGCJn/eolKGz+c5DcidLkI=
-github.com/frikky/schemaless v0.0.28/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
+github.com/frikky/schemaless v0.0.34 h1:7w14wtbeBvIyKEA6ZugEPHyDV8maxMm3FOHWTEeIN+M=
+github.com/frikky/schemaless v0.0.34/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
github.com/fsouza/go-dockerclient v1.12.1 h1:FMoLq+Zhv9Oz/rFmu6JWkImfr6CBgZOPcL+bHW4gS0o=
github.com/fsouza/go-dockerclient v1.12.1/go.mod h1:OqsgJJcpCwqyM3JED7TdfM9QVWS5O7jSYwXxYKmOooY=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -165,8 +165,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -327,8 +327,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
@@ -350,10 +351,10 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shuffle/opensearch-go/v4 v4.0.0 h1:Mh85CD1MwOgXiFFYlzS1llnvdqL3CztRdR1ZT/SLIjU=
github.com/shuffle/opensearch-go/v4 v4.0.0/go.mod h1:gVLZKQE5khQWMb68XBtgKrhu78oLGL2zHwAGnFMDwC0=
-github.com/shuffle/shuffle-shared v0.9.87 h1:INA1cZ18MKcMs8kaVBJoQqrdNuqLHfge9elJ17hZfVc=
-github.com/shuffle/shuffle-shared v0.9.87/go.mod h1:AkXajlWWB16WfWjCw9K7y38L8JKABJzVxcX6KI/J1H4=
-github.com/shuffle/singul v0.0.26 h1:P2uZ8YIYQUN4qNfQujoW3Lhod91X6TAfrRHGWiUPNI8=
-github.com/shuffle/singul v0.0.26/go.mod h1:S8GszXL+fT2mTnh7j57V0r/tY5Y/mSW8QowQEnADs3k=
+github.com/shuffle/shuffle-shared v1.2.46 h1:+etj6NBBDHs2MBi/o6eFASEy3vuafFN2h5YKaAwcvVU=
+github.com/shuffle/shuffle-shared v1.2.46/go.mod h1:1DvYhS/4659FdHxzEpHTPuFUW0IfwQSuyluiGTwz9jo=
+github.com/shuffle/singul v0.0.32 h1:EoJjKCWfbtHqjo/UgofSvTp7rcYEH7k3eGXrZgXcMt8=
+github.com/shuffle/singul v0.0.32/go.mod h1:An0DHl+E+bAO1ziIbqUJdLXhjixN6DOyv0GwlXwsKHc=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -363,8 +364,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
-github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
+github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
+github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -398,41 +399,41 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
-github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
+go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
-go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
-go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
+go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
+go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
-go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
-go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
-go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
-go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
-go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
-go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
-go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
-go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
-go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
-go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
+go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
+go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
+go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
+go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
+go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
+go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
+go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
+go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
@@ -448,8 +449,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
+golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -496,15 +497,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
+golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
+golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
-golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
+golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
+golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -513,8 +514,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -538,13 +539,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
+golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
+golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -554,8 +555,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
+golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
@@ -587,12 +588,14 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
-golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
+golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -625,10 +628,10 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
-google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
+google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -636,12 +639,12 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
-google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/backend/go-app/main.go b/backend/go-app/main.go
index b88c2d52..f8aa1622 100755
--- a/backend/go-app/main.go
+++ b/backend/go-app/main.go
@@ -893,7 +893,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
childOrgs := []shuffle.Org{}
if len(org.CreatorOrg) > 0 {
- childOrgs, err = shuffle.GetAllChildOrgs(ctx, org.CreatorOrg)
+ childOrgs, _, err = shuffle.GetAllChildOrgs(ctx, org.CreatorOrg)
if err != nil {
log.Printf("[ERROR] Failed to get child orgs during getinfo: %s", err)
childOrgs = []shuffle.Org{}
@@ -1263,11 +1263,10 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
// Should run calculations
if len(org.SSOConfig.OpenIdAuthorization) > 0 {
- // baseSSOUrl, err = shuffle.GetOpenIdUrl(request, *org)
- // if err != nil {
- // log.Printf("[ERROR] Failed getting OpenID URL for org %s: %s", org.Name, err)
- // }
baseSSOUrl = shuffle.GetOpenIdUrl(request, *org)
+ if err != nil {
+ log.Printf("[ERROR] Failed getting OpenID URL for org %s: %s", org.Name, err)
+ }
break
}
@@ -4344,7 +4343,8 @@ func runMCPAction(resp http.ResponseWriter, request *http.Request) {
return
}
- go shuffle.HandleAiAgentExecutionStart(parentExec, agentNode, false)
+ callerName := "deployAppShuffleOnprem"
+ go shuffle.HandleAiAgentExecutionStart(parentExec, agentNode, false, callerName)
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s", "mode": "hybrid"}`, parentExec.ExecutionId, parentExec.Authorization)))
@@ -4352,7 +4352,7 @@ func runMCPAction(resp http.ResponseWriter, request *http.Request) {
} else {
//standalone mode
- workflowExecution, err := shuffle.PrepareSingleAction(ctx, user, "agent_starter", marshalledAction, false, "")
+ workflowExecution, err := shuffle.PrepareSingleAction(ctx, request, user, "agent_starter", marshalledAction, false, "")
if err != nil {
log.Printf("[ERROR] Failed to prepare standalone agent execution: %s", err)
resp.WriteHeader(500)
@@ -4368,7 +4368,7 @@ func runMCPAction(resp http.ResponseWriter, request *http.Request) {
}
// For non-agent calls (MCP or other apps): continue with existing flow
- workflowExecution, err := shuffle.PrepareSingleAction(ctx, user, "agent", marshalledAction, false, "")
+ workflowExecution, err := shuffle.PrepareSingleAction(ctx, request, user, "agent", marshalledAction, false, "")
if fileId == "agent_starter" {
log.Printf("[INFO] Returning early for agent_starter single action execution: %s", workflowExecution.ExecutionId)
resp.WriteHeader(200)
@@ -5893,6 +5893,7 @@ func initHandlers() {
r.HandleFunc("/api/v1/mcp", runMCPAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/agent", runMCPAction).Methods("POST", "OPTIONS")
+
//r.HandleFunc("/api/v1/apps/categories/run", shuffle.RunCategoryAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/upload", handleAppZipUpload).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}/activate", activateWorkflowAppDocker).Methods("GET", "OPTIONS")
@@ -5999,13 +6000,13 @@ func initHandlers() {
r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS")
// Specific triggers
- r.HandleFunc("/api/v1/workflows/{key}/outlook", shuffle.HandleCreateOutlookSub).Methods("POST", "OPTIONS")
- r.HandleFunc("/api/v1/workflows/{key}/outlook/{triggerId}", shuffle.HandleDeleteOutlookSub).Methods("DELETE", "OPTIONS")
- r.HandleFunc("/api/v1/triggers/outlook/register", shuffle.HandleNewOutlookRegister).Methods("GET", "OPTIONS")
- r.HandleFunc("/api/v1/triggers/outlook/getFolders", shuffle.HandleGetOutlookFolders).Methods("GET", "OPTIONS")
- r.HandleFunc("/api/v1/triggers/outlook/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
- r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
- r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
+// r.HandleFunc("/api/v1/workflows/{key}/outlook", shuffle.HandleCreateOutlookSub).Methods("POST", "OPTIONS")
+// r.HandleFunc("/api/v1/workflows/{key}/outlook/{triggerId}", shuffle.HandleDeleteOutlookSub).Methods("DELETE", "OPTIONS")
+// r.HandleFunc("/api/v1/triggers/outlook/register", shuffle.HandleNewOutlookRegister).Methods("GET", "OPTIONS")
+// r.HandleFunc("/api/v1/triggers/outlook/getFolders", shuffle.HandleGetOutlookFolders).Methods("GET", "OPTIONS")
+// r.HandleFunc("/api/v1/triggers/outlook/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
+// r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS")
+// r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/triggers/github/register", shuffle.HandleNewGithubRegister).Methods("PUT", "OPTIONS")
//r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS")
@@ -6015,8 +6016,8 @@ func initHandlers() {
//r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS")
- r.HandleFunc("/api/v1/workflows/{key}/gmail", shuffle.HandleCreateGmailSub).Methods("POST", "OPTIONS")
- r.HandleFunc("/api/v1/workflows/{key}/gmail/{triggerId}", shuffle.HandleDeleteGmailSub).Methods("DELETE", "OPTIONS")
+// r.HandleFunc("/api/v1/workflows/{key}/gmail", shuffle.HandleCreateGmailSub).Methods("POST", "OPTIONS")
+// r.HandleFunc("/api/v1/workflows/{key}/gmail/{triggerId}", shuffle.HandleDeleteGmailSub).Methods("DELETE", "OPTIONS")
//r.HandleFunc("/api/v1/triggers/gmail/{key}", handleGetSpecificGmailTrigger).Methods("GET", "OPTIONS")
//r.HandleFunc("/api/v1/triggers/outlook/getFolders", shuffle.HandleGetOutlookFolders).Methods("GET", "OPTIONS")
diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go
index 288fedc0..4badc784 100755
--- a/backend/go-app/walkoff.go
+++ b/backend/go-app/walkoff.go
@@ -300,8 +300,18 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
var env *shuffle.Environment
found := false
+ //for i := range envs {
+ // if envs[i].Name == environment {
+ // env = &envs[i]
+ // found = true
+ // break
+ // }
+ //}
+
+ parsedEnvName := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(environment, " ", "-"), "_", "-"))
for i := range envs {
- if envs[i].Name == environment {
+ parsedInnerName := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(envs[i].Name, " ", "-"), "_", "-"))
+ if parsedInnerName == parsedEnvName {
env = &envs[i]
found = true
break
@@ -344,7 +354,6 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
if len(executionRequests.Data) > 50 {
executionRequests.Data = executionRequests.Data[0:49]
}
-
}
newjson, err := json.Marshal(executionRequests)
@@ -813,15 +822,15 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
// log.Printf("Failed to delete webhook: %s", err)
//}
} else if item.TriggerType == "EMAIL" {
- err = shuffle.HandleOutlookSubRemoval(ctx, user, workflow.ID, item.ID)
- if err != nil {
- log.Printf("[DEBUG] Failed to delete OUTLOOK email sub (checking gmail after): %s", err)
- }
+ // err = shuffle.HandleOutlookSubRemoval(ctx, user, workflow.ID, item.ID)
+ // if err != nil {
+ // log.Printf("[DEBUG] Failed to delete OUTLOOK email sub (checking gmail after): %s", err)
+ // }
- err = shuffle.HandleGmailSubRemoval(ctx, user, workflow.ID, item.ID)
- if err != nil {
- log.Printf("Failed to delete gmail email sub: %s", err)
- }
+ // err = shuffle.HandleGmailSubRemoval(ctx, user, workflow.ID, item.ID)
+ // if err != nil {
+ // log.Printf("Failed to delete gmail email sub: %s", err)
+ // }
}
}
@@ -2143,6 +2152,7 @@ func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
}
+
func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, onlyname string) error {
ctx := context.Background()
@@ -2211,6 +2221,7 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string,
}
// 1. This parses OpenAPI v2 to v3 etc, for use.
+ // Always returns an ID as well
parsedOpenApi, err := handleSwaggerValidation(readFile)
if err != nil {
log.Printf("[WARNING] Validation error for %s: %s", filename, err)
@@ -2653,7 +2664,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
log.Printf("\n\nACTION TO RUN: %s. Body: %s. Source URL: %s\n\n", appId, string(body), request.URL.String())
- workflowExecution, err := shuffle.PrepareSingleAction(ctx, user, appId, body, runValidationAction, decisionId)
+ workflowExecution, err := shuffle.PrepareSingleAction(ctx, request, user, appId, body, runValidationAction, decisionId)
if appId == "agent_starter" {
log.Printf("[INFO] Returning early for agent_starter single action execution: %s", workflowExecution.ExecutionId)
resp.WriteHeader(200)
@@ -3322,6 +3333,7 @@ func LoadSpecificApps(resp http.ResponseWriter, request *http.Request) {
}
IterateAppGithubFolders(ctx, fs, dir, "", "", tmpBody.ForceUpdate, false)
+ iterateOpenApiGithub(fs, dir, "", "")
} else if strings.Contains(tmpBody.URL, "s3") {
//https://docs.aws.amazon.com/sdk-for-go/api/service/s3/
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 971d278b..ae0a46df 100755
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -73,7 +73,7 @@ import AppExplorer from "./views/AppExplorer.jsx";
var globalUrl = window.location.origin;
// Change the Shuffle version on UI from here
-const SHUFFLE_VERSION = "2.2.0"
+const SHUFFLE_VERSION = "2.2.1"
// CORS used for testing purposes. Should only happen with specific port and http
if (window.location.port === "3000") {
diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx
index 1a5d375d..959ce875 100644
--- a/frontend/src/components/Billing.jsx
+++ b/frontend/src/components/Billing.jsx
@@ -52,6 +52,7 @@ import {
Cancel as CancelIcon,
Shield as ShieldIcon,
Cancel as XCircleIcon,
+ LockOutlined as LockIcon,
FlashOn as ZapIcon,
People as UsersIcon,
FmdGoodOutlined as FmdGoodOutlinedIcon,
@@ -81,7 +82,7 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
}
const themeMode = theme.palette.mode;
- const workflowActive = selectedOrganization?.sync_features?.workflow_executions?.active;
+ const workflowActive = selectedOrganization?.sync_features?.app_executions?.active;
const multiTenantActive = selectedOrganization?.sync_features?.multi_tenant?.active;
const multiEnvActive = selectedOrganization?.sync_features?.multi_env?.active;
const brandingActive = selectedOrganization?.sync_features?.branding?.active;
@@ -104,9 +105,9 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
const features = [
{
icon: ZapIcon,
- label: 'Workflow Executions',
- licensed: `${selectedOrganization?.sync_features?.workflow_executions?.limit}/month limit`,
- unlicensed: '10,000/month limit',
+ label: 'App Runs',
+ licensed: `${selectedOrganization?.sync_features?.app_executions?.limit}/month limit`,
+ unlicensed: '25,000/month limit',
isActive: workflowActive,
},
{
@@ -203,7 +204,7 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
>
{isProdStatusOn
? 'Your organization has full access to all enterprise features and capabilities.'
- : 'View your current limits and available features. Upgrade to unlock enterprise capabilities.'}
+ : 'Your organization is running on the open-source plan. Upgrade to Enterprise to remove limits and unlock advanced capabilities.'}
{/* Features Grid */}
@@ -223,8 +224,8 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
})
.map((feature, index) => {
const Icon = feature.icon;
- const isAvailable = isProdStatusOn && feature.isActive;
- const statusColor = isAvailable ? colors.success : colors.disabled;
+ const isAvailable = isProdStatusOn;
+ const statusColor = isAvailable ? colors.success : colors.warning;
const bgColor = isAvailable ? themeMode === "dark" ? "#212121" : "#ffffff" : colors.disabledBg;
return (
@@ -235,19 +236,34 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
alignItems: 'center',
gap: 14,
padding: '14px 16px',
+ paddingLeft: !isAvailable ? 20 : 16,
borderRadius: 10,
background: bgColor,
border: `1px solid ${isAvailable ? colors.success + '40' : colors.border}`,
transition: 'all 0.2s',
+ position: 'relative',
+ overflow: 'hidden',
}}
>
+ {!isAvailable && (
+
+ )}
+
{/* Icon */}
fontSize: 15,
fontWeight: 600,
color: colors.textPrimary,
- marginBottom: 4,
+ marginBottom: 3,
}}
>
{feature.label}
@@ -272,7 +288,7 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
@@ -285,8 +301,8 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
style={{ color: colors.success, fontSize: 20, flexShrink: 0 }}
/>
) : (
-
)}
@@ -304,80 +320,323 @@ const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) =>
/>
{!isProdStatusOn && (
-
-
-
-
-
- About Shuffle Enterprise
-
-
- Shuffle Enterprise is designed for organizations that require scalability, high
- availability, dedicated support, and robust infrastructure to run mission-critical
- workflows in production environments. learn more
-
+ overflow: 'hidden',
+ border: `1px solid ${colors.accent}40`,
+ marginBottom: 24,
+ }}>
+ {/* Header */}
+
+
+
+
+
+
+ Unlock Shuffle Enterprise
+
+
+ Scale your security operations without limits
+
+
-
-
)}
- {/* CTA Section */}
- {!isProdStatusOn && (
-
-
-
- Ready to upgrade?
+ {/* Body */}
+
+
+ {[
+ { icon: ZapIcon, text: 'Higher App Run Limits' },
+ { icon: UsersIcon, text: 'Multi-Tenant Support' },
+ { icon: ShieldIcon, text: 'Enterprise SLA' },
+ { icon: FmdGoodOutlinedIcon, text: 'Multi-Location Deploy' },
+ ].map((item, i) => {
+ const ItemIcon = item.icon;
+ return (
+
+
+
+ {item.text}
+
+
+ );
+ })}
+
+
+
+ Purpose-built for security teams that need scalability, high availability, and dedicated
+ expert support to run mission-critical workflows in production.{' '}
+
+ Learn more
+
-
+ }
+ >
+ Upgrade Now
+
+ }
>
- Contact our team to learn more about enterprise features and pricing.
-
+ Talk to Sales
+
+
-
}
- endIcon={
}
- >
- Contact Sales
-
)}
);
};
+const AppRunsQueueCard = memo(({ environment, isAirGapped, isCloudSynching, totalRuns, limit, theme, navigate }) => {
+
+ const usagePct = limit > 0 ? (totalRuns / limit) * 100 : 0;
+ const hardPauseLimit = limit * 2;
+ const hardPausePct = hardPauseLimit > 0 ? Math.min((totalRuns / hardPauseLimit) * 100, 100) : 0;
+ const mainBarPct = Math.min(usagePct, 100);
+
+ const queueSize = environment?.queue !== undefined && environment?.queue !== null
+ ? Math.max(0, environment.queue)
+ : 0;
+
+ const isThrottled = isCloudSynching ? false : (isAirGapped ? hardPausePct >= 100 : usagePct >= 100);
+
+ let status, statusColor, statusBg;
+ if (isThrottled) {
+ status = 'Throttled';
+ statusColor = '#ef4444';
+ statusBg = 'rgba(239, 68, 68, 0.12)';
+ } else if (!isCloudSynching && usagePct >= 80) {
+ status = 'Warning';
+ statusColor = '#f59e0b';
+ statusBg = 'rgba(245, 158, 11, 0.12)';
+ } else {
+ status = 'Healthy';
+ statusColor = theme.palette.green;
+ statusBg = `${theme.palette.green}1f`;
+ }
+
+ const throttleRate = isThrottled ? '1/min' : '\u2014';
+ const estClearTime = isThrottled && queueSize > 0 ? `${queueSize} min` : '\u2014';
+ const mainBarColor = isThrottled ? '#ef4444' : usagePct >= 80 && !isCloudSynching ? '#f59e0b' : theme.palette.green;
+
+ const envTypeLabel = environment?.run_type === 'cloud' ? 'Cloud' : 'On-prem';
+ const envName = environment?.Name || environment?.name || 'Default';
+
+ const borderColor = theme.palette.slateGrayColor;
+ const trackBg = theme.palette.slateGrayColor;
+ const mutedText = theme.palette.text.secondary;
+
+ return (
+
+ {/* Title row */}
+
+
+ {envTypeLabel} - {envName} · App runs / month
+
+
+
+ {status}
+
+
+
+ {/* Main number */}
+
+
+ {totalRuns.toLocaleString()}
+
+
+ / {limit.toLocaleString()}
+
+
+
+ {isAirGapped && !isCloudSynching && (
+
+ No throttle until {hardPauseLimit.toLocaleString()} runs · 2× your plan limit
+
+ )}
+
+ {/* Main usage bar */}
+
+
+ {/* 80% threshold marker */}
+
+
+
+ 0
+ 80% threshold
+ {limit.toLocaleString()}
+
+
+ {/* Throttle limit row */}
+ {isAirGapped && (
+ <>
+
+
+ Burst throttle threshold (2× limit) · workflows throttle to 1/min above this
+
+
+ {totalRuns.toLocaleString()} / {hardPauseLimit.toLocaleString()}
+
+
+
+
+ >
+ )}
+
+ {/* Alert box for Warning / Throttled */}
+ {status !== 'Healthy' && (
+
+
+ {isThrottled ? 'Running slow \u2014 workflows are still running' : 'Approaching your monthly limit'}
+
+
+ {isThrottled
+ ? isAirGapped
+ ? `You've exceeded the burst threshold of ${hardPauseLimit.toLocaleString()} runs (2\u00d7 your plan limit). Your workflows are still running \u2014 there is no hard stop. Executions slow to 1 per minute until next month.`
+ : `You've exceeded your ${limit.toLocaleString()} monthly limit. Your instance keeps running \u2014 executions slow to 1 per minute until next month. Nothing is lost. You can view or clear the queue from the Locations tab.`
+ : isAirGapped
+ ? `You've used ${totalRuns.toLocaleString()} of ${limit.toLocaleString()} app runs. Workflows run normally \u2014 slowdown only begins at ${hardPauseLimit.toLocaleString()} runs (2\u00d7 your plan limit). No action needed.`
+ : `You've used ${totalRuns.toLocaleString()} of ${limit.toLocaleString()} app runs (${Math.max(0, limit - totalRuns).toLocaleString()} remaining). If you reach 100%, executions continue at a reduced rate of 1 per minute, nothing stops or is lost.`
+ }
+
+ {
+ if (isThrottled) navigate('/admin?tab=locations');
+ else window.open('https://shuffler.io/contact', '_blank', 'noopener,noreferrer');
+ }}
+ >
+ {isThrottled ? 'Manage queue' : 'Contact Us'}
+
+
+ )}
+
+ {/* Stats row */}
+
+ {[
+ { label: 'Queued jobs', value: queueSize },
+ { label: 'Throttle rate', value: throttleRate },
+ { label: 'Est. clear time', value: estClearTime },
+ ].map((stat, i) => (
+
+
+ {stat.label}
+
+
+ {stat.value}
+
+
+ ))}
+
+
+ );
+});
+
const Billing = memo((props) => {
const { globalUrl, userdata, serverside, billingInfo, stripeKey,isLoaded, selectedOrganization, handleGetOrg, clickedFromOrgTab, removeCookie} = props;
//const alert = useAlert();
@@ -413,7 +672,7 @@ const Billing = memo((props) => {
const [statistics, setStatistics] = useState([])
const [monthlyAppRunsParent, setMonthlyAppRunsParent] = useState(0)
const [monthlyAllSuborgExecutions, setMonthlyAllSuborgExecutions] = useState(0)
-
+ const [billingEnvironments, setBillingEnvironments] = useState([])
useEffect(() => {
if (monthlyAppRunsParent > 0 || monthlyAllSuborgExecutions > 0) {
const percentage = ((monthlyAppRunsParent + monthlyAllSuborgExecutions) / userdata.app_execution_limit) * 100;
@@ -525,6 +784,29 @@ const Billing = memo((props) => {
}, [])
+ const getBillingEnvironments = () => {
+ fetch(globalUrl + "/api/v1/getenvironments", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) return;
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson && Array.isArray(responseJson)) {
+ setBillingEnvironments(responseJson);
+ }
+ })
+ .catch((error) => {
+ console.log("Error fetching environments for billing:", error);
+ });
+ };
+
const getStats = (orgid) => {
if (orgid === undefined || orgid === null) {
@@ -563,6 +845,9 @@ const Billing = memo((props) => {
useEffect(() => {
if (selectedOrganization && selectedOrganization?.id?.length > 0) {
getStats(selectedOrganization.id);
+ if (!isCloud) {
+ getBillingEnvironments();
+ }
}
}, [selectedOrganization]);
@@ -2387,6 +2672,17 @@ const Billing = memo((props) => {
const isChildOrg = userdata?.active_org?.creator_org !== "" && userdata?.active_org?.creator_org !== undefined && userdata?.active_org?.creator_org !== null
+ const activeQueueEnvs = Array.isArray(billingEnvironments) ? billingEnvironments.filter(env => env != null && !env.archived && env.Type !== 'cloud') : [];
+ const totalQueueSize = activeQueueEnvs.reduce((sum, env) => sum + Math.max(0, env?.queue || 0), 0);
+ const aggregatedQueueEnv = {
+ Name: `${activeQueueEnvs.length} Runtime Location${activeQueueEnvs.length !== 1 ? 's' : ''}`,
+ run_type: 'on-prem',
+ queue: totalQueueSize,
+ };
+ const appExecLimit = selectedOrganization?.sync_features?.app_executions?.limit ?? 0;
+ const isAirGapped = selectedOrganization != null && (selectedOrganization.cloud_sync_active === true || selectedOrganization.cloud_sync === true) ? false : appExecLimit < 300000 ? false : true;
+ const isCloudSynching = selectedOrganization != null && selectedOrganization.cloud_sync === true && appExecLimit >= 300000;
+
useEffect(() => {
if (isChildOrg && currentTab === 0) {
setCurrentTab(1);
@@ -2800,6 +3096,28 @@ const Billing = memo((props) => {
) : null*/}
+
+ {/* Queue Management */}
+ {!isCloud && activeQueueEnvs.length > 0 && !isChildOrg && (
+
+
+ Queue Management
+
+
+ Real-time status of your app run usage and workflow queue across all runtime locations.
+
+
+
+ )}
+
{!isChildOrg && isCloud && (
diff --git a/frontend/src/components/CloudSyncTab.jsx b/frontend/src/components/CloudSyncTab.jsx
index e02c7783..3c197677 100644
--- a/frontend/src/components/CloudSyncTab.jsx
+++ b/frontend/src/components/CloudSyncTab.jsx
@@ -777,6 +777,10 @@ const CloudSyncTab = (props) => {
newname = "app runs"
}
+ if (key === "onprem_app_executions" && userdata.support !== true) {
+ return null
+ }
+
const griditem = {
primary: newkey,
secondary:
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index f1a87e7f..db69e886 100755
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -692,6 +692,7 @@ const AngularWorkflow = (defaultprops) => {
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
const [executionTimeline, setExecutionTimeline] = React.useState([]);
const [workflowExecutionCount, setWorkflowExecutionCount] = React.useState(0);
+ const [executionsLoading, setExecutionsLoading] = React.useState(false);
const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0);
const [workflowRecommendations, setWorkflowRecommendations] = React.useState(undefined);
const [showErrors, setShowErrors] = React.useState(true);
@@ -2343,6 +2344,8 @@ const AngularWorkflow = (defaultprops) => {
return
}
+ setExecutionsLoading(true);
+
var url = `${globalUrl}/api/v2/workflows/${id}/executions`
var method = "GET"
if (filter === undefined || filter === null || filter.toUpperCase() === "ALL") {
@@ -2413,7 +2416,11 @@ const AngularWorkflow = (defaultprops) => {
const newkeys = sortByKey(responseJson.executions, "-started_at");
setWorkflowExecutions(newkeys);
- var tmpView = new URLSearchParams(cursearch).get("execution_id");
+ // If an explicit orgId is passed (suborg context), don't read execution_id from the URL
+ // because that execution_id belongs to a different org's context.
+ var tmpView = (orgId !== undefined && orgId !== null && orgId.length > 0)
+ ? null
+ : new URLSearchParams(window.location.search).get("execution_id");
if (execution_id !== undefined && execution_id !== null && execution_id.length > 0 && (tmpView === undefined || tmpView === null || tmpView.length === 0)) {
tmpView = execution_id;
}
@@ -2422,6 +2429,7 @@ const AngularWorkflow = (defaultprops) => {
if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
// Don't clean up if it's already open
if (executionModalOpen === true) {
+ setExecutionsLoading(false);
return
}
@@ -2468,8 +2476,8 @@ const AngularWorkflow = (defaultprops) => {
}
}
} else {
- var tmpView = new URLSearchParams(cursearch).get("execution_id");
- if (tmpView === undefined || tmpView === null || tmpView.length === 0) {
+ var tmpView = new URLSearchParams(window.location.search).get("execution_id");
+ if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
const execution_id = tmpView;
setExecutionModalView(1);
setExecutionRequest({
@@ -2479,10 +2487,12 @@ const AngularWorkflow = (defaultprops) => {
start()
}
}
+ setExecutionsLoading(false);
})
.catch((error) => {
//toast(error.toString());
console.log("Get execution error: ", error.toString());
+ setExecutionsLoading(false);
});
};
@@ -10265,7 +10275,15 @@ const AngularWorkflow = (defaultprops) => {
}
}else if(!action.isStartNode) {
// This is to round the corners of the image
- const originalBase64 = action.large_image !== undefined && action.large_image !== null && action.large_image !== "" ? action.large_image : theme.palette.defaultImage
+ // If action has no large_image (e.g. imported/synced workflow where it was stripped),
+ // inject it from the available apps in the sidebar
+ let imageSource = (action.large_image !== undefined && action.large_image !== null && action.large_image !== "") ? action.large_image : ""
+ if (!imageSource) {
+ const foundApp = apps.find((a) => a.id === action.app_id) ||
+ apps.find((a) => a.name === action.app_name)
+ imageSource = (foundApp && foundApp.large_image) ? foundApp.large_image : ""
+ }
+ const originalBase64 = imageSource !== "" ? imageSource : theme.palette.defaultImage
const roundedImage = await roundBase64Image(originalBase64, 16);
action = {...action, large_image: roundedImage}
@@ -21318,6 +21336,21 @@ const AngularWorkflow = (defaultprops) => {
}
if (inputworkflow !== undefined && inputworkflow !== null && inputworkflow.id !== undefined && inputworkflow.id !== null) {
+ const isOrgChange = currentWorkflow.org_id !== undefined && currentWorkflow.org_id !== null && currentWorkflow.org_id.length > 0 && currentWorkflow.org_id !== inputworkflow.org_id;
+
+ if (isOrgChange) {
+ // Switching to a different suborg: clear stale execution state and URL param
+ const currentExecutionId = new URLSearchParams(window.location.search).get("execution_id");
+ if (currentExecutionId) {
+ const newSearch = removeParam("execution_id", window.location.search);
+ navigate(curpath + newSearch);
+ }
+ setExecutionModalView(0);
+ setExecutionData({});
+ setExecutionRunning(false);
+ stop();
+ }
+
getRevisionHistory(inputworkflow.id, 50, 0, inputworkflow.org_id)
getWorkflowExecution(inputworkflow.id, "", executionFilter, inputworkflow.org_id)
loadTriggers(inputworkflow.org_id)
@@ -23634,7 +23667,7 @@ const AngularWorkflow = (defaultprops) => {
)}
) : (
-
+
{
onClick={() => {
setExecutionRunning(false);
stop();
- // getWorkflowExecution(currentWorkflow.id, "");
- getWorkflowExecution(props.match.params.key, "");
setExecutionModalView(0);
setLastExecution(executionData.execution_id);
+ // getWorkflowExecution(currentWorkflow.id, "");
+ setTimeout(() => {
+ getWorkflowExecution(props.match.params.key, "");
+ }, 100);
}}
>
{
marginBottom: "auto",
}}
onClick={() => {
+ const newitem = removeParam("execution_id", cursearch);
+ navigate(curpath + newitem)
setExecutionRunning(false);
stop()
}}
@@ -24078,7 +24115,6 @@ const AngularWorkflow = (defaultprops) => {
{environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Find out here . If the Workflow doesn't start within 30 seconds with Orborus running, contact support: {supportEmail}
- No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Find out here . If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io
: null}
@@ -24160,6 +24196,14 @@ const AngularWorkflow = (defaultprops) => {
}
+ // Fallback: if large_image is still missing, look up by app_id in the apps state
+ if ((imgSrc === undefined || imgSrc === null || imgSrc.length === 0) && data.action.label && apps.length > 0) {
+ const appById = apps.find((a) => a.name === data.action.label);
+ if (appById !== undefined && appById !== null && appById.large_image) {
+ imgSrc = appById.large_image;
+ }
+ }
+
var actionimg =
curapp === null ? null : (
{
if (data.action.app_name === "Shuffle Tools" && data.action.id !== undefined && cy !== undefined) {
const nodedata = cy.getElementById(data.action.id).data();
//if (nodedata !== undefined && nodedata !== null && nodedata.fillstyle === "linear-gradient") {
+ const img = apps.find((a) => a.name === "Shuffle Tools")?.large_image
if (nodedata !== undefined && nodedata !== null) {
var imgStyle = {
marginRight: 20,
@@ -24232,7 +24277,7 @@ const AngularWorkflow = (defaultprops) => {
actionimg = (
);
@@ -24241,7 +24286,7 @@ const AngularWorkflow = (defaultprops) => {
actionimg = (
{
{data?.action?.name === "run_schemaless" || data?.action?.name === "run_singul" || data?.action?.name === "singul" && data?.action?.parameters?.length > 4 ?
param?.name === "x-debug-url")?.value || ""}`}>
{
}}
edge="end"
>
-
+
:
@@ -24755,7 +24800,7 @@ const AngularWorkflow = (defaultprops) => {
variant="body1"
style={{}}
>
- {data.name} :
+ {data.name} :
{showVariable ? data.value : null}
diff --git a/frontend/src/views/AppExplorer.jsx b/frontend/src/views/AppExplorer.jsx
index 1a169020..38297924 100644
--- a/frontend/src/views/AppExplorer.jsx
+++ b/frontend/src/views/AppExplorer.jsx
@@ -65,7 +65,6 @@ import { Context } from "../context/ContextApi.jsx";
import {
SearchBox,
- StaticRefinementList,
RefinementList,
InstantSearch,
connectSearchBox,
diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx
index 443181b5..dea3a08d 100644
--- a/frontend/src/views/Workflows2.jsx
+++ b/frontend/src/views/Workflows2.jsx
@@ -36,6 +36,8 @@ import {
Button,
TextField,
FormControl,
+ FormControlLabel,
+ Switch,
IconButton,
Menu,
MenuItem,
@@ -53,6 +55,12 @@ import {
Zoom,
Collapse,
Skeleton,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
} from "@mui/material";
// Material UI Icons
@@ -1075,6 +1083,10 @@ const Workflows2 = (props) => {
const [downloadBranch, setDownloadBranch] = React.useState("main");
const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] =
React.useState(false);
+ const [syncMode, setSyncMode] = React.useState(false);
+ const [remoteWorkflows, setRemoteWorkflows] = React.useState([]);
+ const [remoteWorkflowsLoading, setRemoteWorkflowsLoading] = React.useState(false);
+ const [syncListModalOpen, setSyncListModalOpen] = React.useState(false);
const [exportModalOpen, setExportModalOpen] = React.useState(false);
const [exportData, setExportData] = React.useState("");
const [dialogModalOpen, setDialogModalOpen] = React.useState(false);
@@ -5967,6 +5979,78 @@ const Workflows2 = (props) => {
);
});
+ const listRemoteWorkflows = () => {
+ const parsedData = {
+ url: downloadUrl,
+ branch: downloadBranch || "master",
+ list_only: true,
+ };
+ if (field1.length > 0) parsedData["username"] = field1;
+ if (field2.length > 0) parsedData["password"] = field2;
+
+ setRemoteWorkflowsLoading(true);
+ fetch(globalUrl + "/api/v1/workflows/download_remote", {
+ method: "POST",
+ mode: "cors",
+ headers: { Accept: "application/json" },
+ body: JSON.stringify(parsedData),
+ credentials: "include",
+ })
+ .then((r) => r.json())
+ .then((responseJson) => {
+ setRemoteWorkflowsLoading(false);
+ if (responseJson.success && Array.isArray(responseJson.workflows)) {
+ setRemoteWorkflows(responseJson.workflows);
+ setLoadWorkflowsModalOpen(false);
+ setSyncListModalOpen(true);
+ } else {
+ toast("Failed to list remote workflows: " + (responseJson.reason || "Unknown error"));
+ }
+ })
+ .catch((err) => {
+ setRemoteWorkflowsLoading(false);
+ toast(err.toString());
+ });
+ };
+
+ const handleRemoteWorkflowAction = (remoteWf, action) => {
+ const parsedData = {
+ url: downloadUrl,
+ branch: downloadBranch || "master",
+ original_workflow_id: remoteWf.id,
+ };
+ if (action === "sync") {
+ parsedData["sync_to_id"] = remoteWf.org_workflow_id || remoteWf.id;
+ }
+ if (field1.length > 0) parsedData["username"] = field1;
+ if (field2.length > 0) parsedData["password"] = field2;
+
+ toast(action === "sync" ? `Syncing "${remoteWf.name}"...` : `Importing "${remoteWf.name}"...`);
+ fetch(globalUrl + "/api/v1/workflows/download_remote", {
+ method: "POST",
+ mode: "cors",
+ headers: { Accept: "application/json" },
+ body: JSON.stringify(parsedData),
+ credentials: "include",
+ })
+ .then((r) => r.json())
+ .then((responseJson) => {
+ if (responseJson.success) {
+ toast.success(action === "sync" ? `"${remoteWf.name}" synced successfully` : `"${remoteWf.name}" imported successfully`);
+ // Refresh org workflow list and update local exists_in_org state
+ getAvailableWorkflows();
+ setRemoteWorkflows((prev) =>
+ prev.map((w) =>
+ w.id === remoteWf.id ? { ...w, exists_in_org: true, org_workflow_id: w.id } : w
+ )
+ );
+ } else {
+ toast("Failed: " + (responseJson.reason || "Unknown error"));
+ }
+ })
+ .catch((err) => toast(err.toString()));
+ };
+
const importWorkflowsFromUrl = (url) => {
console.log("IMPORT WORKFLOWS FROM ", downloadUrl);
@@ -6018,8 +6102,12 @@ const Workflows2 = (props) => {
};
const handleGithubValidation = () => {
- importWorkflowsFromUrl(downloadUrl);
- setLoadWorkflowsModalOpen(false);
+ if (syncMode) {
+ listRemoteWorkflows();
+ } else {
+ importWorkflowsFromUrl(downloadUrl);
+ setLoadWorkflowsModalOpen(false);
+ }
}
const workflowDownloadModalOpen = loadWorkflowsModalOpen ? (
@@ -6141,6 +6229,22 @@ const Workflows2 = (props) => {
fullWidth
/>
+
+ setSyncMode(e.target.checked)}
+ color="primary"
+ />
+ }
+ label={
+
+ Sync — list workflows before importing
+
+ }
+ />
+
{
{
handleGithubValidation();
}}
>
- Submit
+ {remoteWorkflowsLoading ? : (syncMode ? "List & Sync" : "Submit")}
+
+
+
+ ) : null;
+
+ const syncListModal = syncListModalOpen ? (
+
setSyncListModalOpen(false)}
+ PaperProps={{
+ sx: {
+ borderRadius: theme?.palette?.DialogStyle?.borderRadius,
+ border: theme?.palette?.DialogStyle?.border,
+ minWidth: "700px",
+ maxWidth: "900px",
+ fontFamily: theme?.typography?.fontFamily,
+ backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
+ zIndex: 1000,
+ '& .MuiDialogContent-root': {
+ backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
+ },
+ '& .MuiDialogTitle-root': {
+ backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
+ },
+ '& .MuiDialogActions-root': {
+ backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
+ },
+ }
+ }}
+ >
+
+
+ Workflows from remote repository
+ setSyncListModalOpen(false)}>
+
+
+
+
+
+ {remoteWorkflows.length === 0 ? (
+ No workflow JSON files found in the repository.
+ ) : (
+
+
+
+
+ Name
+ {/* Folder */}
+ Last Updated
+ Action
+
+
+
+ {remoteWorkflows.map((wf) => (
+
+ {wf.name}
+ {/* {wf.folder_name || "—"} */}
+
+ {wf.updated_at && wf.updated_at > 0
+ ? new Date(wf.updated_at * 1000).toLocaleDateString()
+ : "—"}
+
+
+ {wf.exists_in_org ? (
+ }
+ onClick={() => handleRemoteWorkflowAction(wf, "sync")}
+ >
+ Sync
+
+ ) : (
+ }
+ onClick={() => handleRemoteWorkflowAction(wf, "import")}
+ >
+ Import
+
+ )}
+
+
+ ))}
+
+
+
+ )}
+
+
+ setSyncListModalOpen(false)} color="primary">
+ Close
@@ -6330,6 +6529,7 @@ const Workflows2 = (props) => {
{publishModal}
{aiAnnouncementModal}
{workflowDownloadModalOpen}
+ {syncListModal}
{/*!drawerOpen ?
diff --git a/functions/kubernetes/charts/shuffle/README.md b/functions/kubernetes/charts/shuffle/README.md
index 7c1315b9..ee8f8e06 100644
--- a/functions/kubernetes/charts/shuffle/README.md
+++ b/functions/kubernetes/charts/shuffle/README.md
@@ -215,7 +215,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
## Parameters
-##### Global parameters
+#### Global parameters
| Name | Description | Value |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
@@ -225,7 +225,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `global.compatibility.openshift.adaptSecurityContext` | Adapt the securityContext sections of the deployment to make them compatible with Openshift restricted-v2 SCC: remove runAsUser, runAsGroup and fsGroup and let the platform use their allowed default IDs. Possible values: auto (apply if the detected running cluster is Openshift), force (perform the adaptation always), disabled (do not perform adaptation) | `auto` |
| `global.compatibility.omitEmptySeLinuxOptions` | If set to true, removes the seLinuxOptions from the securityContexts when it is set to an empty object | `false` |
-##### Common parameters
+#### Common parameters
| Name | Description | Value |
| ------------------------ | --------------------------------------------------------------------------------------- | --------------- |
@@ -241,7 +241,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `diagnosticMode.command` | Command to override all containers in the chart release | `["sleep"]` |
| `diagnosticMode.args` | Args to override all containers in the chart release | `["infinity"]` |
-##### Shared Shuffle Parameters
+#### Shared Shuffle Parameters
| Name | Description | Value |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------- |
@@ -251,7 +251,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `shuffle.appBaseImageName` | The base image used for shuffle apps. The final image for an app is
//: | `frikky` |
| `shuffle.timezone` | The timezone used by Shuffle | `Europe/Berlin` |
-##### backend Parameters
+#### backend Parameters
| Name | Description | Value |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
@@ -356,6 +356,8 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `backend.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
| `backend.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
| `backend.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
+| `backend.debug` | Enable debug mode for backend, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments. | `false` |
+| `backend.autoGOMEMLIMIT` | Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit. | `true` |
| `backend.cleanupSchedule` | The interval in seconds at which the cleanup job runs | `300` |
| `backend.openSearch.url` | The URL at which OpenSearch is available | `http://{{ .Release.Name }}-opensearch:9200` |
| `backend.openSearch.username` | The username that is used for authenticating with OpenSearch | `admin` |
@@ -366,7 +368,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `backend.apps.downloadBranch` | The branch from which apps should be downloaded on startup. | `master` |
| `backend.apps.forceUpdate` | Force an update of apps on startup. | `false` |
-##### frontend Parameters
+#### frontend Parameters
| Name | Description | Value |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
@@ -472,7 +474,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `frontend.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
| `frontend.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
-##### orborus Parameters
+#### orborus Parameters
| Name | Description | Value |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
@@ -575,10 +577,12 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `orborus.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
| `orborus.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
| `orborus.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
+| `orborus.debug` | Enable debug mode for orborus, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments. | `false` |
+| `orborus.autoGOMEMLIMIT` | Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit. | `true` |
| `orborus.executionConcurrency` | The maximum amount of concurrent workflow executions per worker | `25` |
| `orborus.manageWorkerDeployments` | Whether workers are deployed and managed by orborus. When disabled, every worker is expected to be already deployed (see worker.enableHelmDeployment). | `true` |
-##### worker Parameters
+#### worker Parameters
| Name | Description | Value |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
@@ -684,9 +688,11 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `worker.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
| `worker.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
| `worker.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
+| `worker.debug` | Enable debug mode for worker, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments. | `false` |
+| `worker.autoGOMEMLIMIT` | Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit. | `true` |
| `worker.manageAppDeployments` | Whether apps are deployed and managed by worker. When disabled, every used app is expected to to be already deployed (see apps.enabled). | `true` |
-##### app Parameters
+#### app Parameters
| Name | Description | Value |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
@@ -789,120 +795,13 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `app.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` |
| `app.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` |
| `app.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy (ignored if allowExternalEgress=true) | `[]` |
+| `app.debug` | Enable debug mode for app, which enables extra logging | `false` |
| `app.mountTmpVolume` | Whether a writable /tmp emptyDir volume should be mounted to the app. | `true` |
| `app.exposedContainerPort` | The port that shuffle app containers will listen on for new requests. | `80` |
| `app.sdkTimeout` | The timeout in seconds for app actions. | `300` |
| `app.disableLogs` | Do not capture app logs. By default, app logs are captured, so that they are visible in the frontend. | `false` |
-##### Parameters to deploy apps using helm
-
-| Name | Description | Value |
-| ----------------------------- | -------------------------------------------------- | ------- |
-| `apps.enabled` | Whether apps should be deployed using helm. | `false` |
-| `apps.shuffleTools.enabled` | Whether the shuffle-tools app is enabled | `true` |
-| `apps.shuffleTools.version` | The version of the shuffle-tools app to deploy. | `1.2.0` |
-| `apps.shuffleSubflow.enabled` | Whether the shuffle-subflow app is enabled | `true` |
-| `apps.shuffleSubflow.version` | The version of the shuffle-subflow app to deploy. | `1.1.0` |
-| `apps.http.enabled` | Whether the http app is enabled | `true` |
-| `apps.http.version` | The version of the http app to deploy. | `1.4.0` |
-| `apps.MY_APP.app` | The name of the app (required, e.g. shuffle-tools) | |
-| `apps.MY_APP.version` | The version of the app (required, e.g. 1.2.0) | |
-
-##### Traffic Exposure Parameters
-
-| Name | Description | Value |
-| -------------------------- | ----------------------------------------------------------------------------------------------------- | --------------- |
-| `ingress.enabled` | Enable ingress record generation for frontend and backend | `false` |
-| `ingress.pathType` | Ingress path type for the frontend path | `Prefix` |
-| `ingress.backendPathType` | Ingress path type for the backend path | `Prefix` |
-| `ingress.apiVersion` | Force Ingress API version (automatically detected if not set) | `""` |
-| `ingress.hostname` | Default host for the ingress record | `shuffle.local` |
-| `ingress.ingressClassName` | IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) | `nginx` |
-| `ingress.path` | Ingress path for Shuffle frontend | `"/"` |
-| `ingress.backendPath` | Ingress path for Shuffle backend | `"/api/"` |
-| `ingress.annotations` | Additional annotations for the Ingress resource. | `{}` |
-| `ingress.tls` | Enable TLS configuration for the host defined at `ingress.hostname` parameter | `false` |
-| `ingress.selfSigned` | Create a TLS secret for this ingress record using self-signed certificates generated by Helm | `false` |
-| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the ingress record | `[]` |
-| `ingress.extraPaths` | An array with additional arbitrary paths that may need to be added to the ingress under the main host | `[]` |
-| `ingress.extraTls` | TLS configuration for additional hostname(s) to be covered with this ingress record | `[]` |
-| `ingress.secrets` | Custom TLS certificates as secrets | `[]` |
-| `ingress.extraRules` | Additional rules to be covered with this ingress record | `[]` |
-
-##### Istio Parameters
-
-| Name | Description | Value |
-| --------------------------------------- | ------------------------------------------------------------------------------- | ------------------------ |
-| `istio.enabled` | Enable creation of an Istio Gateway and VirtualService for frontend and backend | `false` |
-| `istio.apiVersion` | The istio apiVersion to use for Gateway and VirtualService resources | `networking.istio.io/v1` |
-| `istio.hosts` | One or more hosts exposed by Istio | `[]` |
-| `istio.gateway.annotations` | Additional annotations for the Gateway resource | `{}` |
-| `istio.gateway.selector` | The selector matches the ingress gateway pod labels | `{ istio: ingress }` |
-| `istio.gateway.http.enabled` | Enable HTTP server port 80 | `true` |
-| `istio.gateway.http.httpsRedirect` | If set to true, a 301 redirect is send for all HTTP connections | `false` |
-| `istio.gateway.https.enabled` | Enable HTTPS server on port 443 | `false` |
-| `istio.gateway.https.tlsCredentialName` | The name of the secret that holds the TLS certs including the CA certificates. | `""` |
-| `istio.gateway.https.tlsCipherSuites` | If specified, only support the specified cipher list. | `[]` |
-| `istio.gateway.extraServers` | Additional servers for the Gateway resource | `[]` |
-| `istio.virtualService.annotations` | Additional annotations for the VirtualService resource. | `{}` |
-| `istio.virtualService.backendHeaders` | Header manipulation rules for backend traffic | `{}` |
-| `istio.virtualService.frontendHeaders` | Header manipulation rules for frontend traffic | `{}` |
-
-##### Persistence Parameters
-
-| Name | Description | Value |
-| ------------------------------------- | ------------------------------------------------- | ------------------- |
-| `persistence.enabled` | Enable persistence using Persistent Volume Claims | `true` |
-| `persistence.apps.existingClaim` | Name of an existing PVC to use | `""` |
-| `persistence.apps.storageClass` | PVC Storage Class for shuffle-apps volume | `""` |
-| `persistence.apps.subPath` | The sub path used in the volume | `""` |
-| `persistence.apps.accessModes` | The access mode of the volume | `["ReadWriteOnce"]` |
-| `persistence.apps.size` | The size of the volume | `5Gi` |
-| `persistence.apps.annotations` | Annotations for the PVC | `{}` |
-| `persistence.apps.selector` | Selector to match an existing Persistent Volume | `{}` |
-| `persistence.appBuilder.storageClass` | PVC Storage Class for backend-apps-claim volume | `""` |
-| `persistence.appBuilder.accessModes` | The access mode of the volume | `["ReadWriteOnce"]` |
-| `persistence.appBuilder.size` | The size of the volume | `5Gi` |
-| `persistence.appBuilder.annotations` | Annotations for the PVC | `{}` |
-| `persistence.appBuilder.selector` | Selector to match an existing Persistent Volume | `{}` |
-| `persistence.files.existingClaim` | Name of an existing PVC to use | `""` |
-| `persistence.files.storageClass` | PVC Storage Class for shuffle-files volume | `""` |
-| `persistence.files.subPath` | The sub path used in the volume | `""` |
-| `persistence.files.accessModes` | The access mode of the volume | `["ReadWriteOnce"]` |
-| `persistence.files.size` | The size of the volume | `5Gi` |
-| `persistence.files.annotations` | Annotations for the PVC | `{}` |
-| `persistence.files.selector` | Selector to match an existing Persistent Volume | `{}` |
-
-##### Init Container Parameters
-
-| Name | Description | Value |
-| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
-| `volumePermissions.enabled` | Enable init container that changes the owner/group of the PV mount point to `runAsUser:fsGroup` | `false` |
-| `volumePermissions.image.registry` | OS Shell + Utility image registry | `docker.io` |
-| `volumePermissions.image.repository` | OS Shell + Utility image repository | `bitnamilegacy/os-shell` |
-| `volumePermissions.image.tag` | OS Shell + Utility image tag (immutable tags are recommended) | `12-debian-12-r30` |
-| `volumePermissions.image.pullPolicy` | OS Shell + Utility image pull policy | `IfNotPresent` |
-| `volumePermissions.image.pullSecrets` | OS Shell + Utility image pull secrets | `[]` |
-| `volumePermissions.resourcesPreset` | Set init container resources according to one common preset (allowed values: none, nano, small, medium, large, xlarge, 2xlarge). This is ignored if volumePermissions.resources is set (volumePermissions.resources is recommended for production). | `nano` |
-| `volumePermissions.resources` | Set init container requests and limits for different resources like CPU or memory (essential for production workloads) | `{}` |
-| `volumePermissions.containerSecurityContext.enabled` | Enabled init container' Security Context | `true` |
-| `volumePermissions.containerSecurityContext.seLinuxOptions` | Set SELinux options in init container | `{}` |
-| `volumePermissions.containerSecurityContext.runAsUser` | Set init container's Security Context runAsUser | `0` |
-
-##### OpenSearch Parameters
-
-| Name | Description | Value |
-| -------------------- | ----------------------------------------------------- | ------ |
-| `opensearch.enabled` | Switch to enable or disable the opensearch helm chart | `true` |
-
-##### Vault Parameters
-
-| Name | Description | Value |
-| --------------- | -------------------------------------------------------------------------- | ----- |
-| `vault.role` | Specify the Vault role, which should be used to get the secret from Vault. | `""` |
-| `vault.secrets` | A list of VaultSecrets to create | `[]` |
-
-##### Other Parameters
+#### Parameters to deploy apps using helm
| Name | Description | Value |
| ----------------------------- | -------------------------------------------------- | ------- |
@@ -1012,6 +911,7 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
#### Other Parameters
+
| Name | Description | Value |
| ----------------------------- | -------------------------------------------------- | ------- |
| `apps.enabled` | Whether apps should be deployed using helm. | `false` |
@@ -1119,3 +1019,4 @@ The password should be provided with the `SHUFFLE_OPENSEARCH_PASSWORD` env varia
| `vault.secrets` | A list of VaultSecrets to create | `[]` |
### Other Parameters
+
diff --git a/functions/kubernetes/charts/shuffle/templates/_helpers.tpl b/functions/kubernetes/charts/shuffle/templates/_helpers.tpl
index b78602bb..c3fae7b2 100644
--- a/functions/kubernetes/charts/shuffle/templates/_helpers.tpl
+++ b/functions/kubernetes/charts/shuffle/templates/_helpers.tpl
@@ -4,3 +4,46 @@ Return the proper image name (for the init container volume-permissions image)
{{- define "shuffle.volumePermissions.image" -}}
{{- include "common.images.image" ( dict "imageRoot" .Values.volumePermissions.image "global" .Values.global "chart" .Chart ) -}}
{{- end -}}
+
+{{/*
+Return a value for the GOMEMLIMIT env variable based on a given kubernetes resource memory limit.
+Usage:
+{{ include "shuffle.k8sMemoryLimitToGOMEMLIMIT" (dict "k8sMemoryLimit" $.Values.resources.limits.memory "context" $) }}
+*/}}
+{{- define "shuffle.k8sMemoryLimitToGOMEMLIMIT" -}}
+{{- $memoryLimit := .k8sMemoryLimit | default "" -}}
+{{- $result := "" -}}
+{{- if and $memoryLimit (gt (len $memoryLimit) 0) -}}
+{{- $bytes := 0 -}}
+{{- if hasSuffix "Ki" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "Ki" | int) 1024 -}}
+{{- else if hasSuffix "Mi" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "Mi" | int) 1048576 -}}
+{{- else if hasSuffix "Gi" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "Gi" | int) 1073741824 -}}
+{{- else if hasSuffix "Ti" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "Ti" | int) 1099511627776 -}}
+{{- else if hasSuffix "Pi" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "Pi" | int) 1125899906842624 -}}
+{{- else if hasSuffix "Ei" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "Ei" | int) 1152921504606846976 -}}
+{{- else if hasSuffix "K" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "K" | int) 1000 -}}
+{{- else if hasSuffix "M" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "M" | int) 1000000 -}}
+{{- else if hasSuffix "G" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "G" | int) 1000000000 -}}
+{{- else if hasSuffix "T" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "T" | int) 1000000000000 -}}
+{{- else if hasSuffix "P" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "P" | int) 1000000000000000 -}}
+{{- else if hasSuffix "E" $memoryLimit -}}
+{{- $bytes = mul ($memoryLimit | trimSuffix "E" | int) 1000000000000000000 -}}
+{{- else -}}
+{{- $bytes = $memoryLimit | int -}}
+{{- end -}}
+{{- $gomaxmem := div (mul $bytes 9) 10 -}}
+{{- $result = printf "%d" $gomaxmem -}}
+{{- end -}}
+{{- $result -}}
+{{- end -}}
\ No newline at end of file
diff --git a/functions/kubernetes/charts/shuffle/templates/backend/_helpers.tpl b/functions/kubernetes/charts/shuffle/templates/backend/_helpers.tpl
index 48bd6543..13cb0ce7 100644
--- a/functions/kubernetes/charts/shuffle/templates/backend/_helpers.tpl
+++ b/functions/kubernetes/charts/shuffle/templates/backend/_helpers.tpl
@@ -113,4 +113,13 @@ SHUFFLE_OPENSEARCH_INDEX_PREFIX: "{{ .Values.backend.openSearch.indexPrefix }}"
SHUFFLE_RERUN_SCHEDULE: "{{ .Values.backend.cleanupSchedule }}"
TZ: "{{ .Values.shuffle.timezone }}"
REGISTRY_URL: "{{ .Values.shuffle.appRegistry }}" # Used by app builder
+{{- if .Values.backend.debug }}
+DEBUG: "true"
+{{- end }}
+{{- if .Values.backend.autoGOMEMLIMIT }}
+{{- $backendResources := (.Values.backend.resources | default (include "common.resources.preset" (dict "type" .Values.backend.resourcesPreset) | fromYaml)) -}}
+{{- if and $backendResources $backendResources.limits $backendResources.limits.memory }}
+GOMEMLIMIT: {{ include "shuffle.k8sMemoryLimitToGOMEMLIMIT" (dict "k8sMemoryLimit" $backendResources.limits.memory) | quote }}
+{{- end }}
+{{- end }}
{{- end -}}
\ No newline at end of file
diff --git a/functions/kubernetes/charts/shuffle/templates/orborus/_helpers.tpl b/functions/kubernetes/charts/shuffle/templates/orborus/_helpers.tpl
index 3ced6821..0af574a1 100644
--- a/functions/kubernetes/charts/shuffle/templates/orborus/_helpers.tpl
+++ b/functions/kubernetes/charts/shuffle/templates/orborus/_helpers.tpl
@@ -89,6 +89,15 @@ TZ: "{{ .Values.shuffle.timezone }}"
BASE_URL: {{ include "shuffle.backend.baseUrl" . | quote }}
KUBERNETES_NAMESPACE: "{{ .Release.Namespace }}"
SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY: {{ .Values.orborus.executionConcurrency | quote }}
+{{- if .Values.orborus.debug }}
+DEBUG: "true"
+{{- end }}
+{{- if .Values.orborus.autoGOMEMLIMIT }}
+{{- $orborusResources := (.Values.orborus.resources | default (include "common.resources.preset" (dict "type" .Values.orborus.resourcesPreset) | fromYaml)) -}}
+{{- if and $orborusResources $orborusResources.limits $orborusResources.limits.memory }}
+GOMEMLIMIT: {{ include "shuffle.k8sMemoryLimitToGOMEMLIMIT" (dict "k8sMemoryLimit" $orborusResources.limits.memory) | quote }}
+{{- end }}
+{{- end }}
{{- if .Values.orborus.manageWorkerDeployments }}
# Shuffle worker configuration
diff --git a/functions/kubernetes/charts/shuffle/templates/shuffle-app/_helpers.tpl b/functions/kubernetes/charts/shuffle/templates/shuffle-app/_helpers.tpl
index 5963f9c1..fd6cb6c9 100644
--- a/functions/kubernetes/charts/shuffle/templates/shuffle-app/_helpers.tpl
+++ b/functions/kubernetes/charts/shuffle/templates/shuffle-app/_helpers.tpl
@@ -121,9 +121,15 @@ Usage:
{{/*
Return the environment variables of shuffle apps in the format
KEY: VALUE
+Usage:
+{{- include "shuffle.appInstance.env" (dict "app" $appValues "context" $) -}}
+
+WARNING: Do NOT add environment variables here that would conflict with shuffle.worker.env or shuffle.orborus.env.
+Worker also sets all env variables that are defined here, because they will be passed down to apps when worker.manageAppDeployments is set.
+Instead, add them directly to the deployment template (shuffle-apps.yaml).
*/}}
{{- define "shuffle.appInstance.env" -}}
-SHUFFLE_APP_SDK_TIMEOUT: {{ .Values.app.sdkTimeout | quote }}
-SHUFFLE_APP_EXPOSED_PORT: {{ .Values.app.exposedContainerPort | quote }}
-SHUFFLE_LOGS_DISABLED: {{ .Values.app.disableLogs | quote }}
+SHUFFLE_APP_SDK_TIMEOUT: {{ .app.sdkTimeout | quote }}
+SHUFFLE_APP_EXPOSED_PORT: {{ .app.exposedContainerPort | quote }}
+SHUFFLE_LOGS_DISABLED: {{ .app.disableLogs | quote }}
{{- end -}}
diff --git a/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-apps.yaml b/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-apps.yaml
index af04851f..521e7fe8 100644
--- a/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-apps.yaml
+++ b/functions/kubernetes/charts/shuffle/templates/shuffle-app/shuffle-apps.yaml
@@ -168,7 +168,11 @@ spec:
value: {{ include "shuffle.backend.baseUrl" $ | quote }}
- name: SHUFFLE_SWARM_CONFIG
value: run # Shuffle Worker requires this to be set even when using K8s instead of swarm
- {{- $env := include "shuffle.appInstance.env" $ | fromYaml }}
+ {{- if $appValues.debug }}
+ - name: DEBUG
+ value: "true"
+ {{- end }}
+ {{- $env := include "shuffle.appInstance.env" (dict "app" $appValues "context" $) | fromYaml }}
{{- range $key, $val := $env }}
- name: {{ $key | quote }}
value: {{ $val | quote }}
diff --git a/functions/kubernetes/charts/shuffle/templates/shuffle-worker/_helpers.tpl b/functions/kubernetes/charts/shuffle/templates/shuffle-worker/_helpers.tpl
index 8feafa76..c0e75c72 100644
--- a/functions/kubernetes/charts/shuffle/templates/shuffle-worker/_helpers.tpl
+++ b/functions/kubernetes/charts/shuffle/templates/shuffle-worker/_helpers.tpl
@@ -131,12 +131,13 @@ http://shuffle-workers.{{ .Release.Namespace }}.svc.cluster.local
{{/*
Return the environment variables of shuffle-worker in the format
KEY: VALUE
+
+WARNING: Do NOT add environment variables here that would conflict with shuffle.orborus.env.
+Orborus also sets all env variables that are defined here, because they will be passed down to worker when orborus.manageWorkerDeployments is set.
+Instead, add them directly to the deployment template (shuffle-worker-dpl.yaml).
*/}}
{{- define "shuffle.workerInstance.env" -}}
-IS_KUBERNETES: "true"
-KUBERNETES_NAMESPACE: "{{ .Release.Namespace }}"
SHUFFLE_SWARM_CONFIG: "run" # Shuffle Worker requires this to be set even when using K8s instead of swarm
-BASE_URL: {{ include "shuffle.backend.baseUrl" . | quote }}
SHUFFLE_APP_EXPOSED_PORT: {{ .Values.app.exposedContainerPort | quote }}
WORKER_HOSTNAME: {{ include "shuffle.worker.hostname" . }}
@@ -177,6 +178,6 @@ SHUFFLE_APP_EPHEMERAL_STORAGE_LIMIT: {{ (index $appResources.limits "ephemeral-s
{{- end }}
# Include shuffle app environment variables. Worker passes them down to apps, when creating their deployment.
-{{ include "shuffle.appInstance.env" . }}
+{{ include "shuffle.appInstance.env" (dict "app" .Values.app "context" $) -}}
{{- end }}
{{- end -}}
diff --git a/functions/kubernetes/charts/shuffle/templates/shuffle-worker/shuffle-worker-dpl.yaml b/functions/kubernetes/charts/shuffle/templates/shuffle-worker/shuffle-worker-dpl.yaml
index 1e724ab3..eb1c1682 100644
--- a/functions/kubernetes/charts/shuffle/templates/shuffle-worker/shuffle-worker-dpl.yaml
+++ b/functions/kubernetes/charts/shuffle/templates/shuffle-worker/shuffle-worker-dpl.yaml
@@ -85,6 +85,23 @@ spec:
env:
- name: CLEANUP
value: "false" # Do not remove resources when restarting worker
+ - name: IS_KUBERNETES
+ value: "true"
+ - name: KUBERNETES_NAMESPACE
+ value: {{ .Release.Namespace | quote }}
+ - name: BASE_URL
+ value: {{ include "shuffle.backend.baseUrl" . | quote }}
+ {{- if .Values.worker.debug }}
+ - name: DEBUG
+ value: "true"
+ {{- end }}
+ {{- if .Values.worker.autoGOMEMLIMIT }}
+ {{- $workerResources := (.Values.worker.resources | default (include "common.resources.preset" (dict "type" .Values.worker.resourcesPreset) | fromYaml)) -}}
+ {{- if and $workerResources $workerResources.limits $workerResources.limits.memory }}
+ - name: GOMEMLIMIT
+ value: {{ include "shuffle.k8sMemoryLimitToGOMEMLIMIT" (dict "k8sMemoryLimit" $workerResources.limits.memory) | quote }}
+ {{- end }}
+ {{- end }}
{{- $env := include "shuffle.workerInstance.env" . | fromYaml }}
{{- range $key, $val := $env }}
- name: {{ $key | quote }}
diff --git a/functions/kubernetes/charts/shuffle/values.schema.json b/functions/kubernetes/charts/shuffle/values.schema.json
index ca262866..10ae8ca9 100644
--- a/functions/kubernetes/charts/shuffle/values.schema.json
+++ b/functions/kubernetes/charts/shuffle/values.schema.json
@@ -771,6 +771,16 @@
}
}
},
+ "debug": {
+ "type": "boolean",
+ "description": "Enable debug mode for backend, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments.",
+ "default": false
+ },
+ "autoGOMEMLIMIT": {
+ "type": "boolean",
+ "description": "Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit.",
+ "default": true
+ },
"cleanupSchedule": {
"type": "number",
"description": "The interval in seconds at which the cleanup job runs",
@@ -2059,6 +2069,16 @@
}
}
},
+ "debug": {
+ "type": "boolean",
+ "description": "Enable debug mode for orborus, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments.",
+ "default": false
+ },
+ "autoGOMEMLIMIT": {
+ "type": "boolean",
+ "description": "Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit.",
+ "default": true
+ },
"executionConcurrency": {
"type": "number",
"description": "The maximum amount of concurrent workflow executions per worker",
@@ -2703,6 +2723,16 @@
}
}
},
+ "debug": {
+ "type": "boolean",
+ "description": "Enable debug mode for worker, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments.",
+ "default": false
+ },
+ "autoGOMEMLIMIT": {
+ "type": "boolean",
+ "description": "Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit.",
+ "default": true
+ },
"manageAppDeployments": {
"type": "boolean",
"description": "Whether apps are deployed and managed by worker. When disabled, every used app is expected to to be already deployed (see apps.enabled).",
@@ -3322,6 +3352,11 @@
}
}
},
+ "debug": {
+ "type": "boolean",
+ "description": "Enable debug mode for app, which enables extra logging",
+ "default": false
+ },
"mountTmpVolume": {
"type": "boolean",
"description": "Whether a writable /tmp emptyDir volume should be mounted to the app.",
diff --git a/functions/kubernetes/charts/shuffle/values.yaml b/functions/kubernetes/charts/shuffle/values.yaml
index 590d2c10..87fd3bb0 100644
--- a/functions/kubernetes/charts/shuffle/values.yaml
+++ b/functions/kubernetes/charts/shuffle/values.yaml
@@ -516,6 +516,16 @@ backend:
##
extraEgress: []
+ ## @param backend.debug Enable debug mode for backend, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments.
+ ##
+ debug: false
+
+ ## @param backend.autoGOMEMLIMIT Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit.
+ ## This helps prevent Go runtime memory limits from exceeding container limits, which can cause OOM kills.
+ ## Only effective when backend.resources.limits.memory is set or a resourcesPreset is used that defines memory limits.
+ ##
+ autoGOMEMLIMIT: true
+
## @param backend.cleanupSchedule The interval in seconds at which the cleanup job runs
##
cleanupSchedule: 300
@@ -1329,6 +1339,17 @@ orborus:
##
extraEgress: []
+ ## @param orborus.debug Enable debug mode for orborus, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments.
+ ##
+ debug: false
+
+ ## @param orborus.autoGOMEMLIMIT Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit.
+ ## This helps prevent Go runtime memory limits from exceeding container limits, which can cause OOM kills.
+ ## Only effective when orborus.resources.limits.memory is set or a resourcesPreset is used that defines memory limits.
+ ## Only effective when orborus is deployed via helm.
+ ##
+ autoGOMEMLIMIT: true
+
## @param orborus.executionConcurrency The maximum amount of concurrent workflow executions per worker
##
executionConcurrency: 25
@@ -1739,6 +1760,17 @@ worker:
##
extraEgress: []
+ ## @param worker.debug Enable debug mode for worker, which enables extra logging. orborus.debug, worker.debug, and app.debug should be set to identical values, if shuffle manages these deployments.
+ ##
+ debug: false
+
+ ## @param worker.autoGOMEMLIMIT Automatically set GOMEMLIMIT environment variable to 90% of the container memory limit.
+ ## This helps prevent Go runtime memory limits from exceeding container limits, which can cause OOM kills.
+ ## Only effective when worker.resources.limits.memory is set or a resourcesPreset is used that defines memory limits.
+ ## Only effective when worker is deployed via helm (see worker.enableHelmDeployment).
+ ##
+ autoGOMEMLIMIT: true
+
## @param worker.manageAppDeployments Whether apps are deployed and managed by worker. When disabled, every used app is expected to to be already deployed (see apps.enabled).
## This effectively removes required RBAC permissions from the shuffle-worker service account to create deployments and services.
## The worker might still attempt to create kubernetes objects, resulting in an error. There is currently no way to tell the worker, that it should not manage k8s resources.
@@ -2124,6 +2156,9 @@ app:
##
extraEgress: []
+ ## @param app.debug Enable debug mode for app, which enables extra logging
+ ##
+ debug: false
## @param app.mountTmpVolume Whether a writable /tmp emptyDir volume should be mounted to the app.
##
mountTmpVolume: true
diff --git a/functions/onprem/orborus/Dockerfile b/functions/onprem/orborus/Dockerfile
deleted file mode 100644
index d59aea8d..00000000
--- a/functions/onprem/orborus/Dockerfile
+++ /dev/null
@@ -1,27 +0,0 @@
-FROM golang:1.24 as builder
-
-WORKDIR /app
-
-COPY orborus.go /app/orborus.go
-COPY go.mod /app/go.mod
-
-#RUN go get
-RUN go mod download
-RUN go mod tidy
-#RUN go build -v
-
-# Enabled CGO January 2025 (?)
-#RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o orborus.
-RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app/orborus .
-
-FROM alpine:3.22.1
-
-RUN apk add --no-cache bash tzdata
-#COPY --from=builder /app/orborus orborus
-COPY --from=builder /app/ /
-ENV ENVIRONMENT_NAME=Shuffle \
- BASE_URL=http://shuffle-backend:5001 \
- DOCKER_API_VERSION=1.40 \
- SHUFFLE_OPENSEARCH_URL=https://opensearch:9200
-
-CMD ["./orborus"]
diff --git a/functions/onprem/orborus/build.sh b/functions/onprem/orborus/build.sh
deleted file mode 100755
index bbec5067..00000000
--- a/functions/onprem/orborus/build.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-NAME=shuffle-orborus
-VERSION=1.3.0
-
-echo "Running docker build with $NAME:$VERSION"
-#docker rmi frikky/shuffle:$NAME --force
-docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly -t ghcr.io/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:nightly
-
-#docker push frikky/$NAME:$VERSION
-# docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
-docker push frikky/shuffle:$NAME
-docker push ghcr.io/frikky/$NAME:$VERSION
-docker push ghcr.io/frikky/$NAME:nightly
-
-docker push shuffle/shuffle:$NAME
-docker push ghcr.io/shuffle/$NAME:$VERSION
-docker push ghcr.io/shuffle/$NAME:nightly
diff --git a/functions/onprem/orborus/docker-compose.yml b/functions/onprem/orborus/docker-compose.yml
deleted file mode 100644
index ef6132e2..00000000
--- a/functions/onprem/orborus/docker-compose.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-version: '3'
-services:
- orborus:
- image: ghcr.io/frikky/shuffle-orborus:nightly
- container_name: shuffle-orborus
- hostname: shuffle-orborus
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
- environment:
- - SHUFFLE_APP_SDK_VERSION=nightly
- - SHUFFLE_WORKER_VERSION=nightly
- - ORG_ID=Shuffle
- - ENVIRONMENT_NAME=Shuffle
- - BASE_URL=http://192.168.86.39:5001
- - DOCKER_API_VERSION=1.40
- - SHUFFLE_SCALE_REPLICAS=5
- - SHUFFLE_SWARM_CONFIG=run
- restart: unless-stopped
- networks:
- - shuffle-executions
-networks:
- shuffle-executions:
- driver: overlay
- external: true
-
diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod
deleted file mode 100644
index 59949b3c..00000000
--- a/functions/onprem/orborus/go.mod
+++ /dev/null
@@ -1,161 +0,0 @@
-module orborus
-
-go 1.24.0
-
-toolchain go1.24.4
-
-//replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared
-
-require (
- github.com/docker/docker v28.3.3+incompatible
- github.com/docker/go-connections v0.5.0
- github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.9.90
- k8s.io/api v0.34.2
- k8s.io/apimachinery v0.34.2
-)
-
-require (
- cloud.google.com/go/auth v0.16.1 // indirect
- cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
-)
-
-require (
- cel.dev/expr v0.20.0 // indirect
- cloud.google.com/go v0.121.1 // indirect
- cloud.google.com/go/compute/metadata v0.7.0 // indirect
- cloud.google.com/go/datastore v1.20.0 // indirect
- cloud.google.com/go/iam v1.5.2 // indirect
- cloud.google.com/go/monitoring v1.24.2 // indirect
- cloud.google.com/go/scheduler v1.11.7 // indirect
- cloud.google.com/go/storage v1.55.0 // indirect
- dario.cat/mergo v1.0.0 // indirect
- github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
- github.com/Masterminds/semver v1.5.0 // indirect
- github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/ProtonMail/go-crypto v1.1.6 // indirect
- github.com/adrg/strutil v0.3.1 // indirect
- github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect
- github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
- github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
- github.com/cenkalti/backoff/v5 v5.0.2 // indirect
- github.com/cespare/xxhash/v2 v2.3.0 // indirect
- github.com/cloudflare/circl v1.6.1 // indirect
- github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
- github.com/containerd/errdefs v1.0.0 // indirect
- github.com/containerd/errdefs/pkg v0.3.0 // indirect
- github.com/coreos/go-oidc/v3 v3.17.0 // indirect
- github.com/cyphar/filepath-securejoin v0.4.1 // indirect
- github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
- github.com/distribution/reference v0.6.0 // indirect
- github.com/docker/go-units v0.5.0 // indirect
- github.com/emicklei/go-restful/v3 v3.12.2 // indirect
- github.com/emirpasic/gods v1.18.1 // indirect
- github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
- github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
- github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/frikky/kin-openapi v0.42.0 // indirect
- github.com/frikky/schemaless v0.0.28 // indirect
- github.com/fxamacker/cbor/v2 v2.9.0 // indirect
- github.com/ghodss/yaml v1.0.0 // indirect
- github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
- github.com/go-git/go-billy/v5 v5.6.2 // indirect
- github.com/go-git/go-git/v5 v5.16.5 // indirect
- github.com/go-jose/go-jose/v4 v4.1.3 // indirect
- github.com/go-logr/logr v1.4.2 // indirect
- github.com/go-logr/stdr v1.2.2 // indirect
- github.com/go-openapi/jsonpointer v0.21.0 // indirect
- github.com/go-openapi/jsonreference v0.20.2 // indirect
- github.com/go-openapi/swag v0.23.0 // indirect
- github.com/goccy/go-json v0.10.5 // indirect
- github.com/gogo/protobuf v1.3.2 // indirect
- github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
- github.com/golang/protobuf v1.5.4 // indirect
- github.com/google/gnostic-models v0.7.0 // indirect
- github.com/google/go-github/v28 v28.1.1 // indirect
- github.com/google/go-querystring v1.1.0 // indirect
- github.com/google/s2a-go v0.1.9 // indirect
- github.com/google/uuid v1.6.0 // indirect
- github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
- github.com/googleapis/gax-go/v2 v2.14.2 // indirect
- github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
- github.com/josharian/intern v1.0.0 // indirect
- github.com/json-iterator/go v1.1.12 // indirect
- github.com/kevinburke/ssh_config v1.2.0 // indirect
- github.com/mailru/easyjson v0.7.7 // indirect
- github.com/moby/docker-image-spec v1.3.1 // indirect
- github.com/moby/sys/sequential v0.6.0 // indirect
- github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
- github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
- github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
- github.com/openai/openai-go/v3 v3.8.1 // indirect
- github.com/opencontainers/go-digest v1.0.0 // indirect
- github.com/opencontainers/image-spec v1.1.1 // indirect
- github.com/osteele/liquid v1.7.0 // indirect
- github.com/osteele/tuesday v1.0.3 // indirect
- github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
- github.com/pjbgf/sha1cd v0.3.2 // indirect
- github.com/pkg/errors v0.9.1 // indirect
- github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
- github.com/sashabaranov/go-openai v1.40.5 // indirect
- github.com/sendgrid/rest v2.6.9+incompatible // indirect
- github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
- github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
- github.com/shuffle/opensearch-go/v4 v4.0.0 // indirect
- github.com/skeema/knownhosts v1.3.1 // indirect
- github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
- github.com/spf13/pflag v1.0.6 // indirect
- github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
- github.com/tidwall/gjson v1.18.0 // indirect
- github.com/tidwall/match v1.1.1 // indirect
- github.com/tidwall/pretty v1.2.1 // indirect
- github.com/tidwall/sjson v1.2.5 // indirect
- github.com/x448/float16 v0.8.4 // indirect
- github.com/xanzy/ssh-agent v0.3.3 // indirect
- github.com/zeebo/errs v1.4.0 // indirect
- go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
- go.opentelemetry.io/otel v1.36.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
- go.opentelemetry.io/otel/metric v1.36.0 // indirect
- go.opentelemetry.io/otel/sdk v1.36.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
- go.opentelemetry.io/otel/trace v1.36.0 // indirect
- go.opentelemetry.io/proto/otlp v1.5.0 // indirect
- go.yaml.in/yaml/v2 v2.4.2 // indirect
- go.yaml.in/yaml/v3 v3.0.4 // indirect
- go4.org v0.0.0-20230225012048-214862532bf5 // indirect
- golang.org/x/crypto v0.45.0 // indirect
- golang.org/x/net v0.47.0 // indirect
- golang.org/x/oauth2 v0.30.0 // indirect
- golang.org/x/sync v0.18.0 // indirect
- golang.org/x/sys v0.38.0 // indirect
- golang.org/x/term v0.37.0 // indirect
- golang.org/x/text v0.31.0 // indirect
- golang.org/x/time v0.11.0 // indirect
- google.golang.org/api v0.236.0 // indirect
- google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
- google.golang.org/grpc v1.72.2 // indirect
- google.golang.org/protobuf v1.36.6 // indirect
- gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
- gopkg.in/inf.v0 v0.9.1 // indirect
- gopkg.in/warnings.v0 v0.1.2 // indirect
- gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
- k8s.io/client-go v0.34.2 // indirect
- k8s.io/klog/v2 v2.130.1 // indirect
- k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
- k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
- sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
- sigs.k8s.io/randfill v1.0.0 // indirect
- sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
- sigs.k8s.io/yaml v1.6.0 // indirect
-)
diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum
deleted file mode 100644
index 5f00a945..00000000
--- a/functions/onprem/orborus/go.sum
+++ /dev/null
@@ -1,653 +0,0 @@
-cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
-cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
-cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
-cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
-cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
-cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
-cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
-cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM=
-cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew=
-cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
-cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
-cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
-cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
-cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
-cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
-cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
-cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/scheduler v1.11.7 h1:zkMEJ0UbEJ3O7NwEUlKLIp6eXYv1L7wHjbxyxznajKM=
-cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
-cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
-cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
-cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
-dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
-dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
-github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
-github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
-github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
-github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
-github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
-github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
-github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
-github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk=
-github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
-github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
-github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y=
-github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w=
-github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
-github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-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=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
-github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
-github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
-github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
-github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
-github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
-github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
-github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
-github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
-github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
-github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
-github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
-github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
-github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
-github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
-github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
-github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
-github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
-github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
-github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
-github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
-github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
-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.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
-github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
-github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
-github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
-github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0=
-github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4=
-github.com/frikky/schemaless v0.0.28 h1:gdurMqBwtvY4Y/5pcxn8bdGCJn/eolKGz+c5DcidLkI=
-github.com/frikky/schemaless v0.0.28/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
-github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
-github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
-github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
-github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
-github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
-github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
-github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
-github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
-github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
-github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
-github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
-github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
-github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
-github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
-github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
-github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
-github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
-github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
-github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
-github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
-github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
-github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
-github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
-github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
-github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
-github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
-github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
-github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
-github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
-github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
-github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
-github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
-github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
-github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
-github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
-github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
-github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
-github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
-github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
-github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
-github.com/openai/openai-go/v3 v3.8.1 h1:b+YWsmwqXnbpSHWQEntZAkKciBZ5CJXwL68j+l59UDg=
-github.com/openai/openai-go/v3 v3.8.1/go.mod h1:UOpNxkqC9OdNXNUfpNByKOtB4jAL0EssQXq5p8gO0Xs=
-github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
-github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
-github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
-github.com/osteele/liquid v1.7.0 h1:VsbPSchE5D5S5scylAIvERET4dnCxsO6IDri2oSJ5Dk=
-github.com/osteele/liquid v1.7.0/go.mod h1:xU0Z2dn2hOQIEFEWNmeltOmCtfhtoW/2fCyiNQeNG+U=
-github.com/osteele/tuesday v1.0.3 h1:SrCmo6sWwSgnvs1bivmXLvD7Ko9+aJvvkmDjB5G4FTU=
-github.com/osteele/tuesday v1.0.3/go.mod h1:pREKpE+L03UFuR+hiznj3q7j3qB1rUZ4XfKejwWFF2M=
-github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
-github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
-github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
-github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
-github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
-github.com/sashabaranov/go-openai v1.40.5 h1:SwIlNdWflzR1Rxd1gv3pUg6pwPc6cQ2uMoHs8ai+/NY=
-github.com/sashabaranov/go-openai v1.40.5/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
-github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
-github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
-github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
-github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
-github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
-github.com/shuffle/opensearch-go/v4 v4.0.0 h1:Mh85CD1MwOgXiFFYlzS1llnvdqL3CztRdR1ZT/SLIjU=
-github.com/shuffle/opensearch-go/v4 v4.0.0/go.mod h1:gVLZKQE5khQWMb68XBtgKrhu78oLGL2zHwAGnFMDwC0=
-github.com/shuffle/shuffle-shared v0.9.87 h1:INA1cZ18MKcMs8kaVBJoQqrdNuqLHfge9elJ17hZfVc=
-github.com/shuffle/shuffle-shared v0.9.87/go.mod h1:AkXajlWWB16WfWjCw9K7y38L8JKABJzVxcX6KI/J1H4=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
-github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
-github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
-github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
-github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
-github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
-github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
-github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
-github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
-github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
-github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
-github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
-github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
-github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
-github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ=
-github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM=
-github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
-github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
-github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
-github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
-github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
-go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
-go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
-go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
-go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
-go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
-go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
-go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
-go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
-go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
-go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
-go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
-go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
-go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
-go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
-go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
-go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
-go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
-go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
-golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
-golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
-golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0=
-google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
-google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
-google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
-google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
-google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
-gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
-gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
-gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
-gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
-k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
-k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
-k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
-k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
-k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
-k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
-k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
-k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
-k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
-k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
-k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
-sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
-sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
-sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
-sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
-sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
-sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go
deleted file mode 100755
index 99639e72..00000000
--- a/functions/onprem/orborus/orborus.go
+++ /dev/null
@@ -1,4791 +0,0 @@
-package main
-
-/*
- Orborus exists to listen for new jobs from Shuffle. This is to run workflows, pipelines, and other tasks.
-*/
-import (
- "archive/zip"
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "math"
- "net"
- "net/http"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
-
- "github.com/shuffle/shuffle-shared"
-
- "math/rand"
- //"os/signal"
- //"syscall"
-
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/api/types/filters"
- "github.com/docker/docker/api/types/image"
- "github.com/docker/docker/api/types/mount"
- "github.com/docker/docker/api/types/network"
- "github.com/docker/docker/api/types/swarm"
- "github.com/docker/go-connections/nat"
-
- //"github.com/docker/docker/api/types/filters"
- dockerclient "github.com/docker/docker/client"
- uuid "github.com/satori/go.uuid"
-
- //"github.com/mackerelio/go-osstat/disk"
- //"github.com/mackerelio/go-osstat/memory"
- //"github.com/shirou/gopsutil/cpu"
-
- appsv1 "k8s.io/api/apps/v1"
- corev1 "k8s.io/api/core/v1"
- rbacv1 "k8s.io/api/rbac/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/intstr"
-)
-
-// Starts jobs in bulk, so this could be increased or decreased based on who the user is
-var sleepTime = 2
-
-// Making it work on low-end machines even during busy times :)
-// May cause some things to run slowly
-var maxConcurrency = 25
-
-// Timeout if something rashes
-var workerTimeoutEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_TIMEOUT")
-var concurrencyEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY")
-var appSdkVersion = os.Getenv("SHUFFLE_APP_SDK_VERSION")
-var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION")
-var newWorkerImage = os.Getenv("SHUFFLE_WORKER_IMAGE")
-var dockerSwarmBridgeMTU = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU")
-var dockerSwarmBridgeInterface = os.Getenv("SHUFFLE_SWARM_BRIDGE_DEFAULT_INTERFACE")
-var maxCPUPercent = 90
-
-// Kubernetes settings
-var isKubernetes = os.Getenv("IS_KUBERNETES")
-var kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE")
-var workerServiceAccountName = os.Getenv("SHUFFLE_WORKER_SERVICE_ACCOUNT_NAME")
-var workerPodSecurityContext = os.Getenv("SHUFFLE_WORKER_POD_SECURITY_CONTEXT")
-var workerContainerSecurityContext = os.Getenv("SHUFFLE_WORKER_CONTAINER_SECURITY_CONTEXT")
-var appServiceAccountName = os.Getenv("SHUFFLE_APP_SERVICE_ACCOUNT_NAME")
-var appPodSecurityContext = os.Getenv("SHUFFLE_APP_POD_SECURITY_CONTEXT")
-var appContainerSecurityContext = os.Getenv("SHUFFLE_APP_CONTAINER_SECURITY_CONTEXT")
-var debug = os.Getenv("DEBUG") == "true"
-
-// var baseimagename = "docker.pkg.github.com/shuffle/shuffle"
-// var baseimagename = "ghcr.io/frikky"
-// var baseimagename = "shuffle/shuffle"
-var baseimageregistry = os.Getenv("SHUFFLE_BASE_IMAGE_REGISTRY")
-var baseimagename = os.Getenv("SHUFFLE_BASE_IMAGE_NAME")
-
-//var baseimagetagsuffix = os.Getenv("SHUFFLE_BASE_IMAGE_TAG_SUFFIX")
-
-// Used for cloud with auth. Onprem in certain cases too.
-var auth = os.Getenv("AUTH")
-var org = os.Getenv("ORG")
-
-// var orgId = os.Getenv("ORG_ID")
-var baseUrl = os.Getenv("BASE_URL")
-var workerServerUrl = os.Getenv("SHUFFLE_WORKER_SERVER_URL")
-var environment = os.Getenv("ENVIRONMENT_NAME")
-var dockerApiVersion = os.Getenv("DOCKER_API_VERSION")
-var runningMode = strings.ToLower(os.Getenv("RUNNING_MODE"))
-var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP"))
-var timezone = os.Getenv("TZ")
-var containerName = os.Getenv("ORBORUS_CONTAINER_NAME")
-var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG")
-var swarmNetworkName = os.Getenv("SHUFFLE_SWARM_NETWORK_NAME")
-var orborusLabel = os.Getenv("SHUFFLE_ORBORUS_LABEL")
-var memcached = os.Getenv("SHUFFLE_MEMCACHED")
-var queuePerMinute = os.Getenv("SHUFFLE_EXECUTION_PER_MINIUTE")
-var queuePerMinuteInt int
-
-// Used to download file categories. Not required since 2.1.1
-var pipelineApikey = ""
-var pipelineUrl = os.Getenv("SHUFFLE_PIPELINE_URL")
-
-var executionIds = []string{}
-var pipelines = []shuffle.PipelineInfo{}
-var namespacemade = false // For K8s
-var skipPipelineMount = false
-var tenzirDisabled = false
-
-var dockercli *dockerclient.Client
-var containerId string
-var executionCount = 0
-var orborusUuid = os.Getenv("SHUFFLE_ORBORUS_UUID")
-
-var imagedownloadTimeout = time.Second * 300
-var window = shuffle.NewTimeWindow(1 * time.Minute)
-
-func init() {
- var err error
-
- // dockercli, err = dockerclient.NewEnvClient()
- dockercli, dockerApiVersion, err = shuffle.GetDockerClient()
- if err != nil {
- log.Printf("Unable to create docker client: %s", err)
- }
-
- if os.Getenv("SHUFFLE_EC2_INSTANCE") == "true" {
- log.Printf("[INFO] Detected AWS EC2 instance. Setting up Docker Swarm with AWS optimizations.")
- containers, err := dockercli.ContainerList(context.Background(), container.ListOptions{})
- if err == nil {
- for _, container := range containers {
- if strings.Contains(container.Image, "shuffle-orborus") {
- if len(container.Names) != 0 {
- if strings.Contains(container.Names[0], "shuffle-orborus") {
- containerName = container.Names[0]
- containerName = strings.TrimPrefix(containerName, "/")
- os.Setenv("ORBORUS_CONTAINER_NAME", containerName)
- log.Printf("[DEBUG] Found orborus container name: %s", containerName)
- break
- }
- }
- }
- }
- } else {
- log.Printf("[ERROR] Failed to find orborus container: %s", err)
- }
- }
-
- getThisContainerId()
-
- if len(pipelineApikey) == 0 {
- if len(os.Getenv("SHUFFLE_AUTHORIZATION")) > 0 {
- log.Printf("[DEBUG] No pipeline API key found. Overriding with api key from SHUFFLE_AUTHORIZATION")
-
- pipelineApikey = os.Getenv("SHUFFLE_AUTHORIZATION")
- os.Setenv("SHUFFLE_PIPELINE_AUTH", pipelineApikey)
- }
- }
-
-}
-
-// form id of current running container
-func getThisContainerId() {
- fCol := ""
-
- // some adjusting based on current running mode
- switch runningMode {
- case "kubernetes":
- // cgroup will be like:
- // 11:net_cls,net_prio:/kubepods/besteffort/podf132b44d-cfcf-43f7-9906-79f58e268333/851466f8b5ed5aa0f265b1c95c6d2bafbc51a38dd5c5a1621b6e586572150009
- fCol = "5"
- log.Printf("[INFO] Running containerized in Kubernetes!")
-
- case "docker":
- // cgroup will be like:
- // 12:perf_event:/docker/0f06810364f52a2cd6e80bfba27419cb8a29758a204cd676388f4913bb366f2b
- fCol = "3"
- log.Printf("[INFO] Running containerized in Docker!")
-
- default:
- fCol = "3" // for backward-compatibility with production
- log.Printf("[WARNING] RUNNING_MODE not set - defaulting to Docker (NOT Kubernetes).")
- }
-
- if fCol != "" {
- cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f%s | grep -o -E '[0-9A-z]{64}'", fCol)
- out, err := exec.Command("bash", "-c", cmd).Output()
- if err == nil {
- containerId = strings.TrimSpace(string(out))
- log.Printf("[DEBUG] Set containerId network to %s", containerId)
-
- // cgroup error. Use fallback strategy below.
- // https://github.com/moby/moby/issues/7015
- //log.Printf("Checking if %s is in %s", ".scope", string(out))
- if strings.Contains(string(out), ".scope") {
- log.Printf("[DEBUG] ContainerId contains scope. setting to empty.")
- containerId = ""
- //docker-76c537e9a4b7c7233011f5d70e6b7f2d600b6413ac58a96519b8dca7a3f7117a.scope
- }
- } else {
- log.Printf("[WARNING] Failed getting container ID: %s", err)
- }
- }
-
- if containerId == "" {
- if containerName != "" {
- containerId = containerName
- log.Printf("[INFO] Falling back to ORBORUS_CONTAINER_NAME as container ID")
- } else {
- containerId = "shuffle-orborus"
- log.Printf(`[WARNING] ORBORUS_CONTAINER_NAME env is not set. Falling back to default name "%s" as container ID. This may cause issues on the same server`, containerId)
- }
- }
-
- log.Printf(`[INFO] Started with containerId "%s"`, containerId)
-}
-
-func skipCheckInCleanup(name string) bool {
- return strings.HasPrefix(name, "backend") ||
- strings.HasPrefix(name, "shuffle-backend") ||
- strings.HasPrefix(name, "frontend") ||
- strings.HasPrefix(name, "shuffle-frontend") ||
- strings.HasPrefix(name, "orborus") ||
- strings.HasPrefix(name, "shuffle-orborus") ||
- strings.HasPrefix(name, "opensearch") ||
- strings.HasPrefix(name, "shuffle-opensearch") ||
- strings.HasPrefix(name, "memcached") ||
- strings.HasPrefix(name, "shuffle-memcached")
-}
-
-func cleanupExistingNodes(ctx context.Context) error {
- if cleanupEnv != "true" {
- log.Printf("[INFO] Skipping cleanup of existing workers as CLEANUP is NOT set to true. Swarm actions are being auto-discovered during executions then instead.")
- return nil
- }
-
- if isKubernetes == "true" {
- // Cleanup all workers created by orborus and all apps created by workers.
-
- if kubernetesNamespace == "" {
- kubernetesNamespace = "default"
- }
-
- clientset, _, err := shuffle.GetKubernetesClient()
- if err != nil {
- log.Printf("[ERROR] Error getting kubernetes client:", err)
- return err
- }
-
- // Delete all services
- services, err := clientset.CoreV1().Services(kubernetesNamespace).List(context.Background(), metav1.ListOptions{
- LabelSelector: "app.kubernetes.io/name in (shuffle-worker, shuffle-app),app.kubernetes.io/managed-by in (shuffle-orborus, shuffle-worker)",
- })
- if err != nil {
- log.Printf("[ERROR] Failed listing services: %s", err)
- return err
- }
-
- for _, service := range services.Items {
- err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{})
- if err != nil {
- log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err)
- }
- }
-
- deployments, err := clientset.AppsV1().Deployments(kubernetesNamespace).List(context.Background(), metav1.ListOptions{
- LabelSelector: "app.kubernetes.io/name in (shuffle-worker, shuffle-app),app.kubernetes.io/managed-by in (shuffle-orborus, shuffle-worker)",
- })
- if err != nil {
- log.Printf("[ERROR] Failed listing deployments: %s", err)
- return err
- }
-
- for _, deployment := range deployments.Items {
- err := clientset.AppsV1().Deployments(kubernetesNamespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{})
- if err != nil {
- log.Printf("[ERROR] Failed deleting deployment %s: %s", deployment.Name, err)
- }
- }
-
- log.Printf("[INFO] Cleaned up all services and deployments in namespace %s. Waiting 10 seconds for cleanup to reflect", kubernetesNamespace)
-
- time.Sleep(10 * time.Second)
-
- return nil
- }
-
- serviceListOptions := types.ServiceListOptions{}
- services, err := dockercli.ServiceList(
- context.Background(),
- serviceListOptions,
- )
-
- if err != nil {
- log.Printf("[DEBUG] Failed finding containers: %s", err)
- return err
- }
-
- //log.Printf("\n\nFound %d contaienrs", len(services))
-
- for _, service := range services {
-
- //portFound := false
- //for _, endpoint := range service.Spec.EndpointSpec.Ports {
- // if strings.Contains(endpoint.Name, "port") {
- // //portFound = true
- // }
- //}
-
- if strings.Contains(service.Spec.Annotations.Name, "opensearch") {
- continue
- }
-
- if strings.Contains(service.Spec.TaskTemplate.ContainerSpec.Image, "shuffle") {
-
- if !strings.Contains(service.Spec.TaskTemplate.ContainerSpec.Image, "shuffle-frontend") &&
- !strings.Contains(service.Spec.TaskTemplate.ContainerSpec.Image, "shuffle-backend") &&
- !strings.Contains(service.Spec.TaskTemplate.ContainerSpec.Image, "shuffle-orborus") {
-
- err = dockercli.ServiceRemove(ctx, service.ID)
- if err != nil {
- log.Printf("[DEBUG] Failed to remove service %s", service.Spec.Annotations.Name)
- } else {
- log.Printf("[DEBUG] Removed service %#v", service.Spec.TaskTemplate.ContainerSpec.Image)
- }
- }
- }
- }
-
- return nil
-}
-
-func deployServiceWorkers(image string) {
- log.Printf("[DEBUG] Validating deployment of workers as services IF swarmConfig = run (value: %#v)", swarmConfig)
- if swarmConfig != "run" && swarmConfig != "swarm" {
- log.Printf("[DEBUG] Skipping deployment of workers as services as swarmConfig is not set to run or swarm. Value: %#v", swarmConfig)
- return
- }
- ctx := context.Background()
-
- // Looks for and cleans up all existing items in swarm we can't re-use (Shuffle only)
-
- // frikky@debian:~/git/shuffle/functions/onprem/worker$ docker service create --replicas 5 --name shuffle-workers --env SHUFFLE_SWARM_CONFIG=run --publish published=33333,target=33333 ghcr.io/shuffle/shuffle-worker:nightly
-
- // Get a list of network interfaces
- interfaces, err := net.Interfaces()
- if err != nil {
- log.Printf("[ERROR] Failed to get network interfaces: %s", err)
- }
-
- mtu := 1500
- if len(dockerSwarmBridgeMTU) == 0 {
- mtu, err = strconv.Atoi(dockerSwarmBridgeMTU) // by default
- if err != nil {
- if debug {
- log.Printf("[DEBUG] Failed to convert the default MTU to int: %s. Using 1500 instead. Input: %s", err, dockerSwarmBridgeMTU)
- }
-
- mtu = 1500
- }
- }
-
- bridgeName := dockerSwarmBridgeInterface
- if bridgeName == "" {
- bridgeName = "eth0"
- }
-
- // Check if there is at least one interface
- if len(interfaces) < 2 {
- // this assumes that the machine should have at least 2 network
- // interfaces. If not, we will use the default MTU.
- // interface 1 is the loopback interface
- // interface 2 is eth0, The eth0 interface inside a
- // Docker container corresponds to the virtual Ethernet
- // interface that connects the container to the docker0
- log.Printf("[ERROR] Failed to get enough network interfaces")
- } else {
- // Get the preferred interface
- for _, iface := range interfaces {
- if strings.Contains(iface.Name, bridgeName) {
- targetInterface := iface
- mtu = targetInterface.MTU
- log.Printf("[INFO] Using MTU %d from interface %s", mtu, targetInterface.Name)
- break
- }
- }
- }
-
- // Create the network options with the specified MTU
- options := make(map[string]string)
- options["com.docker.network.driver.mtu"] = fmt.Sprintf("%d", mtu)
-
- ingressOptions := network.CreateOptions{
- Driver: "overlay",
- Attachable: false,
- Ingress: true,
- IPAM: &network.IPAM{
- Driver: "default",
- Config: []network.IPAMConfig{
- network.IPAMConfig{
- Subnet: "10.225.225.0/24",
- Gateway: "10.225.225.1",
- },
- },
- },
- }
-
- _, err = dockercli.NetworkCreate(
- ctx,
- "ingress",
- ingressOptions,
- )
-
- if err != nil {
- log.Printf("[WARNING] Ingress network may already exist: %s", err)
- }
-
- //docker network create --driver=overlay workers
- // Specific subnet?
- networkName := "shuffle_swarm_executions"
- if len(swarmNetworkName) > 0 {
- networkName = swarmNetworkName
- }
-
- networkCreateOptions := network.CreateOptions{
- Driver: "overlay",
- Options: options,
- Attachable: true,
- Ingress: false,
- IPAM: &network.IPAM{
- Driver: "default",
- Config: []network.IPAMConfig{
- network.IPAMConfig{
- Subnet: "10.224.224.0/24",
- Gateway: "10.224.224.1",
- },
- },
- },
- }
- _, err = dockercli.NetworkCreate(
- ctx,
- networkName,
- networkCreateOptions,
- )
-
- if err != nil {
- if strings.Contains(fmt.Sprintf("%s", err), "already exists") {
- // Try patching for attachable
- if debug {
- log.Printf("[DEBUG] Network %s already exists", networkName)
- }
- } else {
- log.Printf("[DEBUG] Failed to create network %s for workers: %s. This is not critical, and containers will still be added", networkName, err)
- }
- }
-
- networkID := ""
-
- // find network ID
- networks, err := dockercli.NetworkList(ctx, network.ListOptions{})
- if err == nil {
- for _, net := range networks {
- if net.Name == networkName {
- if net.Scope == "swarm" {
- log.Printf("[DEBUG] Found swarm-scoped network: %s (%s)", networkName, net.ID)
- networkID = net.ID
- } else {
- log.Printf("[WARNING] Network %s exists but is not swarm scoped (scope=%s)", networkName, net.Scope)
- }
- break
- }
- }
- }
-
- /*
- isMemcachedRunning, err := checkMemcached(ctx, dockercli)
- if err != nil {
- log.Printf("[ERROR] Failed checking memcached: %s", err)
- }
- if isMemcachedRunning == false {
- log.Printf("[ERROR] Memcached is not running. Will try to deploy it.")
- deployMemcached(dockercli)
- }
-
- ip := "shuffle-cache"
- if len(os.Getenv("SHUFFLE_MEMCACHED")) == 0 {
- os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip))
- }
- */
-
- if networkID == "" {
- log.Printf("[ERROR] Network %s does not exist", networkName)
- networkID = networkName
- }
-
- defaultNetworkAttach := false
- if containerId != "" {
- log.Printf("[DEBUG] Should connect orborus container to worker network as it's running in Docker with name %#v!", containerId)
- // https://pkg.go.dev/github.com/docker/docker@v20.10.12+incompatible/api/types/network#EndpointSettings
- networkConfig := &network.EndpointSettings{}
- err := dockercli.NetworkConnect(ctx, networkID, containerId, networkConfig)
- if err != nil {
- log.Printf("[ERROR] Failed connecting Orborus to docker network %s: %s", networkName, err)
- }
-
- if len(containerId) == 64 && baseUrl == "http://shuffle-backend:5001" {
- log.Printf("[WARNING] Network MAY not work due to backend being %s and container length 64. Will try to attach shuffle_shuffle network", baseUrl)
- defaultNetworkAttach = true
- }
- }
-
- if len(os.Getenv("DOCKER_HOST")) > 0 {
- log.Printf("[DEBUG] Deploying docker socket proxy to the network %s as the DOCKER_HOST variable is set", networkName)
-
- listOptions := container.ListOptions{
- All: true,
- }
- containers, err := dockercli.ContainerList(ctx, listOptions)
-
- if err == nil {
- for _, container := range containers {
- if strings.Contains(strings.ToLower(container.Image), "docker-socket-proxy") {
- networkConfig := &network.EndpointSettings{}
- err := dockercli.NetworkConnect(ctx, networkID, container.ID, networkConfig)
- if err != nil {
- log.Printf("[ERROR] Failed connecting Docker socket proxy to docker network %s: %s", networkName, err)
- } else {
- log.Printf("[INFO] Attached the docker socket proxy to the execution network")
- }
-
- break
- }
- }
- } else {
- log.Printf("[ERROR] Failed listing containers when deploying socket proxy on swarm: %s", err)
- }
- //} else {
- // log.Printf("[ERROR] Failed listing and finding the right image for docker socket proxy: %s", err)
- //}
- }
-
- // Running 2 by default instead of 1. Higher scale mechanisms - es
- replicas := uint64(1)
- scaleReplicas := os.Getenv("SHUFFLE_SCALE_REPLICAS")
- if len(scaleReplicas) > 0 {
- tmpInt, err := strconv.Atoi(scaleReplicas)
- if err != nil {
- log.Printf("[ERROR] %s is not a valid number for replication", scaleReplicas)
- } else {
- replicas = uint64(tmpInt)
- }
-
- log.Printf("[DEBUG] SHUFFLE_SCALE_REPLICAS set to value %#v. Trying to overwrite default (%d/node)", scaleReplicas, replicas)
- }
-
- innerContainerName := fmt.Sprintf("shuffle-workers")
- cnt, err := findActiveSwarmNodes()
- if err != nil {
- log.Printf("[ERROR] Failed to find active swarm nodes: %s. Defaulting to 1", err)
- }
-
- nodeCount := uint64(1)
- if cnt > 0 {
- nodeCount = uint64(cnt)
- }
-
- appReplicas := os.Getenv("SHUFFLE_APP_REPLICAS")
- appReplicaCnt := 2
- if len(appReplicas) > 0 {
- newCnt, err := strconv.Atoi(appReplicas)
- if err != nil {
- log.Printf("[ERROR] %s is not a valid number for SHUFFLE_APP_REPLICAS", appReplicas)
- } else {
- appReplicaCnt = newCnt
- }
- }
-
- log.Printf("[DEBUG] Found %d node(s) to replicate over. Defaulting to 1 IF we can't auto-discover them.", cnt)
-
- // FIXME: From September 2025 - This is set back to 1, as this doesn't really reflect how scale works at all. It is just confusing, and makes number larger/smaller "arbitrarily" instead of using default docker scale
- nodeCount = 1
- replicatedJobs := uint64(replicas * nodeCount)
-
- log.Printf("[DEBUG] Deploying %d container(s) for worker with swarm to each node. Service name: %s. Image: %s", replicas, innerContainerName, image)
-
- if timezone == "" {
- timezone = "Europe/Amsterdam"
- }
-
- // FIXME: May not need ingress ports. Could use internal services and DNS of swarm itself
- // https://github.com/moby/moby/blob/e2f740de442bac52b280bc485a3ca5b31567d938/api/types/swarm/service.go#L46
- serviceSpec := swarm.ServiceSpec{
- Annotations: swarm.Annotations{
- Name: innerContainerName,
- Labels: map[string]string{},
- },
- Mode: swarm.ServiceMode{
- Replicated: &swarm.ReplicatedService{
- Replicas: &replicatedJobs,
- },
- },
- Networks: []swarm.NetworkAttachmentConfig{
- swarm.NetworkAttachmentConfig{
- Target: networkID,
- },
- swarm.NetworkAttachmentConfig{
- Target: "ingress",
- },
- },
- EndpointSpec: &swarm.EndpointSpec{
- Mode: "vip",
- Ports: []swarm.PortConfig{
- swarm.PortConfig{
- Protocol: swarm.PortConfigProtocolTCP,
- PublishMode: swarm.PortConfigPublishModeIngress,
- Name: "worker-port",
- PublishedPort: 33333,
- TargetPort: 33333,
- },
- },
- },
- TaskTemplate: swarm.TaskSpec{
- Resources: &swarm.ResourceRequirements{
- Reservations: &swarm.Resources{},
- },
- LogDriver: &swarm.Driver{
- Name: "json-file",
- Options: map[string]string{
- "max-size": "10m",
- },
- },
- ContainerSpec: &swarm.ContainerSpec{
- Image: image,
- Env: []string{
- fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", os.Getenv("SHUFFLE_SWARM_CONFIG")),
- fmt.Sprintf("SHUFFLE_SWARM_NETWORK_NAME=%s", networkName),
- fmt.Sprintf("SHUFFLE_APP_REPLICAS=%d", appReplicaCnt),
- fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")),
- fmt.Sprintf("DEBUG_MEMORY=%s", os.Getenv("DEBUG_MEMORY")),
- fmt.Sprintf("SHUFFLE_APP_SDK_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")),
- fmt.Sprintf("SHUFFLE_MAX_SWARM_NODES=%s", os.Getenv("SHUFFLE_MAX_SWARM_NODES")),
- fmt.Sprintf("SHUFFLE_BASE_IMAGE_NAME=%s", os.Getenv("SHUFFLE_BASE_IMAGE_NAME")),
- fmt.Sprintf("SHUFFLE_APP_REQUEST_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_REQUEST_TIMEOUT")),
- },
- //Hosts: []string{
- // innerContainerName,
- //},
- },
- RestartPolicy: &swarm.RestartPolicy{
- Condition: swarm.RestartPolicyConditionOnFailure,
- },
- Placement: &swarm.Placement{
- Constraints: []string{},
- },
- },
- }
-
- if defaultNetworkAttach == true || strings.ToLower(os.Getenv("SHUFFLE_DEFAULT_NETWORK_ATTACH")) == "true" {
- targetName := "shuffle_shuffle"
- isAttachable := false
- networks, err := dockercli.NetworkList(ctx, network.ListOptions{})
- if err == nil {
- for _, net := range networks {
- if net.Name == targetName {
- if net.Scope == "swarm" {
- log.Printf("[DEBUG] Found swarm-scoped network: %s", targetName)
- isAttachable = true
- } else {
- log.Printf("[WARNING] Network %s exist but is not swarm scoped (scope=%s)", targetName, net.Scope)
- }
- break
- }
- }
- }
-
- if isAttachable {
- log.Printf("[DEBUG] Adding network attach for network %s to worker in swarm", targetName)
- serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{
- Target: targetName,
- })
-
- // FIXM: Remove this if deployment fails?
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=%s", targetName))
- }
- }
-
- if dockerApiVersion != "" {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion))
- }
-
- if len(os.Getenv("SHUFFLE_SCALE_REPLICAS")) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SCALE_REPLICAS=%s", os.Getenv("SHUFFLE_SCALE_REPLICAS")))
- }
-
- if len(os.Getenv("SHUFFLE_MEMCACHED")) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_MEMCACHED=%s", os.Getenv("SHUFFLE_MEMCACHED")))
- }
-
- if strings.ToLower(os.Getenv("SHUFFLE_PASS_WORKER_PROXY")) == "true" {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY")))
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY")))
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY")))
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("no_proxy=%s", os.Getenv("no_proxy")))
- }
-
- if len(workerServerUrl) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_WORKER_SERVER_URL=%s", os.Getenv("SHUFFLE_WORKER_SERVER_URL")))
- }
-
- // Handles backend
- if len(os.Getenv("BASE_URL")) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("BASE_URL=%s", os.Getenv("BASE_URL")))
- }
-
- if len(os.Getenv("SHUFFLE_CLOUDRUN_URL")) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_CLOUDRUN_URL=%s", os.Getenv("SHUFFLE_CLOUDRUN_URL")))
- }
-
- if len(os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD")) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_AUTO_IMAGE_DOWNLOAD=%s", os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD")))
- }
-
- if len(os.Getenv("DOCKER_HOST")) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("DOCKER_HOST=%s", os.Getenv("DOCKER_HOST")))
- } else {
- if runtime.GOOS == "windows" {
- serviceSpec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{
- mount.Mount{
- Source: `\\.\pipe\docker_engine`,
- Target: `\\.\pipe\docker_engine`,
- Type: mount.TypeBind,
- },
- }
- } else {
- serviceSpec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{
- mount.Mount{
- Source: "/var/run/docker.sock",
- Target: "/var/run/docker.sock",
- Type: mount.TypeBind,
- },
- }
-
- }
- }
-
- // Look for SHUFFLE_VOLUME_BINDS
- if len(os.Getenv("SHUFFLE_VOLUME_BINDS")) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_VOLUME_BINDS=%s", os.Getenv("SHUFFLE_VOLUME_BINDS")))
- }
-
- overrideHttpProxy := os.Getenv("SHUFFLE_INTERNAL_HTTP_PROXY")
- overrideHttpsProxy := os.Getenv("SHUFFLE_INTERNAL_HTTPS_PROXY")
- if len(overrideHttpProxy) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_INTERNAL_HTTP_PROXY=%s", overrideHttpProxy))
- }
-
- if len(overrideHttpsProxy) > 0 {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_INTERNAL_HTTPS_PROXY=%s", overrideHttpsProxy))
- }
-
- serviceOptions := types.ServiceCreateOptions{}
- _, err = dockercli.ServiceCreate(
- ctx,
- serviceSpec,
- serviceOptions,
- )
-
- // Force deploy if it's not disabled
- deployTenzirNode()
-
- if err == nil {
- log.Printf("[DEBUG] Successfully deployed workers with %d replica(s) on %d node(s)", replicas, cnt)
- // wait for service to be ready
- time.Sleep(time.Duration(rand.Intn(4)+1) * time.Second)
-
- //log.Printf("[DEBUG] Servicecreate request: %#v %#v", service, err)
- // patch service network
- // this is an edgecase that we noticed on docker version 29
- // and API version 1.44
- services, serr := dockercli.ServiceList(ctx, types.ServiceListOptions{})
- if serr == nil {
- for _, svc := range services {
- if svc.Spec.Annotations.Name == innerContainerName {
- log.Printf("[DEBUG] Found service %s (%s) — patching network attach", innerContainerName, svc.ID)
-
- spec := svc.Spec
- spec.TaskTemplate.Networks = append(spec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{
- Target: networkID,
- })
-
- _, uerr := dockercli.ServiceUpdate(ctx, svc.ID, svc.Version, spec, types.ServiceUpdateOptions{})
- if uerr != nil {
- log.Printf("[WARNING] Failed to patch service %s with network %s: %v", innerContainerName, networkID, uerr)
- } else {
- log.Printf("[INFO] Successfully attached network %s to service %s", networkID, innerContainerName)
- }
- break
- }
- }
- } else {
- log.Printf("[WARNING] Failed to list services for patching network attach: %v", serr)
- }
- } else {
- if !strings.Contains(fmt.Sprintf("%s", err), "Already Exists") && !strings.Contains(fmt.Sprintf("%s", err), "is already in use by service") {
- log.Printf("[ERROR] Failed making service: %s", err)
- if strings.Contains(fmt.Sprintf("%s", err), "networks scoped to the swarm can be used") {
- log.Printf("[WARNING] Swarm network attachment failed, retrying without shuffle_shuffle")
-
- var updatedNetworks []swarm.NetworkAttachmentConfig
- for _, net := range serviceSpec.Networks {
- if net.Target != "shuffle_shuffle" {
- updatedNetworks = append(updatedNetworks, net)
- }
- }
- serviceSpec.Networks = updatedNetworks
-
- var updatedEnv []string
- for _, env := range serviceSpec.TaskTemplate.ContainerSpec.Env {
- if !strings.HasPrefix(env, "SHUFFLE_SWARM_OTHER_NETWORK=") {
- updatedEnv = append(updatedEnv, env)
- }
- }
- serviceSpec.TaskTemplate.ContainerSpec.Env = updatedEnv
- serviceOptions := types.ServiceCreateOptions{}
- _, err = dockercli.ServiceCreate(
- ctx,
- serviceSpec,
- serviceOptions,
- )
- if err != nil {
- log.Printf("[ERROR] Failed to deploy service even without shuffle_shuffle network: %s", err)
- }
- }
- } else {
- log.Printf("[WARNING] Failed deploying workers: %s", err)
- if len(serviceSpec.Networks) > 1 {
- serviceSpec.Networks = []swarm.NetworkAttachmentConfig{
- swarm.NetworkAttachmentConfig{
- Target: "shuffle_shuffle",
- },
- }
-
- _, _ = dockercli.ServiceCreate(
- ctx,
- serviceSpec,
- serviceOptions,
- )
- }
- }
- }
-}
-
-// Deploys the worker with the current available environments
-// https://docs.docker.com/engine/api/sdk/examples/
-func buildEnvVars(envMap map[string]string) []corev1.EnvVar {
- var envVars []corev1.EnvVar
- for key, value := range envMap {
- envVars = append(envVars, corev1.EnvVar{Name: key, Value: value})
- }
-
- return envVars
-}
-
-func buildResourcesFromEnv() corev1.ResourceRequirements {
- requests := corev1.ResourceList{}
- limits := corev1.ResourceList{}
-
- type item struct {
- env string
- resourceName corev1.ResourceName
- resourceList corev1.ResourceList
- }
-
- items := []item{
- // kubernetes requests
- {env: "SHUFFLE_WORKER_CPU_REQUEST", resourceName: corev1.ResourceCPU, resourceList: requests},
- {env: "SHUFFLE_WORKER_MEMORY_REQUEST", resourceName: corev1.ResourceMemory, resourceList: requests},
- {env: "SHUFFLE_WORKER_EPHEMERAL_STORAGE_REQUEST", resourceName: corev1.ResourceEphemeralStorage, resourceList: requests},
- // kubernetes limits
- {env: "SHUFFLE_WORKER_CPU_LIMIT", resourceName: corev1.ResourceCPU, resourceList: limits},
- {env: "SHUFFLE_WORKER_MEMORY_LIMIT", resourceName: corev1.ResourceMemory, resourceList: limits},
- {env: "SHUFFLE_WORKER_EPHEMERAL_STORAGE_LIMIT", resourceName: corev1.ResourceEphemeralStorage, resourceList: limits},
- }
-
- for _, it := range items {
- if value := strings.TrimSpace(os.Getenv(it.env)); value != "" {
- if quantity, err := resource.ParseQuantity(value); err == nil {
- it.resourceList[it.resourceName] = quantity
- } else {
- log.Printf("[WARNING] Cannot parse %s=%q as resource quantity: %v", it.env, value, err)
- }
- }
- }
-
- rr := corev1.ResourceRequirements{}
- if len(requests) > 0 {
- rr.Requests = requests
- }
- if len(limits) > 0 {
- rr.Limits = limits
- }
-
- return rr
-}
-
-func handleBackendImageDownload(ctx context.Context, images string) error {
-
- // Replicate images with lowercase, as the name may be wrong
- // Most of the time lowercase is correct. Swapping to have that first
- originalImages := images
- images = strings.ToLower(images) + "," + originalImages
-
- // Remove the image
- handled := []string{}
- //log.Printf("[DEBUG] Removing existing image (s): %s", images)
- newImages := []string{}
-
- successful := []string{}
- for _, curimage := range strings.Split(images, ",") {
- curimage = strings.TrimSpace(curimage)
- if shuffle.ArrayContains(handled, curimage) {
- continue
- }
-
- handled = append(handled, curimage)
- if !strings.Contains(curimage, "/") {
- curimage = fmt.Sprintf("frikky/shuffle:%s", curimage)
- }
-
- newImages = append(newImages, curimage)
-
- // Force remove the current image to avoid cached layers
- // if swarmConfig == "run" || swarmConfig == "swarm" {
- // _, err := dockercli.ImageRemove(ctx, curimage, image.RemoveOptions{
- // Force: true,
- // PruneChildren: true,
- // })
-
- // if err != nil {
- // log.Printf("[ERROR] Failed removing image for re-download: %s", err)
- // } else {
- // log.Printf("[DEBUG] Removed image: %s", curimage)
- // }
- // } else {
- // //log.Printf("[DEBUG] Skipping image removal for %s as swarmConfig is not set to run or swarm. Value: %#v", curimage, swarmConfig)
- // }
-
- err := shuffle.DownloadDockerImageBackend(&http.Client{Timeout: imagedownloadTimeout}, curimage)
- if err != nil {
- //log.Printf("[ERROR] Failed downloading image: %s", err)
- } else {
- //log.Printf("[DEBUG] Downloaded image: %s", curimage)
- successful = append(successful, curimage)
- }
- }
-
- if len(successful) == 0 {
- log.Printf("[ERROR] Failed downloading image copies: %s. This means the app may not have been updated.", strings.Join(handled, ", "))
- } else {
- log.Printf("[DEBUG] Successfully downloaded image copies: %s", strings.Join(successful, ", "))
- }
-
- if swarmConfig == "run" || swarmConfig == "swarm" {
- log.Printf("[DEBUG] Should update service with new image after updating(s): %s. \n\nBETA REPLACEMENT IMPLEMENTATION: Contact support@shuffler.io for support.", strings.Join(newImages, "\n"))
-
- // 1. Download the image
- // 2. Find the existing service using the image
- // 3. Update the service with the new image in a rolling restart
-
- // Find the existing service
- serviceListOptions := types.ServiceListOptions{}
- services, err := dockercli.ServiceList(
- ctx,
- serviceListOptions,
- )
-
- if err != nil {
- log.Printf("[ERROR] Failed finding services: %s", err)
- } else {
- found := false
- for _, service := range services {
- //log.Printf("Service image: %s", service.Spec.TaskTemplate.ContainerSpec.Image)
-
- for _, image := range newImages {
- if !strings.Contains(service.Spec.TaskTemplate.ContainerSpec.Image, image) {
- continue
- }
-
- log.Printf("[DEBUG] Found service for image: %#v", service.Spec.Annotations.Name)
-
- // Update the service to run with the new image
- //docker service update --image username/imagename:latest servicename --force
- serviceUpdateOptions := types.ServiceUpdateOptions{}
- service.Spec.TaskTemplate.ForceUpdate++
- resp, err := dockercli.ServiceUpdate(
- ctx,
- service.ID,
- service.Version,
- service.Spec,
- serviceUpdateOptions,
- )
-
- if err != nil {
- log.Printf("[ERROR] Failed updating service %s with the new image %s: %s. Resp: %#v", service.Spec.Annotations.Name, image, err, resp)
- } else {
- log.Printf("[DEBUG] Updated service %s with the new image %s. Resp: %#v", service.Spec.Annotations.Name, image, resp)
-
- found = true
-
- if !strings.Contains(fmt.Sprintf("%s", resp), "error") {
- break
- } else {
- log.Printf("[ERROR] Failed updating service %s with the new image %s: %s. Resp: %#v", service.Spec.Annotations.Name, image, err, resp)
- }
- }
- }
-
- if found {
- break
- }
- }
-
- if !found {
- log.Printf("[DEBUG] Failed to find service to update for service %s", newImages)
- }
-
- }
-
- }
-
- return nil
-}
-
-func fixk8sRoles() {
- clientset, _, err := shuffle.GetKubernetesClient()
- if err != nil {
- log.Printf("[ERROR] Error getting kubernetes client: %s", err)
- os.Exit(1)
- }
-
- kubernetesNamespace := "default"
-
- // Check if namespace exist as variable. If so, make it
- if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 {
- kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE")
- }
-
- // fix roles
- // check if "service-creator" role is assigned to the service account "default"
- // roleBindingNames := []string{"service-creator-binding", "pod-creator-binding", "deployment-creator-binding"}
- serviceAccountName := "default"
- roleBindingName := "creator-all"
-
- resourceTypes := []string{"services", "pods", "deployments"}
-
- // Check if the RoleBinding exists
- roleBinding, err := clientset.RbacV1().RoleBindings(kubernetesNamespace).Get(context.TODO(), roleBindingName, metav1.GetOptions{})
- if err != nil {
- log.Printf("[WARNING] Failed to get RoleBinding %s: %s", roleBindingName, err)
-
- // create role and rolebinding
- role := &rbacv1.Role{
- ObjectMeta: metav1.ObjectMeta{
- Name: roleBindingName,
- },
- Rules: []rbacv1.PolicyRule{
- {
- APIGroups: []string{"", "apps"},
- Resources: resourceTypes,
- Verbs: []string{"create", "list"},
- },
- },
- }
-
- ctx := context.TODO()
-
- _, err := clientset.RbacV1().Roles(kubernetesNamespace).Create(ctx, role, metav1.CreateOptions{})
- if err != nil {
- log.Printf("[ERROR] Failed to create Role %s: %s", roleBindingName, err)
- if !strings.Contains(fmt.Sprintf("%s", err), "already exists") {
- log.Printf("[INFO] role %s already exists", roleBindingName)
- }
- }
-
- roleBinding := &rbacv1.RoleBinding{
- ObjectMeta: metav1.ObjectMeta{
- Name: roleBindingName,
- },
- Subjects: []rbacv1.Subject{
- {
- Kind: "ServiceAccount",
- Name: serviceAccountName,
- Namespace: kubernetesNamespace,
- },
- },
- RoleRef: rbacv1.RoleRef{
- Kind: "Role",
- Name: roleBindingName,
- },
- }
-
- _, err = clientset.RbacV1().RoleBindings(kubernetesNamespace).Create(ctx, roleBinding, metav1.CreateOptions{})
- if err != nil {
- log.Printf("[ERROR] Failed to create RoleBinding %s: %s", roleBindingName, err)
- if strings.Contains(fmt.Sprintf("%s", err), "already exists") {
- log.Printf("[INFO] rolebinding %s already exists", roleBindingName)
- }
- }
-
- log.Printf("[INFO] Created Role %s and RoleBinding %s", roleBindingName, roleBindingName)
- } else {
- log.Printf("[INFO] RoleBinding %s exists", roleBindingName)
- }
-
- // Check if the RoleBinding is assigned to the service account
- var found bool
- for _, subject := range roleBinding.Subjects {
- if subject.Kind == "ServiceAccount" && subject.Name == serviceAccountName {
- found = true
- break
- }
- }
-
- if !found {
- log.Printf("[WARNING] Service account %s is not assigned to RoleBinding %s\n", serviceAccountName, roleBindingName)
- // assign the service account to the rolebinding
- roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{
- Kind: "ServiceAccount",
- Name: serviceAccountName,
- Namespace: kubernetesNamespace,
- })
-
- ctx := context.TODO()
-
- _, err := clientset.RbacV1().RoleBindings(kubernetesNamespace).Update(ctx, roleBinding, metav1.UpdateOptions{})
- if err != nil {
- log.Printf("[ERROR](ns - %s) Failed to update RoleBinding %s: %s", kubernetesNamespace, roleBindingName, err)
- if !strings.Contains(fmt.Sprintf("%s", err), "already exists") {
- log.Printf("[INFO] rolebinding %s already exists", roleBindingName)
- }
- }
- }
-}
-
-// TODO: Check if deployment or service already exist by labels and only create if not already exists
-func deployK8sWorker(image string, identifier string, env []string) error {
- env = append(env, fmt.Sprintf("IS_KUBERNETES=true"))
- env = append(env, fmt.Sprintf("KUBERNETES_NAMESPACE=%s", os.Getenv("KUBERNETES_NAMESPACE")))
-
- // app resource env
- for _, k := range []string{
- "SHUFFLE_APP_CPU_REQUEST",
- "SHUFFLE_APP_MEMORY_REQUEST",
- "SHUFFLE_APP_EPHEMERAL_STORAGE_REQUEST",
- "SHUFFLE_APP_CPU_LIMIT",
- "SHUFFLE_APP_MEMORY_LIMIT",
- "SHUFFLE_APP_EPHEMERAL_STORAGE_LIMIT",
- } {
- if v := os.Getenv(k); v != "" {
- env = append(env, fmt.Sprintf("%s=%s", k, v))
- }
- }
-
- if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 {
- env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_HOST=%s", os.Getenv("KUBERNETES_SERVICE_HOST")))
- }
-
- if len(os.Getenv("SHUFFLE_MEMCACHED")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_MEMCACHED=%s", os.Getenv("SHUFFLE_MEMCACHED")))
- }
-
- if len(os.Getenv("KUBERNETES_SERVICE_PORT")) > 0 {
- env = append(env, fmt.Sprintf("KUBERNETES_SERVICE_PORT=%s", os.Getenv("KUBERNETES_SERVICE_PORT")))
- }
-
- if len(os.Getenv("SHUFFLE_BASE_IMAGE_REGISTRY")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_BASE_IMAGE_REGISTRY=%s", os.Getenv("SHUFFLE_BASE_IMAGE_REGISTRY")))
- }
-
- if len(os.Getenv("SHUFFLE_BASE_IMAGE_NAME")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_BASE_IMAGE_NAME=%s", os.Getenv("SHUFFLE_BASE_IMAGE_NAME")))
- } else {
- log.Printf("[INFO] SHUFFLE_BASE_IMAGE_NAME is not set. Defaulting to %s", baseimagename)
- env = append(env, fmt.Sprintf("SHUFFLE_BASE_IMAGE_NAME=%s", baseimagename))
- }
-
- if len(os.Getenv("REGISTRY_URL")) > 0 {
- env = append(env, fmt.Sprintf("REGISTRY_URL=%s", os.Getenv("REGISTRY_URL")))
- }
-
- if len(os.Getenv("SHUFFLE_USE_GHCR_OVERRIDE_FOR_AUTODEPLOY")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_USE_GHCR_OVERRIDE_FOR_AUTODEPLOY=%s", os.Getenv("SHUFFLE_USE_GHCR_OVERRIDE_FOR_AUTODEPLOY")))
- }
-
- if len(os.Getenv("SHUFFLE_APP_EXPOSED_PORT")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_APP_EXPOSED_PORT=%s", os.Getenv("SHUFFLE_APP_EXPOSED_PORT")))
- }
-
- if len(appServiceAccountName) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_APP_SERVICE_ACCOUNT_NAME=%s", appServiceAccountName))
- }
-
- if len(appPodSecurityContext) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_APP_POD_SECURITY_CONTEXT=%s", appPodSecurityContext))
- }
-
- if len(appContainerSecurityContext) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_APP_CONTAINER_SECURITY_CONTEXT=%s", appContainerSecurityContext))
- }
-
- if len(os.Getenv("SHUFFLE_APP_MOUNT_TMP_VOLUME")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_APP_MOUNT_TMP_VOLUME=%s", os.Getenv("SHUFFLE_APP_MOUNT_TMP_VOLUME")))
- }
-
- if len(os.Getenv("SHUFFLE_LOGS_DISABLED")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")))
- }
-
- if len(os.Getenv("SHUFFLE_APP_REPLICAS")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_APP_REPLICAS=%s", os.Getenv("SHUFFLE_APP_REPLICAS")))
- }
-
- clientset, _, err := shuffle.GetKubernetesClient()
- if err != nil {
- log.Printf("[ERROR] Error getting kubernetes client:", err)
- return err
- }
-
- //env = append(env, fmt.Sprintf("KUBERNETES_CONFIG=%s", config.String()))
-
- // Check if namespace exist as variable. If so, make it
- if len(os.Getenv("KUBERNETES_NAMESPACE")) > 0 && !namespacemade {
- kubernetesNamespace = os.Getenv("KUBERNETES_NAMESPACE")
-
- // Make the namespace
- namespace := &corev1.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: os.Getenv("KUBERNETES_NAMESPACE"),
- },
- }
-
- _, err := clientset.CoreV1().Namespaces().Create(context.Background(), namespace, metav1.CreateOptions{})
- if err != nil {
- if !strings.Contains(strings.ToLower(fmt.Sprintf("%s", err)), "already exists") {
- log.Printf("[ERROR] Failed creating Kubernetes namespace: %s", err)
- } else {
- namespacemade = true
- }
- } else {
- namespacemade = true
- }
- }
-
- // Required format:
- // url/org/repo/appname:tag
- // url/org/repo/appname:tag
-
- //env = append(env, fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", swarmConfig))
- env = append(env, fmt.Sprintf("BASE_URL=%s", baseUrl))
- env = append(env, fmt.Sprintf("SHUFFLE_SWARM_CONFIG=run"))
- env = append(env, fmt.Sprintf("WORKER_HOSTNAME=%s", "shuffle-workers"))
-
- if len(kubernetesNamespace) == 0 {
- foundNamespace, err := shuffle.GetKubernetesNamespace()
- if err != nil {
- //log.Printf("[ERROR] Failed getting Kubernetes namespace: %s", err)
- }
-
- if len(foundNamespace) > 0 {
- kubernetesNamespace = foundNamespace
- os.Setenv("KUBERNETES_NAMESPACE", kubernetesNamespace)
- }
- }
-
- if len(kubernetesNamespace) == 0 {
- kubernetesNamespace = "default"
- }
-
- kubernetesImage := os.Getenv("SHUFFLE_KUBERNETES_WORKER")
- if len(kubernetesImage) == 0 {
- kubernetesImage = image
- }
- log.Printf("[DEBUG] Using Kubernetes worker image '%s'", kubernetesImage)
- // image = "shuffle-worker:v1" //hard coded image name to test locally
-
- envMap := make(map[string]string)
- for _, envStr := range env {
- parts := strings.SplitN(envStr, "=", 2)
- if len(parts) == 2 {
- envMap[parts[0]] = parts[1]
- }
- }
-
- labels := map[string]string{
- // Well-known Kubernetes labels
- "app.kubernetes.io/name": "shuffle-worker",
- "app.kubernetes.io/instance": identifier,
- "app.kubernetes.io/part-of": "shuffle",
- "app.kubernetes.io/managed-by": "shuffle-orborus",
- // Keep legacy labels for backward compatibility
- "container": "shuffle-worker",
- }
-
- matchLabels := map[string]string{
- "app.kubernetes.io/name": "shuffle-worker",
- "app.kubernetes.io/instance": identifier,
- }
-
- // Parse security contexts from env
- var podSecurityContext *corev1.PodSecurityContext
- var containerSecurityContext *corev1.SecurityContext
-
- if len(workerPodSecurityContext) > 0 {
- podSecurityContext = &corev1.PodSecurityContext{}
- err = json.Unmarshal([]byte(workerPodSecurityContext), podSecurityContext)
- if err != nil {
- log.Printf("[ERROR] Failed to unmarshal worker pod security context: %v", err)
- return fmt.Errorf("failed to unmarshal worker pod security context: %v", err)
- }
- }
-
- if len(workerContainerSecurityContext) > 0 {
- containerSecurityContext = &corev1.SecurityContext{}
- err = json.Unmarshal([]byte(workerContainerSecurityContext), containerSecurityContext)
- if err != nil {
- log.Printf("[ERROR] Failed to unmarshal worker container security context: %v", err)
- return fmt.Errorf("failed to unmarshal worker container security context: %v", err)
- }
- }
-
- containerAttachment := corev1.Container{
- Name: identifier,
- Image: kubernetesImage,
- Env: buildEnvVars(envMap),
- SecurityContext: containerSecurityContext,
- Resources: buildResourcesFromEnv(),
-
- //ImagePullPolicy: "Never",
- ImagePullPolicy: corev1.PullIfNotPresent,
- }
-
- if len(os.Getenv("REGISTRY_URL")) > 0 && len(os.Getenv("SHUFFLE_BASE_IMAGE_NAME")) > 0 {
- log.Printf("[INFO] Setting image pull policy to Always as private registry is used.")
- containerAttachment.ImagePullPolicy = corev1.PullAlways
- }
-
- podname := shuffle.GetPodName()
- ctx := context.Background()
- if len(podname) > 0 {
- _, err := shuffle.GetCurrentPodNetworkConfig(ctx, clientset, kubernetesNamespace, podname)
- if err != nil {
- log.Printf("[ERROR] Failed getting current pod network: %s", err)
- } else {
- log.Printf("[DEBUG] Current pod found!")
- // currentPodStatus = k8s.io/api/core/v1.PodStatus
- }
- }
-
- // While testing:
- // kubectl delete pods --all --all-namespaces; kubectl delete services --all --all-namespaces
- // pod := &corev1.Pod{
- // ObjectMeta: metav1.ObjectMeta{
- // Name: identifier,
- // Labels: containerLabels,
- // },
- // Spec: corev1.PodSpec{
- // RestartPolicy: "Never",
- // // DNSPolicy: "Default",
- // DNSPolicy: corev1.DNSClusterFirst,
- // // NodeSelector: map[string]string{
- // // "node": "master",
- // // },
- // Containers: []corev1.Container{
- // containerAttachment,
- // },
- // },
- // }
-
- // // Check if running on ARM or x86 to download the correct image
-
- // // Get current pod's network so we can make the pod in it
-
- // _, err = clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{})
- // if err != nil {
- // log.Printf("[ERROR] Failed listing pods: %s", err)
- // }
-
- // createdPod, err := clientset.CoreV1().Pods(kubernetesNamespace).Create(context.Background(), pod, metav1.CreateOptions{})
- // if err != nil {
- // //log.Printf("[ERROR] Failed creating pod: %v", err)
- // return err
- // }
-
- // log.Printf("[INFO] Created pod %q in namespace %q\n", createdPod.Name, createdPod.Namespace)
-
- // // kubectl expose pod shuffle-workers --type=LoadBalancer --port=33333
- // service := &corev1.Service{
- // ObjectMeta: metav1.ObjectMeta{
- // Name: identifier,
- // },
- // Spec: corev1.ServiceSpec{
- // Selector: map[string]string{
- // "container": "shuffle-workers",
- // },
- // Ports: []corev1.ServicePort{
- // {
- // Protocol: "TCP",
- // Port: 33333,
- // TargetPort: intstr.FromInt(33333),
- // },
- // },
- // Type: corev1.ServiceTypeLoadBalancer,
- // },
- // }
-
- // _, err = clientset.CoreV1().Services(kubernetesNamespace).Create(context.TODO(), service, metav1.CreateOptions{})
- // if err != nil {
- // log.Printf("[ERROR] Failed creating service: %v", err)
- // return err
- // }
-
- replicaNumberStr := os.Getenv("SHUFFLE_SCALE_REPLICAS")
- replicaNumber := 1
- if len(replicaNumberStr) > 0 {
- tmpInt, err := strconv.Atoi(replicaNumberStr)
- if err != nil {
- log.Printf("[ERROR] %s is not a valid number for replication", replicaNumberStr)
- } else {
- replicaNumber = tmpInt
-
- }
- }
-
- existing, err := clientset.AppsV1().Deployments(kubernetesNamespace).List(ctx, metav1.ListOptions{
- LabelSelector: "app.kubernetes.io/name=shuffle-worker",
- })
- if err != nil {
- log.Printf("[ERROR] Failed listing existing deployments: %v", err)
- }
-
- if len(existing.Items) > 0 {
- log.Printf("[INFO] Found existing deployments, skipping creation")
- return nil
- }
-
- replicaNumberInt32 := int32(replicaNumber)
- // worker makes authenticated requests to the k8s api to create app deployments.
- // Therefore, it needs to have access to the service account token.
- automountServiceAccountToken := true
-
- deployment := &appsv1.Deployment{
- ObjectMeta: metav1.ObjectMeta{
- Name: identifier,
- Labels: labels,
- },
- Spec: appsv1.DeploymentSpec{
- Replicas: &replicaNumberInt32,
- Selector: &metav1.LabelSelector{
- MatchLabels: matchLabels,
- },
- Template: corev1.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: labels,
- },
- Spec: corev1.PodSpec{
- Containers: []corev1.Container{
- containerAttachment,
- },
- DNSPolicy: corev1.DNSClusterFirst,
- ServiceAccountName: workerServiceAccountName,
- AutomountServiceAccountToken: &automountServiceAccountToken,
- SecurityContext: podSecurityContext,
- },
- },
- },
- }
-
- _, err = clientset.AppsV1().Deployments(kubernetesNamespace).Create(context.Background(), deployment, metav1.CreateOptions{})
- if err != nil {
- log.Printf("[ERROR] Failed creating deployment: %v", err)
- return err
- }
-
- svcAppProtocol := "http"
- service := &corev1.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: identifier,
- Labels: labels,
- },
- Spec: corev1.ServiceSpec{
- Selector: matchLabels,
- Ports: []corev1.ServicePort{
- {
- Protocol: "TCP",
- AppProtocol: &svcAppProtocol,
- Port: 33333,
- TargetPort: intstr.FromInt(33333),
- },
- },
- Type: corev1.ServiceTypeClusterIP,
- },
- }
-
- _, err = clientset.CoreV1().Services(kubernetesNamespace).Create(context.Background(), service, metav1.CreateOptions{})
- if err != nil {
- log.Printf("[ERROR] Failed creating service: %v", err)
- return err
- }
-
- return nil
-}
-
-func deployWorker(image string, identifier string, env []string, executionRequest shuffle.ExecutionRequest) error {
-
- if len(os.Getenv("REGISTRY_URL")) > 0 && os.Getenv("REGISTRY_URL") != "" {
- env = append(env, fmt.Sprintf("REGISTRY_URL=%s", os.Getenv("REGISTRY_URL")))
- }
-
- if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" {
- // FIXME: Should we handle replies properly?
- // In certain cases, a workflow may e.g. be aborted already. If it's aborted, that returns
- // a 401 from the worker, which returns an error here
- go sendWorkerRequest(executionRequest, image, env)
-
- return nil
- }
-
- // Binds is the actual "-v" volume.
- // Max 20% CPU every second
-
- //CPUQuota: 25000,
- //CPUPeriod: 100000,
- //CPUShares: 256,
- hostConfig := &container.HostConfig{
- LogConfig: container.LogConfig{
- Type: "json-file",
- Config: map[string]string{
- "max-size": "10m",
- },
- },
- Resources: container.Resources{},
- }
-
- // This is just to test the mounting locally so
- // I can control from what source I'm mounting
- // the certs to. Default behaviour is:
- // /certs:/certs.
- certPath := "/certs"
- if os.Getenv("SHUFFLE_CERT_PATH") != "" {
- certPath = os.Getenv("SHUFFLE_CERT_PATH")
- }
-
- _, err := os.ReadDir(certPath)
- if certPath != "" && err == nil {
- certVol := mount.Mount{
- Type: mount.TypeBind,
- Source: certPath,
- Target: "/certs",
- }
-
- hostConfig.Mounts = append(hostConfig.Mounts, certVol)
- }
-
- if len(os.Getenv("DOCKER_HOST")) == 0 {
- if runtime.GOOS == "windows" {
- hostConfig.Binds = []string{`\\.\pipe\docker_engine:\\.\pipe\docker_engine`}
- } else {
- hostConfig.Binds = []string{"/var/run/docker.sock:/var/run/docker.sock:rw"}
- }
- }
-
- //var swarmConfig = os.Getenv("SHUFFLE_SWARM_CONFIG")
- parsedUuid := uuid.NewV4()
-
- config := &container.Config{
- Image: image,
- Env: env,
- }
-
- if isKubernetes != "true" {
- hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
-
- if strings.ToLower(cleanupEnv) == "true" {
- hostConfig.AutoRemove = true
- }
- }
-
- //log.Printf("[INFO] Identifier: %s", identifier)
- cont, err := dockercli.ContainerCreate(
- context.Background(),
- config,
- hostConfig,
- nil,
- nil,
- identifier,
- )
-
- if err != nil {
- if strings.Contains(fmt.Sprintf("%s", err), "Conflict. The container name ") {
- identifier = fmt.Sprintf("%s-%s", identifier, parsedUuid)
- //log.Printf("[INFO] 2 - Identifier: %s", identifier)
- cont, err = dockercli.ContainerCreate(
- context.Background(),
- config,
- hostConfig,
- nil,
- nil,
- identifier,
- )
-
- if err != nil {
- log.Printf("[ERROR][%s] Container create error(2): %s", executionRequest.ExecutionId, err)
- return err
- }
- } else {
- log.Printf("[ERROR][%s] Container create error: %s", executionRequest.ExecutionId, err)
- return err
- }
- }
-
- // FIXME: Verbosity for testing
- //log.Printf("WORKER STARTING WITH ENV: %#v", env)
-
- ctx := context.Background()
- containerStartOptions := container.StartOptions{}
- err = dockercli.ContainerStart(ctx, cont.ID, containerStartOptions)
- if err != nil {
- // Trying to recreate and start WITHOUT network if it's possible. No extended checks. Old execution system (<0.9.30)
- if strings.Contains(fmt.Sprintf("%s", err), "cannot join network") || strings.Contains(fmt.Sprintf("%s", err), "No such container") {
- hostConfig.NetworkMode = ""
- //container.NetworkMode(fmt.Sprintf("container:%s", containerId))
- cont, err = dockercli.ContainerCreate(
- context.Background(),
- config,
- hostConfig,
- nil,
- nil,
- identifier+"-2",
- )
- if err != nil {
- log.Printf("[ERROR][%s] Failed to CREATE container (2): %s", executionRequest.ExecutionId, err)
- }
-
- err = dockercli.ContainerStart(context.Background(), cont.ID, containerStartOptions)
- if err != nil {
- log.Printf("[ERROR][%s] Failed to start container (2): %s", executionRequest.ExecutionId, err)
- }
- } else {
- log.Printf("[ERROR][%s] Failed initial container start. Quitting as this is NOT a simple network issue. Err: %s", executionRequest.ExecutionId, err)
- }
-
- if err != nil {
- log.Printf("[ERROR][%s] Failed to start worker container in environment '%s': %s", executionRequest.ExecutionId, environment, err)
- return err
- } else {
- log.Printf("[INFO][%s] Worker Container created (2). Runtime Location '%s': docker logs -f %s", executionRequest.ExecutionId, environment, cont.ID)
- }
-
- stats, err := dockercli.ContainerInspect(ctx, cont.ID)
- if err != nil {
- log.Printf("[WARNING][%s] Failed checking worker '%s': %s", executionRequest.ExecutionId, cont.ID, err)
- return nil
- }
-
- containerStatus := stats.ContainerJSONBase.State.Status
- if containerStatus != "running" {
- log.Printf("[ERROR][%s] Status of %s is %s. Should be running. Contact support@shuffler.io if this persists.", executionRequest.ExecutionId, cont.ID, containerStatus)
- }
- /*
- err = stopWorker(containerName)
- if err != nil {
- log.Printf("Failed stopping worker %s", execution.ExecutionId)
- return nil
- }
-
- err = deployWorker(dockercli, workerImage, containerName, env)
- if err != nil {
- log.Printf("Failed executing worker %s in state %s", execution.ExecutionId, containerStatus)
- return nil
- }
- }
- */
- } else {
- log.Printf("[INFO][%s] New Worker created. Environment %s: docker logs %s", executionRequest.ExecutionId, environment, cont.ID)
- }
-
- return nil
-}
-
-func stopWorker(containername string) error {
- ctx := context.Background()
-
- // containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
- // All: true,
- // })
-
- //if err := dockercli.ContainerStop(ctx, containername, nil); err != nil {
- var options container.StopOptions
- if err := dockercli.ContainerStop(ctx, containername, options); err != nil {
- log.Printf("[ERROR] Unable to stop container %s - running removal anyway, just in case: %s", containername, err)
- }
-
- removeOptions := container.RemoveOptions{
- RemoveVolumes: true,
- Force: true,
- }
-
- if err := dockercli.ContainerRemove(ctx, containername, removeOptions); err != nil {
- log.Printf("[ERROR] Unable to remove container: %s", err)
- }
-
- return nil
-}
-
-func initializeImages() {
- ctx := context.Background()
-
- if appSdkVersion == "" {
- appSdkVersion = "latest"
- log.Printf("[INFO] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %#v", appSdkVersion)
- }
-
- if workerVersion == "" {
- workerVersion = "latest"
- log.Printf("[INFO] SHUFFLE_WORKER_VERSION not defined. Defaulting to %#v", workerVersion)
- }
-
- if baseimageregistry == "" {
- //baseimageregistry = "ghcr.io" // Github
- baseimageregistry = "docker.io" // Dockerhub
-
- if len(os.Getenv("REGISTRY_URL")) > 0 {
- baseimageregistry = os.Getenv("REGISTRY_URL")
- } else {
- // os.Setenv("REGISTRY_URL", baseimageregistry)
- }
-
- os.Setenv("SHUFFLE_BASE_IMAGE_REGISTRY", baseimageregistry)
-
- log.Printf("[INFO] Setting baseimageregistry to %#v", baseimageregistry)
- }
-
- if baseimagename == "" {
- // FIXME: This is probably the problem for image names tbh
- //baseimagename = "shuffle" // Github (ghcr.io)
- baseimagename = "frikky/shuffle" // Dockerhub
-
- os.Setenv("SHUFFLE_BASE_IMAGE_NAME", baseimagename)
- log.Printf("[INFO] Setting baseimagename to %#v", baseimagename)
- }
-
- // Old sane default overrides:
- if baseimageregistry == "ghcr.io" && baseimagename == "shuffle" {
- baseimageregistry = "docker.io"
- baseimagename = "frikky/shuffle"
-
- os.Setenv("REGISTRY_URL", baseimageregistry)
- os.Setenv("SHUFFLE_BASE_IMAGE_REGISTRY", baseimageregistry)
- os.Setenv("SHUFFLE_BASE_IMAGE_NAME", baseimagename)
-
- log.Printf("[WARNING] Overriding bad defaults of ghcr.io/shuffle")
- }
-
- log.Printf("[DEBUG] Setting swarm config to %#v. Default is empty.", swarmConfig)
-
- // This is now always static
- newWorker := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
- if len(newWorkerImage) > 0 {
- newWorker = newWorkerImage
- }
-
- // Check whether they are the same first
- if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") == "false" {
- log.Printf("[DEBUG] Skipping image download as SHUFFLE_AUTO_IMAGE_DOWNLOAD is set to false")
- } else {
- images := []string{
- fmt.Sprintf("frikky/shuffle:app_sdk"),
- newWorker,
- }
-
- pullOptions := image.PullOptions{}
- for _, image := range images {
- if isKubernetes == "true" {
- log.Printf("[DEBUG] Skipping image pull of '%s' because Kubernetes does it in realtime instead", image)
- } else {
- log.Printf("[DEBUG] Pulling image %s", image)
- reader, err := dockercli.ImagePull(ctx, image, pullOptions)
- if err != nil {
- log.Printf("[ERROR] Failed getting image %s: %s", image, err)
-
- continue
- }
-
- io.Copy(os.Stdout, reader)
- log.Printf("[DEBUG] Successfully downloaded and built %s", image)
- }
- }
- }
-}
-
-func findActiveSwarmNodes() (int64, error) {
- ctx := context.Background()
- nodes, err := dockercli.NodeList(ctx, types.NodeListOptions{})
- if err != nil {
- return 1, err
- }
-
- nodeCount := int64(0)
- for _, node := range nodes {
- //log.Printf("ID: %s - %#v", node.ID, node.Status.State)
- if node.Status.State == "ready" {
- nodeCount += 1
- }
- }
-
- // Check for SHUFFLE_MAX_NODES
- // Make it into a number and check if it's lower than nodeCount
- maxNodesString := os.Getenv("SHUFFLE_MAX_SWARM_NODES")
- if len(maxNodesString) > 0 {
- maxNodes, err := strconv.ParseInt(maxNodesString, 10, 64)
- if err != nil {
- return nodeCount, err
- }
-
- if nodeCount > maxNodes {
- nodeCount = maxNodes
- }
- }
-
- return nodeCount, nil
-}
-
-// Get IP
-func getLocalIP() string {
- addrs, err := net.InterfaceAddrs()
- if err != nil {
- return ""
- }
-
- for _, address := range addrs {
- // check the address type and if it is not a loopback the display it
- if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
- if ipnet.IP.To4() != nil {
- return ipnet.IP.String()
- }
- }
- }
-
- return ""
-}
-
-// Get all local IPs in the system
-func getLocalIPs() ([]string, error) {
- var ipv4s []string
- var ipv6s []string
-
- ifaces, err := net.Interfaces()
- if err != nil {
- return nil, err
- }
-
- for _, iface := range ifaces {
- if iface.Flags&net.FlagUp == 0 {
- continue
- }
- if iface.Flags&net.FlagLoopback != 0 {
- continue
- }
-
- addrs, err := iface.Addrs()
- if err != nil {
- continue
- }
-
- for _, address := range addrs {
- ipnet, ok := address.(*net.IPNet)
- if !ok || ipnet.IP == nil {
- continue
- }
-
- ip := ipnet.IP
- if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
- continue
- }
-
- if ip4 := ip.To4(); ip4 != nil {
- ipv4s = append(ipv4s, ip4.String())
- continue
- }
-
- if ip.To16() != nil {
- ipv6s = append(ipv6s, ip.String())
- }
- }
- }
-
- return append(ipv4s, ipv6s...), nil
-}
-
-func checkSwarmService(ctx context.Context) {
- // https://docs.docker.com/engine/reference/commandline/swarm_init/
- ip := getLocalIP()
- log.Printf("[DEBUG] Attempting swarm setup on %s", ip)
-
- info, err := dockercli.Info(ctx)
- if err != nil {
- log.Printf("[WARNING] Failed to get Docker Info: %s", err)
- }
-
- if info.Swarm.ControlAvailable {
- log.Printf("[INFO] Already part of swarm as a manager")
- return
- }
-
- listenAddr := "0.0.0.0"
- req := swarm.InitRequest{
- ListenAddr: fmt.Sprintf("%s:2377", listenAddr),
- AdvertiseAddr: fmt.Sprintf("%s:2377", ip),
- }
-
- id, err := dockercli.SwarmInit(ctx, req)
- if err != nil {
- log.Printf("[ERROR] Swarm init issue: %s. Retrying with a failover IP address from interface.", err)
-
- // Dummy message used for testing
- //err = errors.New("Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on different interfaces (10.52.208.221 on eno1 and 192.168.122.1 on virbr0) - specify one with --advertise-addr")
- // Update 28 Jan 2026: The error message updated and not as clear
-
- candidates, err := getLocalIPs()
- if len(candidates) > 0 && err == nil {
- for cnt, candidate := range candidates {
- if cnt > 5 {
- break
- }
-
- req.AdvertiseAddr = fmt.Sprintf("%s:2377", candidate)
- id, err = dockercli.SwarmInit(context.Background(), req)
- if err != nil {
- continue
- }
-
- log.Printf("[INFO] Swarm init ID: '%s'.", id)
- return
- }
- }
-
- log.Printf("[ERROR] Swarm init failed after advertise-addr retries: %s, try running swarm init manually: docker swarm init", err)
- return
- }
-}
-
-func getContainerResourceUsage(ctx context.Context, cli *dockerclient.Client, containerID string) (float64, float64, error) {
- // Get container stats
- stats, err := cli.ContainerStats(ctx, containerID, false)
- if err != nil {
- return 0, 0, err
- }
-
- defer stats.Body.Close()
- // Parse and return CPU and memory utilization
- cpuUsage, memoryUsage, err := parseResourceUsage(stats.Body)
- if err != nil {
- return 0, 0, err
- }
-
- return cpuUsage, memoryUsage, nil
-}
-
-func parseResourceUsage(body io.Reader) (float64, float64, error) {
- //var stats types.StatsJSON
- var stats container.Stats
-
- // Decode the stream of stats as JSON
- decoder := json.NewDecoder(body)
- if err := decoder.Decode(&stats); err != nil {
- return 0, 0, err
- }
-
- //log.Printf("[DEBUG] CPU : %d", stats.CPUStats.CPUUsage.TotalUsage)
- //log.Printf("[DEBUG] CPU2: %d", stats.PreCPUStats.CPUUsage.TotalUsage)
- if stats.CPUStats.CPUUsage.TotalUsage == 0 || stats.PreCPUStats.CPUUsage.TotalUsage == 0 {
- //log.Printf("[DEBUG] BODY: %#v", stats)
- return 0, 0, nil
- }
-
- // Calculate time difference between current and previous stats in nanoseconds
- timeDelta := float64(stats.Read.Sub(stats.PreRead).Nanoseconds())
-
- // Calculate CPU usage percentage
- cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage)
- cpuUsage := (cpuDelta / timeDelta) * 100.0
-
- // Calculate memory usage percentage
- memoryUsage := float64(stats.MemoryStats.Usage) / float64(stats.MemoryStats.Limit) * 100.0
-
- return cpuUsage, memoryUsage, nil
-
-}
-
-func getOrborusStats(ctx context.Context) shuffle.OrborusStats {
- newStats := shuffle.OrborusStats{
- OrgId: org,
- Environment: environment,
- OrborusLabel: orborusLabel,
- Timestamp: time.Now().Unix(),
-
- Uuid: orborusUuid,
- }
-
- if (swarmConfig == "run" || swarmConfig == "swarm") && strings.Contains(newWorkerImage, "scale") {
- newStats.Swarm = true
- }
-
- newStats.PollTime = sleepTime
- newStats.MaxQueue = maxConcurrency
- newStats.Queue = executionCount
-
- if isKubernetes == "true" || runningMode == "kubernetes" || runningMode == "k8s" {
- newStats.Kubernetes = true
- return newStats
- }
-
- // Disable orborus stats
- if os.Getenv("SHUFFLE_STATS_DISABLED") == "true" {
- return newStats
- }
-
- // FIXME: Returning for now due to this causing network congestion
- // and database fillup. The backend api also has it disabled.
- return newStats
-
- // Use the docker API to get the CPU usage of the docker engine machine
- pers, err := dockercli.Info(ctx)
- if err != nil {
- log.Printf("[ERROR] Failed getting docker info: %s. This is normal IF there are many containers running.", err)
- return newStats
- } else {
- newStats.TotalContainers = pers.Containers
- newStats.StoppedContainers = pers.ContainersStopped
-
- // Calculate the amount of CPU utilization on the host
- newStats.CPU = int(pers.NCPU)
- newStats.MaxCPU = int(pers.NCPU)
- newStats.Memory = int(pers.MemTotal)
- newStats.MaxMemory = int(pers.MemTotal)
- }
-
- // Get list of all running containers
- containers, err := dockercli.ContainerList(ctx, container.ListOptions{})
-
- if err != nil {
- log.Printf("[ERROR] Failed getting container list: %s", err)
- return newStats
- }
-
- // Use a WaitGroup to wait for all goroutines to finish
- var wg sync.WaitGroup
-
- // Channel to collect results
- resultCh := make(chan struct {
- containerID string
- cpuUsage float64
- memoryUsage float64
- })
-
- // Iterate through containers and start a goroutine for each container
- for _, container := range containers {
- // Check if container is running
- if container.State != "running" {
- continue
- }
-
- wg.Add(1)
- go func(container types.Container) {
- defer wg.Done()
-
- // Get CPU and memory usage for the container
- cpuUsage, memoryUsage, err := getContainerResourceUsage(ctx, dockercli, container.ID)
- if err != nil {
- //log.Printf("[DEBUG] Error getting resource usage for container %s: %v\n", container.ID, err)
- }
-
- // Send the result to the channel
- resultCh <- struct {
- containerID string
- cpuUsage float64
- memoryUsage float64
- }{container.ID, cpuUsage, memoryUsage}
- }(container)
- }
-
- // Close the result channel after all goroutines are done
- go func() {
- wg.Wait()
- close(resultCh)
- }()
-
- // Collect results from the channel
-
- // Iterate through containers and get CPU usage
- totalCPU := float64(0.0)
- memUsage := float64(0.0)
- for result := range resultCh {
- //log.Printf("[DEBUG] Container %s CPU utilization: %.2f%%, Memory utilization: %.2f%%\n", result.containerID, result.cpuUsage, result.memoryUsage)
-
- // check if it's NaN or Inf
- if !math.IsNaN(result.cpuUsage) {
- totalCPU += float64(result.cpuUsage)
- }
-
- if !math.IsNaN(result.memoryUsage) {
- memUsage += float64(result.memoryUsage)
- }
- }
-
- newStats.CPUPercent = totalCPU / float64(newStats.CPU)
- newStats.MemoryPercent = memUsage
-
- //log.Printf("[DEBUG] CPU: %.2f, Memory: %.2f", newStats.CPUPercent, newStats.MemoryPercent)
-
- /*
- cpuPercent, err := cpu.Percent(250*time.Millisecond, false)
- if err == nil && len(cpuPercent) > 0 {
- newStats.CPUPercent = cpuPercent[0]
- }
- //Percent(interval time.Duration, percpu bool) ([]float64, error)
-
- // Get memory usage
- memory, err := memory.Get()
- if err != nil {
- log.Printf("[ERROR] Failed getting memory stats: %s", err)
- } else {
- newStats.Memory = int(memory.Used)
- newStats.MaxMemory = int(memory.Total)
- }
- */
-
- // Get disk usage
- /*
- disk, err := disk.Get()
- if err != nil {
- log.Printf("[ERROR] Failed getting disk stats: %s", err)
- } else {
- newStats.Disk = int(disk.Used)
- newStats.MaxDisk = int(disk.Total)
- }
- */
-
- /*
- // General
- Disk int `json:"disk"`
-
- // Docker
- AppContainers int `json:"app_containers"`
- WorkerContainers int `json:"worker_containers"`
- TotalContainers int `json:"total_containers"`
- }
- */
- return newStats
-}
-
-func sendRemoveRequest(client *http.Client, toBeRemoved shuffle.ExecutionRequestWrapper, baseUrl, environment, auth, org string, sleepTime int) error {
- confirmUrl := fmt.Sprintf("%s/api/v1/workflows/queue/confirm", baseUrl)
- data, err := json.Marshal(toBeRemoved)
- if err != nil {
- log.Printf("[WARNING] Failed removal marshalling: %s", err)
- time.Sleep(time.Duration(sleepTime) * time.Second)
- return err
- }
-
- result, err := http.NewRequest(
- "POST",
- confirmUrl,
- bytes.NewBuffer([]byte(data)),
- )
-
- if err != nil {
- log.Printf("[ERROR] Failed building confirm request: %s", err)
- time.Sleep(time.Duration(sleepTime) * time.Second)
- return err
- }
-
- result.Header.Add("Content-Type", "application/json")
- result.Header.Add("Org-Id", environment)
-
- if len(auth) > 0 {
- result.Header.Add("Authorization", auth)
- }
-
- if len(org) > 0 {
- result.Header.Add("Org", org)
- }
-
- if len(orborusLabel) > 0 {
- result.Header.Add("X-Orborus-Label", orborusLabel)
- }
-
- resultResp, err := client.Do(result)
- if err != nil {
- if !strings.Contains(fmt.Sprintf("%s", err), "timeout") {
- log.Printf("[ERROR] Failed making confirm request: %s", err)
- }
-
- time.Sleep(time.Duration(sleepTime) * time.Second)
- return err
- }
-
- defer resultResp.Body.Close()
- body, err := ioutil.ReadAll(resultResp.Body)
- if err != nil {
- log.Printf("[ERROR] Failed reading confirm body: %s", err)
- time.Sleep(time.Duration(sleepTime) * time.Second)
- return err
- }
-
- _ = body
- //log.Printf("[DEBUG] Confirm response: %s", string(body))
-
- return nil
-}
-
-func cleanup() {
- log.Printf("[INFO] Cleaning up during shutdown")
- ctx := context.Background()
- cleanupExistingNodes(ctx)
- zombiecheck(ctx, 600)
- os.Exit(0)
-}
-
-func StartAgent() {
- log.Printf("[INFO] Starting Orborus agent mode")
-
- auditLogEnabled := os.Getenv("SHUFFLE_AUDIT_LOG_ENABLED") == "true"
-
- if auditLogEnabled {
- log.Printf("[INFO] Audit log monitoring is enabled")
-
- // Initialize telemetry configuration
- telemetryConfig := shuffle.TelemetryConfig{
- Enabled: true,
- Modes: []string{"audit_log"},
- BufferSize: 1000,
- FlushInterval: 10 * time.Second,
- }
-
- if excludePatterns := os.Getenv("SHUFFLE_AUDIT_LOG_EXCLUDE"); excludePatterns != "" {
- patterns := strings.Split(excludePatterns, ",")
- telemetryConfig.Filters = append(telemetryConfig.Filters, shuffle.TelemetryFilter{
- Type: "message",
- Exclude: patterns,
- })
- }
-
- if includePatterns := os.Getenv("SHUFFLE_AUDIT_LOG_INCLUDE"); includePatterns != "" {
- patterns := strings.Split(includePatterns, ",")
- telemetryConfig.Filters = append(telemetryConfig.Filters, shuffle.TelemetryFilter{
- Type: "message",
- Include: patterns,
- })
- }
-
- collector, err := shuffle.NewAuditLogCollector(telemetryConfig)
- if err != nil {
- log.Printf("[ERROR] Failed to create audit log collector: %v", err)
- } else {
- ctx := context.Background()
- if err := collector.LogCollectorStart(ctx); err != nil {
- log.Printf("[ERROR] Failed to start audit log collector: %v", err)
- } else {
- log.Printf("[INFO] Audit log collector started successfully")
-
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
-
- go func() {
- <-sigChan
- log.Printf("[INFO] Received shutdown signal, stopping audit log collector...")
- collector.Stop()
-
- os.Exit(0)
- }()
- }
- }
- } else {
- log.Printf("[INFO] Audit log monitoring is disabled")
- }
- select {}
-}
-
-// Initial loop etc
-func main() {
- // Get arch. amd64 or arm64
-
- //sigCh := make(chan os.Signal, 1)
- //signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
- //defer cleanup()
-
- agentMode := os.Getenv("SHUFFLE_AGENT_MODE")
- if agentMode == "true" {
- log.Printf("[INFO] Running in agent mode. Starting the agent.")
- StartAgent()
- return
- }
-
- if os.Getenv("SHUFFLE_PIPELINE_STANDALONE") == "true" {
- log.Printf("[INFO] Allowing use of standalone pipeline (tenzir). URL: %s", pipelineUrl)
-
- tenzirDisabled = false
- os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
- os.Setenv("SHUFFLE_PIPELINE_ENABLED", "true")
- }
-
- // Block until a signal is received
- if shuffle.IsRunningInCluster() {
- log.Printf("[INFO] Running inside k8s cluster")
- }
-
- if isKubernetes == "true" {
- fixk8sRoles()
- }
-
- startupDelay := os.Getenv("SHUFFLE_ORBORUS_STARTUP_DELAY")
- if len(startupDelay) > 0 {
- log.Printf("[DEBUG] Setting startup delay to %#v", startupDelay)
-
- tmpInt, err := strconv.Atoi(startupDelay)
- if err == nil {
- time.Sleep(time.Duration(tmpInt) * time.Second)
- } else {
- log.Printf("[WARNING] Env SHUFFLE_ORBORUS_STARTUP_DELAY must be a number, not '%s'. Using default.", startupDelay)
- }
- }
-
- // Auto enables pipelines IF they are not mentioned
- if len(os.Getenv("SHUFFLE_SKIP_PIPELINES")) == 0 {
- tenzirDisabled = false
- os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
- os.Setenv("SHUFFLE_PIPELINE_ENABLED", "true")
- }
-
- if os.Getenv("SHUFFLE_SKIP_PIPELINES") != "true" && os.Getenv("SHUFFLE_PIPELINE_ENABLED") != "false" {
- // Run in 15 seconds in a goroutine
- go func() {
- time.Sleep(15 * time.Second)
- log.Printf("[INFO] Auto-downloading Sigma rules during startup")
- ruleType := "sigma"
- err := handleFileCategoryChange(ruleType)
- if err != nil {
- log.Printf("[WARNING] Failed downloading %s rules: %s", ruleType, err)
- }
- }()
- }
-
- log.Println("[INFO] Setting up execution environment for env '%s'", environment)
- // //FIXME
- if baseUrl == "" {
- baseUrl = "https://shuffler.io"
- }
-
- if len(orborusUuid) == 0 {
- orborusUuid = uuid.NewV4().String()
- }
-
- //if orgId == "" {
- // log.Printf("[ERROR] Org not defined. Set variable ORG_ID based on your org")
- // os.Exit(3)
- //}
- if environment == "" {
- log.Printf("[ERROR] Environment not defined. Set variable ENVIRONMENT_NAME to configure it.")
- os.Exit(3)
- }
-
- if timezone == "" {
- timezone = "Europe/Amsterdam"
- }
-
- log.Printf("[INFO] Using environment '%s' with timezone %s", environment, timezone)
-
- if len(os.Getenv("SHUFFLE_ORBORUS_PULL_TIME")) > 0 {
- log.Printf("[INFO] Trying to set Orborus sleep time between polls to %s", os.Getenv("SHUFFLE_ORBORUS_PULL_TIME"))
-
- tmpInt, err := strconv.Atoi(os.Getenv("SHUFFLE_ORBORUS_PULL_TIME"))
- if err == nil {
- sleepTime = tmpInt
- }
- }
-
- // Handle Cleanup - made it cleanup by default
- if strings.ToLower(os.Getenv("SHUFFLE_CONTAINER_AUTO_CLEANUP")) != "false" && os.Getenv("CLEANUP") == "" {
- cleanupEnv = "true"
- }
-
- if len(cleanupEnv) > 0 {
- log.Printf("[DEBUG] Verbose mode. NOT cleaning up. Cleanup env: %s", cleanupEnv)
- }
-
- // Default to 120 instead of default 30
- if len(os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")) == 0 {
- os.Setenv("SHUFFLE_APP_SDK_TIMEOUT", "120")
- }
-
- workerTimeout := 600
- if workerTimeoutEnv != "" {
- tmpInt, err := strconv.Atoi(workerTimeoutEnv)
- if err == nil {
- workerTimeout = tmpInt
- } else {
- log.Printf("[WARNING] Env SHUFFLE_ORBORUS_EXECUTION_TIMEOUT must be a number, not %s", workerTimeoutEnv)
- }
-
- log.Printf("[INFO] Cleanup process running every %d seconds", workerTimeout)
- }
-
- if concurrencyEnv != "" {
- //var concurrencyEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY")
- tmpInt, err := strconv.Atoi(concurrencyEnv)
- if err == nil {
- maxConcurrency = tmpInt
- log.Printf("[INFO] Max workflow execution concurrency set to %d", maxConcurrency)
- } else {
- log.Printf("[WARNING] Env SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY must be a number, not %s. Defaulted to %d", workerTimeoutEnv, maxConcurrency)
- }
- }
-
- if len(os.Getenv("DOCKER_HOST")) > 0 {
- log.Printf("[DEBUG] Running docker with socket proxy %s instead of default", os.Getenv("DOCKER_HOST"))
-
- } else {
- log.Printf(`[DEBUG] Running docker with default socket /var/run/docker.sock or `)
- }
-
- ctx := context.Background()
- // Run by default from now
- //commenting for now as its stoppoing minikube
-
- log.Printf("[INFO] Running towards %s (BASE_URL) with environment name %s", baseUrl, environment)
-
- if environment == "" {
- environment = "onprem"
- log.Printf("[WARNING] Defaulting to environment name %s. Set environment variable ENVIRONMENT_NAME to change. This should be the same as in the frontend action.", environment)
- }
-
- if pipelineUrl == "" {
- pipelineUrl = "http://localhost:5160"
-
- // Find the IP in baseUrl. Base format is http://:
- if baseUrl != "" && !strings.Contains(baseUrl, "shuffle") && !strings.Contains(baseUrl, "localhost") && !strings.Contains(baseUrl, "run.app") {
- urlSplit := strings.Split(baseUrl, "://")
- if len(urlSplit) > 1 {
- // Find the IP
- ipSplit := strings.Split(urlSplit[1], ":")
- if len(ipSplit) > 0 {
- pipelineUrl = fmt.Sprintf("http://%s:5160", ipSplit[0])
- }
- }
- }
-
- if len(containerId) > 0 {
- pipelineUrl = "http://tenzir-node:5160"
- }
-
- log.Printf("[WARNING] SHUFFLE_PIPELINE_URL not set, falling back to default URL: %s. If BASE_URL is set, we use the external IP for that", pipelineUrl)
- os.Setenv("SHUFFLE_PIPELINE_URL", pipelineUrl)
- }
-
- // FIXME - during init, BUILD and/or LOAD worker and app_sdk
- // Build/load app_sdk so it can be loaded as 127.0.0.1:5000/walkoff_app_sdk
- log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
-
- initializeImages()
- workerImage := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
- if len(newWorkerImage) > 0 {
- workerImage = newWorkerImage
- }
-
- if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" {
-
- if isKubernetes != "true" {
- checkSwarmService(ctx)
- }
-
- log.Printf("[DEBUG] Cleaning up containers from previous run")
- cleanupExistingNodes(ctx)
- time.Sleep(time.Duration(5) * time.Second)
-
- log.Printf("[DEBUG] Deploying worker image %s to swarm", workerImage)
-
- runString := "Run: \"docker service ls\" for more info"
-
- if isKubernetes != "true" {
- deployServiceWorkers(workerImage)
-
- err := setBackendToSwarmNetwork(ctx)
- if err != nil {
- log.Printf("[WARNING] Failed setting backend to swarm network: %s", err)
- }
-
- } else {
- deployK8sWorker(workerImage, "shuffle-workers", []string{})
- runString = "Run: \"kubectl get pods\" for more info"
- }
-
- log.Printf("[DEBUG] Waiting 45 seconds to ensure workers are deployed. %s", runString)
- time.Sleep(time.Duration(45) * time.Second)
-
- //deployServiceWorkers(workerImage)
- }
-
- zombiecheck(ctx, workerTimeout)
-
- client := shuffle.GetExternalClient(baseUrl)
- fullUrl := fmt.Sprintf("%s/api/v1/workflows/queue", baseUrl)
-
- // Increases default concurrency to 50 for swarm
- if maxConcurrency < 50 && (swarmConfig == "run" || swarmConfig == "swarm") {
- fullUrl += "?amount=50"
- }
-
- if isKubernetes == "true" {
- log.Printf("[INFO] Finished configuring kubernetes environment. Connecting to %s", fullUrl)
- } else {
- log.Printf("[INFO] Finished configuring docker environment. Connecting to %s", fullUrl)
- }
-
- forwardData := bytes.NewBuffer([]byte{})
- forwardMethod := "POST"
-
- req, err := http.NewRequest(
- forwardMethod,
- fullUrl,
- forwardData,
- )
-
- if err != nil {
- log.Printf("[ERROR] Failed making request builder during init: %s", err)
- return
- }
-
- zombiecounter := 0
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Org-Id", environment)
- if len(auth) > 0 {
- req.Header.Add("Authorization", auth)
- }
-
- if len(org) > 0 {
- req.Header.Add("Org", org)
- }
-
- if len(orborusLabel) > 0 {
- log.Printf("[DEBUG] Sending with Label '%s'", orborusLabel)
- req.Header.Add("X-Orborus-Label", orborusLabel)
- }
-
- if swarmConfig != "run" && swarmConfig != "swarm" {
- req.Header.Add("X-Orborus-Runmode", "Default")
- } else {
- req.Header.Add("X-Orborus-Runmode", "Docker Swarm")
- }
-
- if os.Getenv("SHUFFLE_MAX_CPU") != "" {
- // parse
- tmpInt, err := strconv.Atoi(os.Getenv("SHUFFLE_MAX_CPU"))
- if err == nil {
- maxCPUPercent = tmpInt
- }
- }
-
- swarmPollingTime := time.Now()
- swarmRequestsMade := 0
- swarmControlMode := false
- if os.Getenv("SHUFFLE_SWARM_CONTROL_MODE") == "true" {
- swarmControlMode = true
- }
-
- log.Printf("[INFO] Waiting for executions at %s with Environment %#v", fullUrl, environment)
-
- hasStarted := false
- for {
- if req.Method == "POST" {
- // Should find data to send (memory etc.)
-
- // Create timeout of max 4 seconds just in case
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- // Marshal and set body
- orborusStats := getOrborusStats(ctx)
-
- pipelinePayload, pipelineerr := sendPipelineHealthStatus()
-
- if pipelineerr != nil {
- // Too verbose to be enabled.
- //log.Printf("[ERROR] Failed sending pipeline health status: %s", pipelineerr)
- }
-
- orborusStats.DataLake = pipelinePayload
- jsonData, err := json.Marshal(orborusStats)
- if err == nil {
- req.Body = ioutil.NopCloser(bytes.NewBuffer(jsonData))
- } else {
- log.Printf("[ERROR] Failed marshalling. Maybe max 4 second timeout? %s", err)
- }
-
- if int(orborusStats.CPUPercent) > maxCPUPercent {
- log.Printf("[DEBUG] CPU usage is at %f%%. This is more than the max limit the machine should be running at (%d). Waiting before continue.", orborusStats.CPUPercent, maxCPUPercent)
- time.Sleep(time.Duration(sleepTime) * time.Second)
- continue
- }
- }
-
- newresp, err := client.Do(req)
- if err != nil {
- log.Printf("[WARNING] Failed making request to %s: %s", fullUrl, err)
-
- zombiecounter += 1
- if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(ctx, workerTimeout)
- zombiecounter = 0
- }
- time.Sleep(time.Duration(sleepTime) * time.Second)
- continue
- }
-
- //defer newresp.Body.Close()
- if newresp.StatusCode == 405 {
- log.Printf("[WARNING] Received 405 from %s. This is likely due to a misconfigured base URL. Automatically swapping to GET request (backwards compatibility)", fullUrl)
-
- req.Method = "GET"
- req.Body = nil
-
- //time.Sleep(time.Duration(sleepTime) * time.Second)
- continue
- }
-
- body, err := ioutil.ReadAll(newresp.Body)
- if err != nil {
- log.Printf("[ERROR] Failed reading body from Shuffle: %s", err)
- zombiecounter += 1
- if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(ctx, workerTimeout)
- zombiecounter = 0
- }
- time.Sleep(time.Duration(sleepTime) * time.Second)
- continue
- }
-
- // Controls Leader/Follower mode
- if newresp.StatusCode == 409 {
- log.Printf("[INFO] Another Orborus is already handling jobs. Polling every 30 seconds in case Leader stops. Resp: %s", string(body))
- time.Sleep(time.Duration(30) * time.Second)
- continue
- } else if newresp.StatusCode != 200 {
- log.Printf("[ERROR] Backend connection failed for url '%s', or is missing (%d): %s", fullUrl, newresp.StatusCode, string(body))
- } else {
- if !hasStarted {
- log.Printf("[DEBUG] Starting iteration on environment %#v (default = Shuffle). Got statuscode %d from backend on first request", environment, newresp.StatusCode)
- }
-
- if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" && os.Getenv("SHUFFLE_SCALE_REPLICAS") == "" {
- //go AutoScale(ctx)
- }
- hasStarted = true
- }
-
- var executionRequests shuffle.ExecutionRequestWrapper
- err = json.Unmarshal(body, &executionRequests)
- if err != nil {
- log.Printf("[WARNING] Failed executionrequest in queue unmarshaling: %s", err)
- sleepTime = 10
- zombiecounter += 1
- if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(ctx, workerTimeout)
- zombiecounter = 0
- }
- time.Sleep(time.Duration(sleepTime) * time.Second)
- continue
- }
-
- if hasStarted && len(executionRequests.Data) > 0 {
- //log.Printf("[INFO] Body: %s", string(body))
- // Type string `json:"type"`
- }
-
- // FIXME: Add features here for orborus & worker to
- // do things on behalf of backend
- var toBeRemoved shuffle.ExecutionRequestWrapper
- if len(executionRequests.Data) > 0 {
- newrequests := []shuffle.ExecutionRequest{}
-
- // Deduplicating in case same job shows up multiple times
- // This is specifically to handle data pipelines better
- deduplicatedJobs := []shuffle.ExecutionRequest{}
- for _, incRequest := range executionRequests.Data {
- if !strings.Contains(incRequest.Type, "DOCKER") && !strings.Contains(incRequest.Type, "PIPELINE") && !strings.Contains(incRequest.Type, "SIGMA") && !strings.Contains(incRequest.Type, "TENZIR") {
- deduplicatedJobs = append(deduplicatedJobs, incRequest)
- continue
- }
-
- found := false
- for _, dedupRequest := range deduplicatedJobs {
- if incRequest.ExecutionArgument == dedupRequest.ExecutionArgument && incRequest.Type == dedupRequest.Type {
- found = true
- break
- }
- }
-
- if found {
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
- continue
- }
-
- deduplicatedJobs = append(deduplicatedJobs, incRequest)
- }
-
- executionRequests.Data = deduplicatedJobs
- for _, incRequest := range executionRequests.Data {
-
- // Looking for specific jobs
- if incRequest.Type == "PIPELINE_CREATE" || incRequest.Type == "PIPELINE_START" || incRequest.Type == "PIPELINE_STOP" || incRequest.Type == "PIPELINE_DELETE" || incRequest.Type == "PIPELINE_UPDATE" {
- log.Printf("[INFO] Handling pipeline request from backend: '%s' with argument '%s'", incRequest.Type, incRequest.ExecutionArgument)
-
- os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
- os.Setenv("SHUFFLE_PIPELINE_ENABLED", "true")
- tenzirDisabled = false
-
- // Running NEW or editing pipelines
- err := handlePipeline(incRequest)
- if err != nil {
- log.Printf("[ERROR] Failed handling pipeline ('%s' '%s'): %s. Deleting job anyway.", incRequest.Type, incRequest.ExecutionSource, err)
- }
-
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
- } else if incRequest.Type == "DOCKER_IMAGE_DOWNLOAD" {
- log.Printf("[INFO] Re-downloading new image(s) due to backend request: %#v", incRequest.ExecutionArgument)
-
- if len(incRequest.ExecutionArgument) > 0 {
- go handleBackendImageDownload(ctx, incRequest.ExecutionArgument)
- } else {
- log.Printf("[ERROR] No image name provided for download. Removing job from queue.")
- }
-
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
-
- } else if incRequest.Type == "CATEGORY_UPDATE" {
- os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
-
- tenzirDisabled = false
- err = handleFileCategoryChange("sigma")
- if err != nil {
- log.Printf("[ERROR] Failed to download the file category: %s", err)
- }
-
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
-
- } else if incRequest.Type == "DISABLE_SIGMA_FOLDER" {
- log.Printf("[INFO] Got job to disable sigma rules")
-
- err = removeFileCategory("sigma")
- if err != nil {
- log.Printf("[ERROR] Failed to disable the sigma rules: %s", err)
- }
-
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
-
- } else if incRequest.Type == "DISABLE_SIGMA_FILE" {
- fileName := incRequest.ExecutionArgument
- log.Printf("[INFO] Got job to disable sigma file %s", fileName)
-
- err = disableRule(fileName)
- if err != nil {
- log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err)
- }
-
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
-
- } else if incRequest.Type == "ENABLE_SIGMA_FILE" {
- fileName := incRequest.ExecutionArgument
- log.Printf("[INFO] Got job to enable sigma file %s", fileName)
-
- err = enableRule(fileName)
- if err != nil {
- log.Printf("[ERROR] Failed to disable the sigma file %s, reason: %s", fileName, err)
- }
-
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
- } else if incRequest.Type == "START_TENZIR" {
- log.Printf("[INFO] Got job to start tenzir")
-
- // Manual command = overrides to allow starting of Tenzir from the frontend anyway.
- //os.Setenv("SHUFFLE_SKIP_PIPELINES", "false")
- tenzirDisabled = false
-
- // Removed either way
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
-
- err := deployTenzirNode()
- if err != nil {
- if strings.Contains(fmt.Sprintf("%s", err), "node available") {
- // Disabling until UI is updated
- //os.Setenv("SHUFFLE_SKIP_PIPELINES", "true")
- //tenzirDisabled = true
-
- log.Printf("[ERROR] Failed to start tenzir, reason: %s", err)
- err = shuffle.CreateOrgNotification(
- ctx,
- fmt.Sprintf("Failed to start Tenzir: %s", err),
- fmt.Sprintf("Tenzir failed to start due to: %s", err),
- fmt.Sprintf("/detections/Sigma"),
- org,
- true,
- "LOW",
- "TENZIR_START",
- )
-
- if err != nil {
- log.Printf("[ERROR] Failed to send notification: %s", err)
- return
- }
- }
- }
-
- } else {
- if debug {
- log.Printf("[DEBUG] Passing execution ID request to normal queue: %#v", incRequest.ExecutionId)
- }
-
- newrequests = append(newrequests, incRequest)
- }
- }
-
- if len(toBeRemoved.Data) > 0 {
- err = sendRemoveRequest(client, toBeRemoved, baseUrl, environment, auth, org, sleepTime)
- if err != nil {
- log.Printf("[ERROR] Failed sending remove request: %s", err)
- } else {
- toBeRemoved.Data = []shuffle.ExecutionRequest{}
- }
- }
-
- // Remove the download image request
- executionRequests.Data = newrequests
- }
-
- // Skipping throttling with swarm
- if swarmConfig != "run" && swarmConfig != "swarm" {
- if len(executionRequests.Data) == 0 {
- zombiecounter += 1
- if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(ctx, workerTimeout)
- zombiecounter = 0
- }
- time.Sleep(time.Duration(sleepTime) * time.Second)
- continue
- }
-
- // Anything below here verifies concurrency
- executionCount = getRunningWorkers(ctx, workerTimeout)
- if executionCount >= maxConcurrency {
- if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(ctx, workerTimeout)
- zombiecounter = 0
- }
- time.Sleep(time.Duration(sleepTime) * time.Second)
- continue
- }
-
- allowed := maxConcurrency - executionCount
- if len(executionRequests.Data) > allowed {
- log.Printf("[WARNING] Throttle - Cutting down requests from %d to %d (MAX: %d, CUR: %d)", len(executionRequests.Data), allowed, maxConcurrency, executionCount)
- executionRequests.Data = executionRequests.Data[0:allowed]
- }
- } else if swarmControlMode && (swarmConfig == "run" || swarmConfig == "swarm") {
- // any reason it is not maxConcurrency instead of
- // hardcoded 50?
- if len(executionRequests.Data) > 50 {
- executionRequests.Data = executionRequests.Data[0:50]
- }
-
- if swarmRequestsMade > 100 && time.Since(swarmPollingTime).Seconds() > 5 {
- log.Printf("[DEBUG] Swarm requests made: %d", swarmRequestsMade)
- time.Sleep(time.Duration(sleepTime) * time.Second)
-
- swarmPollingTime = time.Now()
- swarmRequestsMade = 0
- }
-
- swarmRequestsMade += len(executionRequests.Data)
- }
-
- // New, abortable version. Should check executionid and remove everything else
- for _, execution := range executionRequests.Data {
- if len(execution.ExecutionArgument) > 0 {
- log.Printf("[INFO] Argument: %s", execution.ExecutionArgument)
- }
-
- if execution.Type == "schedule" {
- log.Printf("[INFO] Schedule type! Weird deployment. Type: %s", execution.Type)
- continue
- }
-
- if len(execution.ExecutionId) == 0 {
- log.Printf("[WARNING] Execution ID is empty: %#v", execution)
- continue
- }
-
- if execution.Status == "ABORT" || execution.Status == "FAILED" {
- log.Printf("[INFO][%s] Executionstatus issue: ", execution.ExecutionId, execution.Status)
- }
-
- if shuffle.ArrayContains(executionIds, execution.ExecutionId) {
- log.Printf("[INFO][%s] Execution already handled (rerunning old execution)", execution.ExecutionId)
- toBeRemoved.Data = append(toBeRemoved.Data, execution)
-
- // Should check when last this was ran, and if it's more than 10 minutes ago and it's not finished, we should run it again?
- /*
- if swarmConfig != "run" && swarmConfig != "swarm" {
- continue
- }
- */
- }
-
- // Now, how do I execute this one?
- containerName := fmt.Sprintf("worker-%s", execution.ExecutionId)
- env := []string{
- fmt.Sprintf("AUTHORIZATION=%s", execution.Authorization),
- fmt.Sprintf("EXECUTIONID=%s", execution.ExecutionId),
- fmt.Sprintf("ENVIRONMENT_NAME=%s", environment),
- fmt.Sprintf("BASE_URL=%s", baseUrl),
- fmt.Sprintf("CLEANUP=%s", cleanupEnv),
- fmt.Sprintf("TZ=%s", timezone),
- fmt.Sprintf("SHUFFLE_PASS_APP_PROXY=%s", os.Getenv("SHUFFLE_PASS_APP_PROXY")),
- fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", os.Getenv("SHUFFLE_SWARM_CONFIG")),
- fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")),
- fmt.Sprintf("SHUFFLE_BASE_IMAGE_NAME=%s", os.Getenv("SHUFFLE_BASE_IMAGE_NAME")),
- fmt.Sprintf("SHUFFLE_ALLOW_PACKAGE_INSTAL=%s", os.Getenv("SHUFFLE_ALLOW_PACKAGE_INSTALL")),
- }
-
- //log.Printf("Running worker with proxy? %s", os.Getenv("SHUFFLE_PASS_WORKER_PROXY"))
- if strings.ToLower(os.Getenv("SHUFFLE_PASS_WORKER_PROXY")) == "true" {
- env = append(env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY")))
- env = append(env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY")))
- env = append(env, fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY")))
- }
-
- if dockerApiVersion != "" {
- env = append(env, fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion))
- }
-
- if len(os.Getenv("DOCKER_HOST")) > 0 {
- env = append(env, fmt.Sprintf("DOCKER_HOST=%s", os.Getenv("DOCKER_HOST")))
- }
-
- if len(os.Getenv("SHUFFLE_MEMCACHED")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_MEMCACHED=%s", os.Getenv("SHUFFLE_MEMCACHED")))
- }
-
- if len(os.Getenv("SHUFFLE_CLOUDRUN_URL")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_CLOUDRUN_URL=%s", os.Getenv("SHUFFLE_CLOUDRUN_URL")))
- }
-
- if len(os.Getenv("SHUFFLE_SKIPSSL_VERIFY")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_SKIPSSL_VERIFY=%s", os.Getenv("SHUFFLE_SKIPSSL_VERIFY")))
- }
-
- if len(os.Getenv("SHUFFLE_DEBUG_MEMORY")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_DEBUG_MEMORY=%s", os.Getenv("SHUFFLE_DEBUG_MEMORY")))
- }
-
- // Look for volume binds
- if len(os.Getenv("SHUFFLE_VOLUME_BINDS")) > 0 {
- //log.Printf("[DEBUG] Added volume binds: %s", os.Getenv("SHUFFLE_VOLUME_BINDS"))
- env = append(env, fmt.Sprintf("SHUFFLE_VOLUME_BINDS=%s", os.Getenv("SHUFFLE_VOLUME_BINDS")))
- }
-
- if len(os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_APP_SDK_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")))
- }
-
- // Setting up internal proxy config for Shuffle -> shuffle comms
- overrideHttpProxy := os.Getenv("SHUFFLE_INTERNAL_HTTP_PROXY")
- overrideHttpsProxy := os.Getenv("SHUFFLE_INTERNAL_HTTPS_PROXY")
- if len(overrideHttpProxy) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_INTERNAL_HTTP_PROXY=%s", overrideHttpProxy))
- }
-
- if len(overrideHttpsProxy) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_INTERNAL_HTTPS_PROXY=%s", overrideHttpsProxy))
- }
-
- if len(os.Getenv("SHUFFLE_MAX_SWARM_NODES")) > 0 {
- env = append(env, fmt.Sprintf("SHUFFLE_MAX_SWARM_NODES=%s", os.Getenv("SHUFFLE_MAX_SWARM_NODES")))
- }
-
- err = deployWorker(workerImage, containerName, env, execution)
- zombiecounter += 1
- if err == nil {
- //log.Printf("[DEBUG] ExecutionID %s was deployed and to be removed from queue.", execution.ExecutionId)
- toBeRemoved.Data = append(toBeRemoved.Data, execution)
- executionIds = append(executionIds, execution.ExecutionId)
- } else {
- log.Printf("[WARNING][%s] Failed to deploy: %s", execution.ExecutionId, err)
-
- if strings.Contains(err.Error(), "already exists") {
- toBeRemoved.Data = append(toBeRemoved.Data, execution)
- executionIds = append(executionIds, execution.ExecutionId)
- } else if strings.Contains(err.Error(), "No such image") {
- // Download the image
-
- if isKubernetes == "true" {
- log.Printf("[DEBUG] Skipping image pull of '%s' because Kubernetes does it in realtime instead", workerImage)
- } else {
- log.Printf("[DEBUG] Re-pulling image %s as it doesn't exist, and is necessary for worker to run (autofix)", workerImage)
- pullOptions := image.PullOptions{}
- _, err = dockercli.ImagePull(ctx, workerImage, pullOptions)
- if err != nil {
- log.Printf("[ERROR] Failed to pull image %s: %s", workerImage, err)
- }
- }
- }
- }
- }
-
- // Removes handled workflows (worker is made)
- //log.Printf("\n\n[INFO] Removing %d executions from queue\n\n", len(toBeRemoved.Data))
- if len(toBeRemoved.Data) > 0 {
-
- err = sendRemoveRequest(client, toBeRemoved, baseUrl, environment, auth, org, sleepTime)
- if err != nil {
- log.Printf("[ERROR] Failed to remove executions from queue: %s", err)
- }
-
- }
- time.Sleep(time.Duration(sleepTime) * time.Second)
- }
-}
-
-// Tenzir command samples
-// docker pull ghcr.io/dominiklohmann/tenzir-arm64:latest
-// docker tag ghcr.io/dominiklohmann/tenzir-arm64:latest tenzir/tenzir:latest
-
-// Read from Cache and send it to a webhook
-// docker run tenzir/tenzir:latest 'from http://192.168.86.44:5002/api/v1/orgs/7e9b9007-5df2-4b47-bca5-c4d267ef2943/cache/CIDR%20ranges?type=text&authorization=cec9d01f-09b2-4419-8a0a-76c6046e3fef read lines | to http://192.168.86.44:5002/api/v1/hooks/webhook_665ace5f-f27b-496a-a365-6e07eb61078c write lines'
-func handlePipeline(incRequest shuffle.ExecutionRequest) error {
-
- log.Printf("[INFO] Pipeline: '%s' with source '%s'", incRequest.Type, incRequest.ExecutionSource)
-
- err := deployTenzirNode()
- if err != nil {
- log.Printf("[ERROR] Failed to deploy the pipeline, reason: %s", err)
- return err
- }
-
- // no need of execution arguments for STOP and DELETE
- if (incRequest.Type != "PIPELINE_STOP" && incRequest.Type != "PIPELINE_DELETE") && len(incRequest.ExecutionArgument) == 0 {
- log.Printf("[ERROR] No execution argument found for pipeline type %s. Skipping", incRequest.Type)
-
- return errors.New("no execution argument found for pipeline create. Skipping")
- }
-
- identifier := strings.ToLower(strings.ReplaceAll(incRequest.ExecutionSource, " ", "-"))
- if !strings.HasPrefix(strings.ToLower(incRequest.ExecutionSource), "shuffle") {
- identifier = fmt.Sprintf("shuffle-%s", strings.ToLower(strings.ReplaceAll(incRequest.ExecutionSource, " ", "-")))
- }
-
- command := incRequest.ExecutionArgument
- pipelines = []shuffle.PipelineInfo{}
- if incRequest.Type == "PIPELINE_CREATE" {
- log.Printf("[INFO] Should delete -> recreate new pipeline with id %#v", identifier)
- //err := deployPipeline(image, identifier, command)
- _, err := createPipeline(command, identifier)
- if err != nil {
- log.Printf("[ERROR] Failed to create pipeline: %s", err)
- return err
- }
- } else if incRequest.Type == "PIPELINE_DELETE" || incRequest.Type == "PIPELINE_STOP" {
- pipelineId := incRequest.ExecutionId
- log.Printf("[INFO] Should delete pipeline %#v. PipelineID: %s", identifier, pipelineId)
- //pipelineId, err := searchPipeline(identifier)
- //if err != nil {
- //}
-
- err = deletePipeline(pipelineId)
- if err != nil {
- log.Printf("[ERROR] Failed Deleting Pipeline %s", err)
- return err
- }
-
- /*
- } else if incRequest.Type == "PIPELINE_STOP" {
- log.Printf("[INFO] Should stop the pipeline %#v", identifier)
- pipelineId, err := searchPipeline(identifier)
- if err != nil {
- log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err)
- toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
- return err
- }
- _, err = updatePipelineState(command, pipelineId, "stop")
- if err != nil {
- log.Printf("[ERROR] Failed to stop Pipeline: %s reason:%s ", pipelineId, err)
- return err
- } else {
- log.Printf("[INFO] Successfully stopped the Pipeline: %s", pipelineId)
- }
- */
-
- } else if incRequest.Type == "PIPELINE_START" {
- log.Printf("[INFO] Should start the pipeline %#v", identifier)
- pipelineId, err := searchPipeline(identifier)
- if err != nil {
- if err.Error() == "no existing pipeline found with name" {
- log.Printf("[INFO] Starting a new pipeline with command '%s' and identifier '%s'", command, identifier)
- var createErr error
- pipelineId, createErr = createPipeline(command, identifier)
- if createErr != nil {
- return createErr
- }
- } else {
- log.Printf("[ERROR] Failed searching for Pipeline with name %s reason:%s ", identifier, err)
- return err
- }
- }
-
- log.Printf("[INFO] Starting existing pipeline with ID %s", pipelineId)
- _, err = updatePipelineState(command, pipelineId, "start")
- if err != nil {
- log.Printf("[ERROR] Failed to start Pipeline: %s reason:%s ", pipelineId, err)
- return err
- } else {
- log.Printf("[INFO] Successfully started pipeline: %s", pipelineId)
- }
-
- } else {
- log.Printf("[ERROR] Unknown type for pipeline: %s", incRequest.Type)
- return errors.New("unknown type for pipeline")
- }
-
- return nil
-}
-
-func deployTenzirNode() error {
- // Specifically for standalone tenzir
- if os.Getenv("SHUFFLE_PIPELINE_STANDALONE") == "true" {
- return nil
- }
-
- // Disabled all pipeline features
- if os.Getenv("SHUFFLE_SKIP_PIPELINES") == "true" {
- return errors.New("Pipelines are disabled by user with SHUFFLE_SKIP_PIPELINES (1)")
- }
-
- if isKubernetes == "true" {
- return errors.New("Tenzir not implemented for k8s")
- }
-
- err := checkTenzirNode()
- if err == nil {
- return nil
- }
-
- ctx := context.Background()
- cacheKey := "tenzir-key"
- _, err = shuffle.GetCache(ctx, cacheKey)
- if err == nil {
- return nil
- }
-
- imageName := "frikky/shuffle:tenzir"
- if os.Getenv("TENZIR_IMAGE_NAME") != "" {
- imageName = os.Getenv("TENZIR_IMAGE_NAME")
- log.Printf("[INFO] Using custom Tenzir image name: %s", imageName)
- }
-
- containerName := "tenzir-node"
- containerStartOptions := container.StartOptions{}
-
- containerInfo, err := dockercli.ContainerInspect(ctx, containerName)
- if err != nil {
- if dockerclient.IsErrNotFound(err) {
- // Create network if it doesn't exist
- networkName := "tenzir-network"
- networkSubnet := "192.168.102.0/24"
- networkGateway := "192.168.102.1"
-
- err = createNetworkIfNotExists(ctx, networkName, networkSubnet, networkGateway)
- if err != nil {
- log.Printf("[ERROR] Failed to create network %s: %s", networkName, err)
- //return err
- }
-
- // Trying to connect orborus to the tenzir network as well
- err = dockercli.NetworkConnect(ctx, networkName, containerId, nil)
- if err != nil {
- log.Printf("[ERROR] Error connecting tenzir container to network: %s", err)
- }
-
- // Check if image exists
- _, _, err := dockercli.ImageInspectWithRaw(ctx, imageName)
- if dockerclient.IsErrNotFound(err) {
- log.Printf("[DEBUG] Pulling image %s. This may take a while.", imageName)
- pullOptions := image.PullOptions{}
- out, err := dockercli.ImagePull(ctx, imageName, pullOptions)
- if err != nil {
- log.Printf("[ERROR] Failed to pull the Tenzir image: %s", err)
- return err
- }
- defer out.Close()
-
- io.Copy(io.Discard, out)
- } else if err != nil {
- return err
- }
-
- err = createAndStartTenzirNode(ctx, containerName, imageName, containerStartOptions)
- if err != nil {
- return err
- }
- } else {
- return err
- }
- } else {
- if !containerInfo.State.Running {
- log.Printf("[DEBUG] Tenzir Node exists, but is not running. Restarting it.")
- err := dockercli.ContainerStart(ctx, containerName, containerStartOptions)
- if err != nil {
- log.Printf("[ERROR] Failed to start Tenzir Node container: %v", err)
- return err
- }
-
- time.Sleep(10 * time.Second)
- log.Printf("[INFO] Waiting for Tenzir to become available ...")
- err = checkTenzirNode()
- if err != nil {
- return err
- }
- }
- }
-
- tenzirStatus := struct {
- ContainerStatus string `json:"container_status"`
- }{
- ContainerStatus: "running",
- }
-
- cacheData, err := json.Marshal(tenzirStatus)
- if err != nil {
- log.Printf("[WARNING] Failed marshalling execution: %s", err)
- }
-
- err = shuffle.SetCache(ctx, cacheKey, cacheData, 1)
- if err != nil {
- log.Printf("[WARNING] Failed updating cache for tenzir: %s", err)
- }
-
- return nil
-}
-
-func createAndStartTenzirNode(ctx context.Context, containerName, imageName string, containerStartOptions container.StartOptions) error {
- healthconfig := &container.HealthConfig{
- Test: []string{"tenzir --connection-timeout=30s --connection-retry-delay=1s 'api /ping'"},
- Interval: 30 * time.Second,
- Retries: 1,
- }
-
- // Ensure restart policy is there
- config := &container.Config{
- Hostname: containerName,
- Cmd: []string{"--commands=web server --mode=dev --bind=0.0.0.0"},
- Image: imageName,
- Healthcheck: healthconfig,
- ExposedPorts: nat.PortSet{
- "5160/tcp": struct{}{},
- "1514/udp": struct{}{},
- "1514/tcp": struct{}{},
- },
- Entrypoint: []string{containerName},
- Env: []string{},
- }
-
- tenzirApikey := os.Getenv("TENZIR_PLUGINS__PLATFORM__API_KEY")
- tenzirControlEndpoint := os.Getenv("TENZIR_PLUGINS__PLATFORM__CONTROL_ENDPOINT")
- tenzirPluginsPlatform := os.Getenv("TENZIR_PLUGINS__PLATFORM__TENANT_ID")
-
- anyFound := false
- if len(tenzirApikey) > 0 {
- config.Env = append(config.Env, fmt.Sprintf("TENZIR_PLUGINS__PLATFORM__API_KEY=%s", tenzirApikey))
- anyFound = true
- }
-
- if len(tenzirControlEndpoint) > 0 {
- config.Env = append(config.Env, fmt.Sprintf("TENZIR_PLUGINS__PLATFORM__CONTROL_ENDPOINT=%s", tenzirControlEndpoint))
- anyFound = true
- }
-
- if len(tenzirPluginsPlatform) > 0 {
- config.Env = append(config.Env, fmt.Sprintf("TENZIR_PLUGINS__PLATFORM__TENANT_ID=%s", tenzirPluginsPlatform))
- anyFound = true
- }
-
- tenzirStorageFolder := os.Getenv("SHUFFLE_STORAGE_FOLDER")
- if len(tenzirStorageFolder) > 0 {
- tenzirStorageFolder = tenzirStorageFolder
-
- if !strings.HasSuffix(tenzirStorageFolder, "/") {
- tenzirStorageFolder = tenzirStorageFolder + "/"
- }
- } else {
- tenzirStorageFolder = "/tmp/"
- log.Printf("[DEBUG] Using base folder %s for Tenzir storage. Change it using environment variable SHUFFLE_STORAGE_FOLDER=/filepath/", tenzirStorageFolder)
- }
-
- if !anyFound {
- //log.Printf("[DEBUG] No Tenzir Plugin environment variables found.")
- } else {
- //log.Printf("[DEBUG] Attempting Tenzir connection with app.tenzir.com tenant '%s'", tenzirPluginsPlatform)
- }
-
- hostConfig := &container.HostConfig{
- PortBindings: nat.PortMap{
- "1514/tcp": []nat.PortBinding{{HostPort: "1514"}},
- "1514/udp": []nat.PortBinding{{HostPort: "1514"}},
- "5160/tcp": []nat.PortBinding{{HostPort: "5160"}},
- },
- Mounts: []mount.Mount{
- {
- Type: "bind",
- Source: tenzirStorageFolder,
- Target: "/tmp",
- },
- /*
- {
- Type: "bind",
- Source: tenzirStorageFolder,
- Target: "/var/log/tenzir/",
- },
- {
- Type: "bind",
- Source: tenzirStorageFolder,
- Target: "/var/cache/tenzir/",
- },
- */
- },
- VolumeDriver: "local",
- RestartPolicy: container.RestartPolicy{
- Name: "always",
- },
- }
-
- if os.Getenv("SHUFFLE_DISABLE_SYSLOG") == "true" {
- hostConfig.PortBindings = nat.PortMap{
- "5160/tcp": []nat.PortBinding{{HostPort: "5160"}},
- }
- }
-
- if skipPipelineMount {
- hostConfig.Mounts = []mount.Mount{}
- }
-
- //networkingConfig := &network.NetworkingConfig{
- // EndpointsConfig: map[string]*network.EndpointSettings{
- // "tenzir-network": {
- // IPAMConfig: &network.EndpointIPAMConfig{
- // IPv4Address: "192.168.102.100",
- // },
- // },
- // },
- //}
-
- networkingConfig := &network.NetworkingConfig{
- EndpointsConfig: map[string]*network.EndpointSettings{
- "tenzir-network": {
- IPAMConfig: nil,
- Aliases: []string{"tenzir-node"},
- },
- },
- }
-
- // FIXME: Is this necessary? Seems to screw up networking:
- // conflicting options: hostname and the network mode
- /*
- if isKubernetes != "true" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" {
- hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
- }
- */
-
- resp, err := dockercli.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, containerName)
- if err != nil {
- if strings.Contains(err.Error(), "path does not exist") {
- log.Printf("[ERROR] Not using permanent pipeline storage as storage folder %s does not exist. If you want permanent storage, create the %s folder then restart Orborus (1). Raw: %s", tenzirStorageFolder, tenzirStorageFolder, err)
- skipPipelineMount = true
- } else {
- log.Printf("[ERROR] Failed to create Tenzir Node container: %v", err)
- }
-
- return err
- }
-
- if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" {
- networkName := "shuffle_swarm_executions"
- err = dockercli.NetworkConnect(ctx, networkName, resp.ID, nil)
- if err != nil {
- log.Printf("[ERROR] Error connecting tenzir container to network: %s", err)
- }
- }
-
- err = dockercli.ContainerStart(ctx, containerName, containerStartOptions)
- if err != nil {
- if strings.Contains(err.Error(), "path does not exist") {
- log.Printf("[ERROR] Not using permanent pipeline storage as storage folder %s does not exist. If you want permanent storage, create the %s folder then restart Orborus (2). Raw: %s", tenzirStorageFolder, tenzirStorageFolder, err)
- skipPipelineMount = true
- } else {
- log.Printf("[ERROR] Failed to START Tenzir Node container: %v", err)
- }
-
- return err
- }
-
- log.Printf("[INFO] Tenzir Node container started successfully. Waiting for it to become available..")
- time.Sleep(20 * time.Second)
- err = checkTenzirNode()
- if err != nil {
- log.Printf("[ERROR] Tenzir connection not available: %s. IF the URL seems wrong, set SHUFFLE_PIPELINE_URL=http://:5160", err)
- return err
- }
-
- log.Printf("[INFO] Successfully deployed Tenzir Node! Setting up default syslog listener on TCP/1514 AND UDP/1514")
-
- command := `load_tcp "0.0.0.0:1514" { read_syslog } | import`
- _, err = createPipeline(command, "default-syslog-tcp-514")
- if err != nil {
- log.Printf("[ERROR] Failed to create tcp syslog pipeline: %s", err)
- return nil
- }
-
- command = `load_udp "0.0.0.0:1514", insert_newlines=true | read_syslog | import`
- _, err = createPipeline(command, "default-syslog-udp-514")
- if err != nil {
- log.Printf("[ERROR] Failed to create udp syslog pipeline: %s", err)
- return nil
- }
-
- return nil
-}
-
-func createNetworkIfNotExists(ctx context.Context, networkName, subnet, gateway string) error {
- listOptions := network.ListOptions{}
- networks, err := dockercli.NetworkList(ctx, listOptions)
- if err != nil {
- return err
- }
-
- for _, network := range networks {
- if network.Name == networkName {
- // Network exists
- return nil
- }
- }
-
- ipamConfig := &network.IPAM{
- Config: []network.IPAMConfig{
- {
- Subnet: subnet,
- Gateway: gateway,
- },
- },
- }
-
- networkCreate := network.CreateOptions{
- //CheckDuplicate: true,
- Driver: "bridge",
- IPAM: ipamConfig,
- }
-
- _, err = dockercli.NetworkCreate(ctx, networkName, networkCreate)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func checkTenzirNode() error {
- if tenzirDisabled && os.Getenv("SHUFFLE_SKIP_PIPELINES") == "true" && os.Getenv("SHUFFLE_PIPELINE_ENABLED") == "false" {
- return errors.New("Pipelines are disabled by user with SHUFFLE_SKIP_PIPELINES (2)")
- }
-
- url := fmt.Sprintf("%s/api/v0/ping", pipelineUrl)
- forwardMethod := "POST"
-
- client := http.Client{
- Timeout: 1 * time.Second,
- }
- req, err := http.NewRequest(forwardMethod, url, nil)
- if err != nil {
- log.Printf("[ERROR] Failed to create HTTP request: %s", err)
- return err
- }
-
- resp, err := client.Do(req)
- if err == nil && resp.StatusCode == http.StatusOK {
- return nil
- }
-
- return fmt.Errorf("Tenzir node is not available due to: %s", err)
-}
-
-func createPipeline(command, identifier string) (string, error) {
-
- //toBeDeleted := false
- /*
- // Pre-checked. No point here
- pipelineId, err := searchPipeline(identifier)
- if err != nil {
- return "", err
- }
- */
-
- url := fmt.Sprintf("%s/api/v0/pipeline/create", pipelineUrl)
- forwardMethod := "POST"
-
- /*
- if err != nil {
- if strings.Contains(fmt.Sprintf("%s", err), "no existing pipeline found") {
- log.Printf("[INFO] No existing pipeline found with id: %s. Creating a new one!", identifier)
- } else {
- log.Printf("[ERROR] Failed to search for existing pipeline but continuing anyway : %s", err)
- }
- } else {
- log.Printf("[INFO] an existing pipeline found with ID: %s. it will be deleted", pipelineId)
- toBeDeleted = true
- }
- */
-
- // if strings.Contains(command, "shuffler.io") {
-
- // } else {
- // var scheme string
- // if strings.Contains(command, "http://") {
- // scheme = "http://"
- // } else if strings.Contains(command, "https://") {
- // scheme = "https://"
- // }
-
- // startIndex := strings.Index(command, scheme)
- // if startIndex != -1 {
- // endIndex := startIndex + len(scheme)
- // endIndex += strings.Index(command[endIndex:], "/")
-
- // command = command[:startIndex] + baseUrl + command[endIndex:]
- // }
- // }
-
- //command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | sigma /var/lib/tenzir/rule.yaml"
- //command = "from file /var/lib/tenzir/sysmon_logs.ndjson read json | import"
-
- // Make sure to escape them
- //if strings.Contains(command, "/") {
- // command = strings.ReplaceAll("\\\"", "", command)
- // command = strings.ReplaceAll(command, "\"", "")
- //}
-
- requestBody := map[string]interface{}{
- "definition": command,
- "name": identifier,
- "hidden": false,
- "retry_delay": "500.0ms",
- "unstoppable": true,
- }
-
- requestBodyJSON, err := json.Marshal(requestBody)
- if err != nil {
- log.Printf("[ERROR] failed marshalling body: %s", err)
- return "", err
- }
-
- forwardData := bytes.NewBuffer(requestBodyJSON)
- req, err := http.NewRequest(
- forwardMethod,
- url,
- forwardData,
- )
-
- if err != nil {
- log.Printf("[ERROR] Failed to create HTTP request: %s", err)
- return "", err
- }
-
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- log.Printf("[ERROR] Failed to send HTTP request: %s", err)
- return "", err
- }
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- log.Printf("[ERROR] Failed reading response body: %s", err)
- return "", err
- }
-
- if strings.Contains(string(body), "error") {
- log.Printf("[ERROR] Pipeline creation error resp (%d): %s", resp.StatusCode, string(body))
- } else {
- log.Printf("[DEBUG] Pipeline creation debug (%d): %s", resp.StatusCode, string(body))
- }
-
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- log.Printf("[DEBUG] status code is %d instead of 200", resp.StatusCode)
- return "", fmt.Errorf("got the status code %d instead of 200", resp.StatusCode)
- }
-
- type PipelineResponse struct {
- ID string `json:"id"`
- Message string `json:"message"`
- Severity string `json:"severity"`
- }
-
- var response PipelineResponse
- if err := json.Unmarshal(body, &response); err != nil {
- log.Printf("[ERROR] Failed unmarshalling response: %s", err)
- return "", err
- }
-
- if response.ID == "" {
- log.Printf("[ERROR] ID not found or empty in response. Severity: %#v, Message: %#v", response.Severity, response.Message)
- return "", errors.New("Pipeline ID not found or empty in the response. See error logs.")
- }
-
- return response.ID, nil
-}
-
-func updatePipelineState(command, pipelineId, action string) (string, error) {
-
- url := fmt.Sprintf("%s/api/v0/pipeline/update", pipelineUrl)
- forwardMethod := "POST"
- requestBody := map[string]interface{}{
- "id": pipelineId,
- "action": action,
-
- /*
- "autostart": map[string]bool{
- "created": true,
- "completed": false,
- "failed": false,
- },
- "autodelete": map[string]bool{
- "completed": false,
- "failed": false,
- "stopped": false,
- },
- */
- }
-
- requestBodyJSON, err := json.Marshal(requestBody)
- if err != nil {
- return "", err
- }
-
- log.Printf("[INFO] Updating pipeline %s with action %s to ensure it starts. Body: %s", pipelineId, action, string(requestBodyJSON))
-
- forwardData := bytes.NewBuffer(requestBodyJSON)
- req, err := http.NewRequest(
- forwardMethod,
- url,
- forwardData,
- )
- if err != nil {
- log.Printf("[ERROR] Failed to update HTTP request: %s", err)
- return "", err
- }
-
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- log.Printf("[ERROR] Failed to send HTTP request: %s", err)
- return "", err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return "", fmt.Errorf("got the status code %d instead of 200", resp.StatusCode)
- }
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return "", err
- }
-
- var responseData struct {
- Pipeline struct {
- State string `json:"state"`
- } `json:"pipeline"`
- }
- if err := json.Unmarshal(body, &responseData); err != nil {
- return "", err
- }
-
- return responseData.Pipeline.State, nil
-}
-
-func deletePipeline(pipelineId string) error {
- requestBody := map[string]string{
- "id": pipelineId,
- }
-
- url := fmt.Sprintf("%s/api/v0/pipeline/delete", pipelineUrl)
- forwardMethod := "POST"
-
- requestBodyJSON, err := json.Marshal(requestBody)
- if err != nil {
- log.Println("[ERROR] failed marshalling request body:", err)
- return err
- }
-
- forwardData := bytes.NewBuffer(requestBodyJSON)
-
- req, err := http.NewRequest(
- forwardMethod,
- url,
- forwardData,
- )
- if err != nil {
- log.Printf("[ERROR] Failed to delete HTTP request: %s", err)
- return err
- }
- req.Header.Set("Content-Type", "application/json")
-
- client := &http.Client{Timeout: 10 * time.Second}
- resp, err := client.Do(req)
- if err != nil {
- log.Printf("[ERROR] Failed to send HTTP request: %s", err)
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != 200 {
- log.Printf("[DEBUG] The deletion of pipeline with ID: %s is unsucessful as status code is NOT 200 !!!", pipelineId)
- return fmt.Errorf("got the status code %d instead of 200", resp.StatusCode)
- }
-
- log.Printf("[INFO] Pipeline with ID: %s deleted successfully", pipelineId)
-
- pipelines = []shuffle.PipelineInfo{}
- return nil
-}
-
-// Lists the pipelines from the API exactly as they are. Definition is set up in Shuffle structs
-func listPipelines() ([]shuffle.PipelineInfo, error) {
- responseData := shuffle.PipelineInfoWrapper{}
-
- if tenzirDisabled {
- return responseData.Pipelines, errors.New("Tenzir is disabled")
- }
-
- var reqBody []byte
- url := fmt.Sprintf("%s/api/v0/pipeline/list", pipelineUrl)
- client := http.Client{
- Timeout: 2 * time.Second,
- }
-
- req, err := http.NewRequest(
- "POST",
- url,
- bytes.NewBuffer(reqBody),
- )
-
- if err != nil {
- return responseData.Pipelines, err
- }
-
- req.Header.Set("Content-Type", "application/json")
- resp, err := client.Do(req)
- if err != nil {
- return responseData.Pipelines, err
- }
-
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return responseData.Pipelines, fmt.Errorf("Got the status code %d instead of 200 from Pipeline node", resp.StatusCode)
- }
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return responseData.Pipelines, err
- }
-
- if err := json.Unmarshal(body, &responseData); err != nil {
- return responseData.Pipelines, err
- }
-
- return responseData.Pipelines, nil
-}
-
-func searchPipeline(identifier string) (string, error) {
- allPipelines, err := listPipelines()
- if err != nil {
- return "", err
- }
-
- for _, pipeline := range allPipelines {
- if pipeline.Name == identifier {
- return pipeline.ID, nil
- }
- }
-
- return "", errors.New("no existing pipeline found with name")
-}
-
-func handleFileCategoryChange(ruleType string) error {
- apiEndpoint := fmt.Sprintf("%s/api/v1/files/namespaces/%s", baseUrl, ruleType)
- req, err := http.NewRequest("GET", apiEndpoint, nil)
- if err != nil {
- return err
- }
-
- if len(pipelineApikey) == 0 {
- //var auth = os.Getenv("AUTH")
- //var org = os.Getenv("ORG")
-
- if len(auth) > 0 && len(org) > 0 {
- pipelineApikey = auth
- } else {
- return errors.New("Shuffle API-key not set for Pipelines: SHUFFLE_PIPELINE_AUTH=")
- }
- }
-
- req.Header.Add("Authorization", "Bearer "+pipelineApikey)
- if len(org) > 0 {
- req.Header.Add("Org-Id", org)
- }
-
- client := shuffle.GetExternalClient(apiEndpoint)
- resp, err := client.Do(req)
- if err != nil {
- return err
- }
-
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("Received non-200 response '%d' from backend URL %s. ", resp.StatusCode, apiEndpoint)
- }
-
- out, err := os.Create("files.zip")
- if err != nil {
- return err
- }
-
- defer out.Close()
- defer os.Remove("files.zip")
- _, err = io.Copy(out, resp.Body)
- if err != nil {
- log.Printf("[ERROR] Failed to io.Copy ZIP file content: %s", err)
- return err
- }
-
- //log.Println("[DEBUG] ZIP file downloaded successfully.")
- tenzirStorageFolder := os.Getenv("SHUFFLE_STORAGE_FOLDER")
- if len(tenzirStorageFolder) == 0 {
- tenzirStorageFolder = "/tmp/"
- }
-
- tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
- sigmaPath := fmt.Sprintf("%s/%s_rules", tenzirStorageFolder, ruleType)
- err = extractZIP("files.zip", sigmaPath)
- if err != nil {
- log.Printf("[ERROR] Failed to extract ZIP file: %s", err)
- return err
- }
-
- log.Printf("[DEBUG] Detection files copied to '%s' successfully.", sigmaPath)
-
- return nil
-}
-
-func extractZIP(zipFile, destDir string) error {
- r, err := zip.OpenReader(zipFile)
- if err != nil {
- return err
- }
-
- // FInd size of the zip
- var totalSize uint64
- for _, f := range r.File {
- totalSize += f.UncompressedSize64
- }
-
- log.Printf("[DEBUG] Total size of the ZIP file: %d bytes", totalSize)
- defer r.Close()
- if err := os.MkdirAll(destDir, 0755); err != nil {
- return err
- }
-
- log.Printf("[DEBUG] Total files to extract: %d", len(r.File))
- for _, f := range r.File {
- // Fix path traversal
- if strings.Contains(f.Name, "..") {
- return fmt.Errorf("illegal file name: %s", f.Name)
- }
-
- err := extractFile(f, destDir)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func extractFile(f *zip.File, destDir string) error {
- rc, err := f.Open()
- if err != nil {
- return err
- }
-
- defer rc.Close()
- path := filepath.Join(destDir, f.Name)
- out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
- if err != nil {
- return err
- }
-
- defer out.Close()
- _, err = io.Copy(out, rc)
- return err
-}
-
-func copyToTenzir(srcPath, destPath string) error {
- containerName := "tenzir-node"
-
- checkCmd := exec.Command("docker", "exec", containerName, "test", "-d", destPath)
- if err := checkCmd.Run(); err == nil {
- rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "rm", "-rf", destPath)
- if err := rmCmd.Run(); err != nil {
- return fmt.Errorf("error removing existing directory in container: %v", err)
- }
- }
-
- cpCmd := exec.Command("docker", "cp", srcPath, fmt.Sprintf("%s:%s", containerName, destPath))
- var out bytes.Buffer
- cpCmd.Stdout = &out
- cpCmd.Stderr = &out
-
- err := cpCmd.Run()
- if err != nil {
- return fmt.Errorf("error copying files: %v, output: %s", err, out.String())
- }
-
- return nil
-}
-
-func removeFileCategory(ruleType string) error {
- tenzirStorageFolder := os.Getenv("SHUFFLE_STORAGE_FOLDER")
- if len(tenzirStorageFolder) == 0 {
- tenzirStorageFolder = "/tmp/"
- }
-
- tenzirStorageFolder = strings.TrimRight(tenzirStorageFolder, "/")
-
- //sigmaPath := "/var/lib/tenzir/sigma_rules/*"
- rulePath := fmt.Sprintf("%s/%s_rules", tenzirStorageFolder, ruleType)
-
- err := os.RemoveAll(rulePath)
- if err != nil {
- return fmt.Errorf("Error removing category files in %s: %v", rulePath, err)
- }
-
- log.Printf("[INFO] Removed all local category data in %s", rulePath)
-
- return nil
-}
-
-// curl https://get.tenzir.app | sh
-func removeFile(fileName string) error {
- containerName := "tenzir-node"
- srcPath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", fileName)
-
- checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath))
- if err := checkSrcCmd.Run(); err != nil {
- // If the file does not exist, simply return nil
- if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
- log.Printf("[ERROR] No such file: %s, nothing to delete\n", srcPath)
- return nil
- }
- return fmt.Errorf("error checking source file: %v", err)
- }
-
- return removePath(containerName, srcPath)
-}
-
-func removePath(containerName, path string) error {
- rmCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("rm -rf %s", path))
- output, err := rmCmd.CombinedOutput()
- if err != nil {
- return fmt.Errorf("error removing path: %v, output: %s", err, output)
- }
- return nil
-}
-
-func sendPipelineHealthStatus() (shuffle.LakeConfig, error) {
- pipelinePayload := shuffle.LakeConfig{
- Enabled: false,
- Pipelines: []shuffle.PipelineInfo{},
- }
-
- if tenzirDisabled {
- return pipelinePayload, nil
- }
-
- // To not spam down the list API too much
- randint := rand.Intn(5)
- if len(pipelines) == 0 || randint == 0 {
- pipelineDef, err := listPipelines()
-
- if err == nil || len(pipelines) > 0 {
- pipelines = pipelineDef
- pipelinePayload.Pipelines = pipelines
- }
- } else {
- pipelinePayload.Pipelines = pipelines
- }
-
- err := deployTenzirNode()
- if err != nil {
- if (!strings.Contains(err.Error(), "SHUFFLE_SKIP_PIPELINES") && !strings.Contains(err.Error(), "Kubernetes not implemented for Tenzir node")) && !strings.Contains(err.Error(), "Tenzir Node is already running") && !strings.Contains(err.Error(), "docker daemon") {
- log.Printf("[ERROR] Tenzir node connection problem: %s", err)
-
- } else {
- //tenzirDisabled = true
- if debug {
- log.Printf("[WARNING] Disabling pipelines: %s. You will need to restart the Orborus to fix this.", err)
- }
-
- }
-
- return pipelinePayload, err
- }
-
- pipelinePayload.Enabled = true
-
- // No direct sending.
- return pipelinePayload, nil
-}
-
-func disableRule(fileName string) error {
- containerName := "tenzir-node"
- srcPath := fmt.Sprintf("/var/lib/tenzir/sigma_rules/%s", fileName)
- destDir := "/var/lib/tenzir/disabled_rules"
- destPath := fmt.Sprintf("%s/%s", destDir, fileName)
-
- checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath))
- if err := checkSrcCmd.Run(); err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
- fmt.Printf("File does not exist: %s\n", srcPath)
- return nil // Nothing to disable
- }
- return fmt.Errorf("error checking source file: %v", err)
- }
-
- checkDestDirCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mkdir -p %s", destDir))
- if err := checkDestDirCmd.Run(); err != nil {
- return fmt.Errorf("error ensuring destination directory exists: %v", err)
- }
-
- moveCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mv %s %s", srcPath, destPath))
- if err := moveCmd.Run(); err != nil {
- return fmt.Errorf("error moving file: %v", err)
- }
-
- fmt.Printf("File %s moved to %s successfully.\n", fileName, destDir)
- return nil
-}
-
-func enableRule(fileName string) error {
- containerName := "tenzir-node"
- srcPath := fmt.Sprintf("/var/lib/tenzir/disabled_rules/%s", fileName)
- destDir := "/var/lib/tenzir/sigma_rules"
- destPath := fmt.Sprintf("%s/%s", destDir, fileName)
-
- checkSrcCmd := exec.Command("docker", "exec", containerName, "sh", "-c", fmt.Sprintf("test -f %s", srcPath))
- if err := checkSrcCmd.Run(); err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
- fmt.Printf("File does not exist: %s\n", srcPath)
- return nil // Nothing to enable
- }
- return fmt.Errorf("error checking source file: %v", err)
- }
-
- checkDestDirCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mkdir -p %s", destDir))
- if err := checkDestDirCmd.Run(); err != nil {
- return fmt.Errorf("error ensuring destination directory exists: %v", err)
- }
- moveCmd := exec.Command("docker", "exec", "-u", "root", containerName, "sh", "-c", fmt.Sprintf("mv %s %s", srcPath, destPath))
- if err := moveCmd.Run(); err != nil {
- return fmt.Errorf("error moving file: %v", err)
- }
-
- fmt.Printf("[DEBUG] File %s moved to %s successfully.\n", fileName, destDir)
- return nil
-}
-
-// Is this ok to do with Docker? idk :)
-func getRunningWorkers(ctx context.Context, workerTimeout int) int {
- //log.Printf("[DEBUG] Getting running workers with API version %s", dockerApiVersion)
- counter := 0
- if isKubernetes == "true" {
- log.Printf("[INFO] Getting running workers in kubernetes")
-
- thresholdTime := time.Now().Add(time.Duration(-workerTimeout) * time.Second)
-
- clientset, _, err := shuffle.GetKubernetesClient()
- if err != nil {
- log.Printf("[ERROR] Failed getting kubernetes client: %s", err)
- return 0
- }
-
- pods, podErr := clientset.CoreV1().Pods(kubernetesNamespace).List(ctx, metav1.ListOptions{
- LabelSelector: "app.kubernetes.io/name=shuffle-worker",
- })
- if podErr != nil {
- log.Printf("[ERROR] Failed getting running workers: %s", podErr)
- return 0
- }
-
- for _, pod := range pods.Items {
- if pod.Status.Phase == "Running" && pod.CreationTimestamp.Time.After(thresholdTime) {
- counter++
- }
- }
-
- if counter > 0 {
- log.Printf("[INFO] Found %d running workers in Orborus", counter)
- }
- } else {
-
- containers, err := dockercli.ContainerList(ctx, container.ListOptions{
- All: true,
- })
-
- // Automatically updates the version
- if err != nil {
- log.Printf("[ERROR] Error getting containers from Docker: %s", err)
-
- newVersionSplit := strings.Split(fmt.Sprintf("%s", err), "version is")
- if len(newVersionSplit) > 1 {
- //dockerApiVersion = strings.TrimSpace(newVersionSplit[1])
- log.Printf("[DEBUG] WANT to change the API version to default to %s?", strings.TrimSpace(newVersionSplit[1]))
- }
-
- return maxConcurrency
- }
-
- currenttime := time.Now().Unix()
-
- for _, container := range containers {
- // Skip random containers. Only handle things related to Shuffle.
- if !strings.Contains(container.Image, baseimagename) {
- shuffleFound := false
- for _, item := range container.Labels {
- if item == "shuffle" {
- shuffleFound = true
- break
- }
- }
-
- // Check image name
- if !shuffleFound {
- continue
- }
- //} else {
- // log.Printf("NAME: %s", container.Image)
- }
-
- for _, name := range container.Names {
- // FIXME - add name_version_uid_uid regex check as well
- if !strings.HasPrefix(name, "/worker") {
- continue
- }
-
- //log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout))
- if container.State == "running" && currenttime-container.Created < int64(workerTimeout) {
- counter += 1
- break
- }
- }
- }
- }
- return counter
-}
-
-// FIXME - add this to remove exited workers
-// Should it check what happened to the execution? idk
-func zombiecheck(ctx context.Context, workerTimeout int) error {
- isK8s := isKubernetes == "true"
-
- executionIds = []string{}
- if swarmConfig == "run" || swarmConfig == "swarm" || isK8s {
- //log.Printf("[DEBUG] Skipping Zombie check due to new execution model (swarm)")
- return nil
- }
-
- log.Println("[INFO] Looking for old containers to remove")
- containers, err := dockercli.ContainerList(ctx, container.ListOptions{
- All: true,
- })
-
- if err != nil {
- log.Printf("[ERROR] Failed creating Containerlist: %s", err)
- return err
- }
-
- containerNames := map[string]string{}
- stopContainers := []string{}
- removeContainers := []string{}
- log.Printf("[INFO] Baseimage: %s, Workertimeout: %d", baseimagename, int64(workerTimeout))
- //baseString := `/bin/sh -c 'python app.py --log-level DEBUG'`
- baseString := `python app.py`
- for _, container := range containers {
- // Skip random containers. Only handle things related to Shuffle.
- if !strings.Contains(container.Image, baseimagename) && !strings.Contains(container.Command, baseString) && !strings.Contains(container.Command, "walkoff") && container.Command != "./worker" {
- shuffleFound := false
- for _, item := range container.Labels {
- if item == "shuffle" {
- shuffleFound = true
- break
- }
- }
-
- // Check image name
- if !shuffleFound {
- //log.Printf("[DEBUG] Zombie container skip: %#v, %s", container.Labels, container.Image)
- continue
- }
- //} else {
- // log.Printf("NAME: %s", container.Image)
- } else {
- //log.Printf("Img: %s", container.Image)
- //log.Printf("Names: %s", container.Names)
- }
-
- for _, name := range container.Names {
- // FIXME - add name_version_uid_uid regex check as well
- if strings.HasPrefix(name, "/shuffle") && !strings.HasPrefix(name, "/shuffle-subflow") {
- continue
- }
-
- currenttime := time.Now().Unix()
- //log.Printf("[INFO] (%s) NAME: %s. TIME: %d", container.State, name, currenttime-container.Created)
-
- // Need to check time here too because a container can be removed the same instant as its created
- if container.State != "running" && currenttime-container.Created > int64(workerTimeout) {
- removeContainers = append(removeContainers, container.ID)
- containerNames[container.ID] = name
- }
-
- // stopcontainer & removecontainer
- //log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout))
- if container.State == "running" && currenttime-container.Created > int64(workerTimeout) {
- stopContainers = append(stopContainers, container.ID)
- containerNames[container.ID] = name
- }
- }
- }
-
- // FIXME - add killing of apps with same execution ID too
- log.Printf("[INFO] Should STOP and remove %d containers.", len(stopContainers))
- var options container.StopOptions
- for _, containername := range stopContainers {
- log.Printf("[INFO] Stopping and removing container %s", containerNames[containername])
- go dockercli.ContainerStop(ctx, containername, options)
- removeContainers = append(removeContainers, containername)
- }
-
- removeOptions := container.RemoveOptions{
- RemoveVolumes: true,
- Force: true,
- }
-
- log.Printf("[INFO] Should REMOVE %d containers.", len(removeContainers))
- for _, containername := range removeContainers {
- dockercli.ContainerRemove(ctx, containername, removeOptions)
- }
-
- return nil
-}
-
-func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest, image string, env []string) error {
- parsedRequest := shuffle.OrborusExecutionRequest{
- ExecutionId: workflowExecution.ExecutionId,
- Authorization: workflowExecution.Authorization,
- BaseUrl: os.Getenv("BASE_URL"),
- EnvironmentName: os.Getenv("ENVIRONMENT_NAME"),
- Timezone: os.Getenv("TZ"),
- Cleanup: os.Getenv("CLEANUP"),
- HTTPProxy: os.Getenv("HTTP_PROXY"),
- HTTPSProxy: os.Getenv("HTTPS_PROXY"),
- ShufflePassProxyToApp: os.Getenv("SHUFFLE_PASS_APP_PROXY"),
- WorkerServerUrl: os.Getenv("SHUFFLE_WORKER_SERVER_URL"),
- }
-
- parsedBaseurl := baseUrl
- if strings.Contains(baseUrl, ":") {
- baseUrlSplit := strings.Split(baseUrl, ":")
- if len(baseUrlSplit) >= 3 {
- parsedBaseurl = strings.Join(baseUrlSplit[0:2], ":")
- }
- }
-
- data, err := json.Marshal(parsedRequest)
- if err != nil {
- log.Printf("[ERROR] Failed marshalling worker request: %s", err)
- return err
- }
-
- streamUrl := fmt.Sprintf("http://shuffle-workers:33333/api/v1/execute")
- if containerId == "" || containerId == "shuffle-orborus" {
- streamUrl = fmt.Sprintf("%s:33333/api/v1/execute", parsedBaseurl)
- }
-
- identifier := "shuffle-workers"
- if isKubernetes == "true" {
- // FIXME: Do we need this to map the cluster?
- //if shuffle.IsRunningInCluster() {
- //log.Printf("[INFO] Running in Kubernetes cluster")
- // try getting the k8s worker server url
- //}
- }
-
- if strings.Contains(streamUrl, "shuffler.io") || strings.Contains(streamUrl, "localhost") || strings.Contains(streamUrl, "127.0.0.1") || strings.Contains(streamUrl, "shuffle-backend") {
-
- // Specific to debugging
- if len(workerServerUrl) == 0 {
- if debug {
- log.Printf("[INFO] Using default worker server url as previous is invalid: %s. Swapping to shuffle-workers:33333", streamUrl)
- }
- }
-
- streamUrl = fmt.Sprintf("http://shuffle-workers:33333/api/v1/execute")
- }
-
- if len(workerServerUrl) > 0 {
- // Check if a port is supplied or not
- if strings.Contains(workerServerUrl, "/api/v1/execute") {
- streamUrl = workerServerUrl
- } else {
- streamUrl = fmt.Sprintf("%s/api/v1/execute", workerServerUrl)
- if !strings.Contains(workerServerUrl, ":") {
- streamUrl = fmt.Sprintf("%s:33333/api/v1/execute", workerServerUrl)
- }
- }
- }
-
- client := &http.Client{
- //Transport: &http.Transport{
- // TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- //},
- Timeout: time.Duration(120 * time.Second),
- }
-
- if debug {
- log.Printf("[DEBUG][%s] Worker request to be sent to URL: %s", workflowExecution.ExecutionId, streamUrl)
- }
-
- req, err := http.NewRequest(
- "POST",
- streamUrl,
- bytes.NewBuffer([]byte(data)),
- )
-
- if err != nil {
- log.Printf("[ERROR] Failed creating worker request: %s", err)
- if strings.Contains(fmt.Sprintf("%s", err), "connection refused") || strings.Contains(fmt.Sprintf("%s", err), "EOF") {
- workerImage := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
- if len(newWorkerImage) > 0 {
- workerImage = newWorkerImage
- }
-
- if isKubernetes == "true" {
- deployK8sWorker(workerImage, identifier, env)
- } else {
- deployServiceWorkers(workerImage)
- }
-
- time.Sleep(time.Duration(10) * time.Second)
- //err = sendWorkerRequest(executionRequest)
- }
-
- return err
- }
-
- newresp, err := client.Do(req)
- if err != nil {
- // Connection refused?
- if !strings.Contains(fmt.Sprintf("%s", err), "timeout") {
- log.Printf("[ERROR][%s] Error running worker request to %s (1): %s", workflowExecution.ExecutionId, streamUrl, err)
- }
-
- if strings.Contains(fmt.Sprintf("%s", err), "connection refused") || strings.Contains(fmt.Sprintf("%s", err), "EOF") {
- workerImage := fmt.Sprintf("ghcr.io/shuffle/shuffle-worker:%s", workerVersion)
- if len(newWorkerImage) > 0 {
- workerImage = newWorkerImage
- }
-
- if isKubernetes == "true" {
- deployK8sWorker(workerImage, identifier, env)
- } else {
- deployServiceWorkers(workerImage)
- }
-
- time.Sleep(time.Duration(10) * time.Second)
- //err = sendWorkerRequest(executionRequest)
- }
-
- return err
- }
-
- defer newresp.Body.Close()
- body, err := ioutil.ReadAll(newresp.Body)
- if err != nil {
- log.Printf("[ERROR] Failed reading body in worker request body to worker on %s: %s", streamUrl, err)
- return err
- }
- window.AddEvent(time.Now())
-
- if newresp.StatusCode != 200 {
- log.Printf("[WARNING] POTENTIAL error running worker request (2) - status code is %d for %s, not 200. Body: %s", newresp.StatusCode, streamUrl, string(body))
-
- // In case of old executions
- if strings.Contains(strings.ToLower(string(body)), "bad status ") {
- return nil
- }
-
- if strings.Contains(strings.ToLower(string(body)), "no apps to handle") {
- return nil
- }
-
- return errors.New(fmt.Sprintf("Bad statuscode from worker: %d - expecting 200", newresp.StatusCode))
- }
-
- _ = body
-
- debugCommand := fmt.Sprintf("docker service logs shuffle-workers 2>&1 -f | grep %s", workflowExecution.ExecutionId)
- if isKubernetes == "true" {
- debugCommand = fmt.Sprintf("kubectl logs -n %s deployment/shuffle-workers | grep %s", kubernetesNamespace, workflowExecution.ExecutionId)
- }
-
- log.Printf("[DEBUG][%s] Ran worker from requests. Worker URL: %s. DEBUGGING:\n%s", workflowExecution.ExecutionId, streamUrl, debugCommand)
- return nil
-}
-
-// 0x0elliot:
-// let's never increase worker replicas.
-// in our tests, workers replicas mattered a lot less.
-// edge-case: subflows are helped with when worker replicas are higher.
-func AutoScale(ctx context.Context) {
- if os.Getenv("SHUFFLE_SCALE_REPLICAS") != "" {
- return
- }
-
- ticker := time.NewTicker(1 * time.Second)
- coolDownPeriod := 10 * time.Second
- queuePerMinuteInt = 20
- if os.Getenv("SHUFFLE_QUEUE_PER_MINUTE") != "" {
- var err error
- queuePerMinuteInt, err = strconv.Atoi(os.Getenv("SHUFFLE_QUEUE_PER_MINUTE"))
- if err != nil {
- log.Printf("[WARNING] Cannot convert %s to int. Using default value for it: %d", queuePerMinute, queuePerMinuteInt)
- }
- }
-
- lastScaleTime := time.Now()
- currentWorkers := currentWokerCount(ctx, dockercli)
- for {
- select {
- case <-ctx.Done():
- return
- case <-ticker.C:
- if time.Since(lastScaleTime) < (coolDownPeriod) {
- continue
- }
- currentRequestCount := window.CountEvents(time.Now())
- requiredReplicas := 0
- if currentRequestCount >= queuePerMinuteInt*currentWorkers {
- // FIXME: Hardcoded Max Replicas should be 6
- requiredReplicas = int(math.Min(float64(6), float64(currentRequestCount/queuePerMinuteInt)+1))
- }
-
- if requiredReplicas > 0 {
- err := scaleService(ctx, dockercli, uint64(requiredReplicas))
- if err != nil {
- log.Printf("[ERROR] Failed to scale service: %s", err)
- } else {
- lastScaleTime = time.Now()
- currentWorkers = currentWokerCount(ctx, dockercli)
- }
- }
- }
- }
-}
-
-func scaleService(ctx context.Context, client *dockerclient.Client, replicas uint64) error {
- service, _, err := client.ServiceInspectWithRaw(ctx, "shuffle-workers", types.ServiceInspectOptions{})
- if err != nil {
- return err
- }
-
- if service.Spec.Mode.Replicated == nil {
- return errors.New("Service cannot be replicated")
- }
-
- if *service.Spec.Mode.Replicated.Replicas >= replicas {
- return nil
- }
-
- service.Spec.Mode.Replicated.Replicas = &replicas
-
- _, err = dockercli.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
- if err != nil {
- return err
- }
-
- log.Printf("[INFO] Scaled shuffle-worker to %d replicas", replicas)
- return nil
-}
-
-func currentWokerCount(ctx context.Context, client *dockerclient.Client) int {
- service, _, err := client.ServiceInspectWithRaw(ctx, "shuffle-workers", types.ServiceInspectOptions{})
- if err != nil {
- return 0
- }
-
- if service.Spec.Mode.Replicated == nil {
- return 0
- }
-
- return int(*service.Spec.Mode.Replicated.Replicas)
-}
-
-func queueScaleFactor(numQueue int, queuePerMin int) float64 {
- if numQueue > queuePerMin {
- queuePressure := float64(numQueue) / float64(queuePerMin)
- return 1.0 + math.Min(queuePressure-1.0, 1.0)
- }
-
- return 1.0
-}
-
-func checkMemcached(ctx context.Context, dockercli *dockerclient.Client) (bool, error) {
- containerName := "shuffle-cache"
- continer, err := dockercli.ContainerInspect(context.Background(), containerName)
- if err != nil {
- if dockerclient.IsErrNotFound(err) {
- return false, nil
- }
- return false, err
- }
- networkName := "shuffle_swarm_executions"
- err = dockercli.NetworkConnect(ctx, networkName, containerName, nil)
- if err != nil {
- log.Printf("[WARNING] Failed connecting memcached container to network: %s", err)
- }
-
- if continer.State.Running == false {
- log.Printf("[INFO] Container %s exists but is not running. Attempting to start it.", containerName)
- err = dockercli.ContainerStart(ctx, containerName, container.StartOptions{})
- if err != nil {
- log.Printf("[ERROR] Failed to start container %s: %v", containerName, err)
- return false, err
- }
- log.Printf("[INFO] Successfully started container %s.", containerName)
- return true, nil
- }
-
- return continer.State.Running, nil
-}
-
-func deployMemcached(dockercli *dockerclient.Client) error {
- if os.Getenv("SHUFFLE_MEMCACHED") != "" {
- return errors.New("Memcached already running")
- }
-
- defaultMem := "1024"
- log.Printf("[INFO] Spanning a default memcached container to handle the distribution between cache across different workers. Default memory assigned %s", defaultMem)
-
- ctx := context.Background()
-
- memcachedImage := "docker.io/library/memcached:latest"
- containerConfig := &container.Config{
- Image: memcachedImage,
- Cmd: []string{"-m", defaultMem},
- }
-
- hostConfig := &container.HostConfig{
- PortBindings: nat.PortMap{
- "11211/tcp": []nat.PortBinding{{HostPort: "11211"}},
- },
- }
-
- _, _, err := dockercli.ImageInspectWithRaw(ctx, memcachedImage)
- if dockerclient.IsErrNotFound(err) {
- log.Printf("[DEBUG] Pulling image %s. This may take a while.", memcachedImage)
- pullOptions := image.PullOptions{}
- out, err := dockercli.ImagePull(ctx, memcachedImage, pullOptions)
- if err != nil {
- log.Printf("[ERROR] Failed to pull the memcached image: %s", err)
- return err
- }
- defer out.Close()
-
- io.Copy(io.Discard, out)
- } else if err != nil {
- return err
- }
-
- containerName := "shuffle-cache"
- resp, err := dockercli.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, containerName)
- if err != nil {
- log.Printf("[ERROR] Error spanning memcached continer: %s", err)
- return err
- }
-
- if os.Getenv("SHUFFLE_SWARM_CONFIG") == "run" {
- networkName := "shuffle_swarm_executions"
- err = dockercli.NetworkConnect(ctx, networkName, resp.ID, nil)
- if err != nil {
- log.Printf("[ERROR] Error connecting tenzir container to network: %s", err)
- }
- }
-
- err = dockercli.ContainerStart(ctx, resp.ID, container.StartOptions{})
- if err != nil {
- log.Printf("[ERROR] Error starting memcached continer: %s", err)
- return err
- }
-
- networkName := "shuffle_swarm_executions"
- err = dockercli.NetworkConnect(ctx, networkName, resp.ID, nil)
- if err != nil {
- log.Printf("[ERROR] Error connecting memcached container to network: %s", err)
- }
-
- log.Printf("[INFO] Memcached container started successfully at port 11211")
-
- return nil
-}
-
-// How do we get the cpu usage? maybe just get the number of requests (much more useful for apps)
-/*
-func nodesResourceUsage(ctx context.Context, client *dockerclient.Client) error {
- nodes, err := client.NodeList(ctx, types.NodeListOptions{})
- if err != nil {
- return err
- }
- for _, node := range nodes {
- res := node.Description.Resources
- }
-
- return nil
-}
-*/
-
-/*
-func numberOfReplicas(ctx context.Context, queueLength int, config shuffle.ScalingConfig) (int, int) {
- queueScaleFactor := queueScaleFactor(queueLength, config)
- numReplicas := int(float64(queueLength) * queueScaleFactor)
- serviceName := "shuffle-workers"
- nodes, err := dockercli.NodeList(ctx, types.NodeListOptions{})
- if err != nil {
- log.Printf("[ERROR] Cannot find any nodes in the swarm network")
- }
-
- filterArgs := filters.NewArgs()
- filterArgs.Add("service", serviceName)
- filterArgs.Add("desired-state", "running")
-
- tasks, err := dockercli.TaskList(context.Background(), types.TaskListOptions{
- Filters: filterArgs,
- })
- if err != nil {
- log.Fatalf("[WARNING] Failed to list tasks for service %s: %s", serviceName, err)
- }
-
- runningReplicas := len(tasks)
- if numReplicas > runningReplicas*len(nodes) {
- maxIncrease := config.MaxScaleUpStep
- if numReplicas > (runningReplicas*len(nodes) + maxIncrease) {
- numReplicas = runningReplicas + maxIncrease
- }
- }
-
- if numReplicas < config.MinReplicas {
- numReplicas = config.MinReplicas
- }
- if numReplicas > config.MaxReplicas {
- numReplicas = config.MaxReplicas
- }
-
- return numReplicas, runningReplicas
-}
-*/
-
-// TODO: Currently we use number of request made for the worker to run a execution as it is much
-// easier to track in a window time frame. But this could be useful.
-func collectMetrics(ctx context.Context, dockerClient *dockerclient.Client) (int, error) {
- client := shuffle.GetExternalClient(baseUrl)
- fullUrl := fmt.Sprintf("%s/api/v1/workflows/queue", baseUrl)
- req, err := http.NewRequest("GET", fullUrl, nil)
- if err != nil {
- log.Printf("[ERROR] Failed to send a request to %s: %s", fullUrl, err)
- return 0, err
- }
-
- req.Header.Add("Content-Type", "application/json")
- req.Header.Add("Org-Id", environment)
- if len(auth) > 0 {
- req.Header.Add("Authorization", auth)
- }
-
- if len(org) > 0 {
- req.Header.Add("Org", org)
- }
-
- if len(orborusLabel) > 0 {
- log.Printf("[DEBUG] Sending with Label '%s'", orborusLabel)
- req.Header.Add("X-Orborus-Label", orborusLabel)
- }
-
- if swarmConfig != "run" && swarmConfig != "swarm" {
- req.Header.Add("X-Orborus-Runmode", "Default")
- } else {
- req.Header.Add("X-Orborus-Runmode", "Docker Swarm")
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return 0, err
- }
-
- var executionRequests shuffle.ExecutionRequestWrapper
- body, err := ioutil.ReadAll(resp.Body)
-
- json.Unmarshal(body, &executionRequests)
-
- return len(executionRequests.Data), nil
-}
-
-func setBackendToSwarmNetwork(ctx context.Context) error {
- containerId := ""
- filterArgs := filters.NewArgs()
- filterArgs.Add("name", "shuffle-backend")
-
- containers, err := dockercli.ContainerList(ctx, container.ListOptions{
- All: true,
- Filters: filterArgs,
- })
- if err != nil {
- return err
- }
- if len(containers) == 0 {
- return errors.New("No containers found with name shuffle-backend")
- }
-
- containerId = containers[0].ID
- networkName := "shuffle_swarm_executions"
- err = dockercli.NetworkConnect(ctx, networkName, containerId, nil)
- if err != nil {
- log.Printf("[ERROR] Error connecting backend container to network: %s", err)
- }
-
- return nil
-}
diff --git a/functions/onprem/orborus/orborus.yaml b/functions/onprem/orborus/orborus.yaml
deleted file mode 100644
index 0ff50ef7..00000000
--- a/functions/onprem/orborus/orborus.yaml
+++ /dev/null
@@ -1,82 +0,0 @@
----
-
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
- namespace: default
- name: pod-manager
-rules:
-- apiGroups: [""]
- resources: ["pods"]
- verbs: ["get", "list", "create", "update", "delete"]
-- apiGroups: ["batch"]
- resources: ["jobs"]
- verbs: ["create", "get", "list", "watch", "delete"]
-
----
-
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
- name: pod-manager-binding
- namespace: default
-subjects:
-- kind: ServiceAccount
- name: default
- namespace: default
-roleRef:
- kind: Role
- name: pod-manager
- apiGroup: rbac.authorization.k8s.io
-
----
-
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: orborus
- annotations:
- kompose.cmd: kompose convert -f docker-compose.yml
- kompose.version: 1.26.0 (40646f47)
- labels:
- io.kompose.service: orborus
-spec:
- replicas: 1
- selector:
- matchLabels:
- io.kompose.service: orborus
- strategy: {}
- template:
- metadata:
- annotations:
- kompose.cmd: kompose convert -f docker-compose.yml
- kompose.version: 1.26.0 (40646f47)
- creationTimestamp: null
- labels:
- io.kompose.network/shuffle: "true"
- io.kompose.service: orborus
- spec:
- dnsPolicy: "Default"
- containers:
- - env:
- - name: BASE_URL
- value: "https://shuffler.io"
- - name: SHUFFLE_SCALE_REPLICAS
- value: "7"
- - name: IS_KUBERNETES
- value: "true"
- - name: ENVIRONMENT_NAME
- value: "environment test"
- - name: ORG
- value: "9c938e5b-d812-40d9-92f0-93783f43ec0d"
- - name: AUTH
- value: "3663a270-bb3a-4678-a365-d879601a1a0c"
- - name: SHUFFLE_WORKER_IMAGE
- value: "ghcr.io/shuffle/shuffle-worker:nightly"
-
- image: ghcr.io/shuffle/shuffle-orborus:nightly
- imagePullPolicy: Always
- name: shuffle-orborus
- resources: {}
- hostname: shuffle-orborus
- restartPolicy: Always
diff --git a/functions/onprem/orborus/proxy_server.py b/functions/onprem/orborus/proxy_server.py
deleted file mode 100644
index 8020616c..00000000
--- a/functions/onprem/orborus/proxy_server.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# curl -H "Org-id: Shuffle" --proxy "http://192.168.86.45:8081" http://192.168.86.45:5001/api/v1/workflows/queue
-
-import SocketServer
-import SimpleHTTPServer
-import requests
-import json
-
-PORT = 8082
-
-class MyProxy(SimpleHTTPServer.SimpleHTTPRequestHandler):
- def do_GET(self):
- url=self.path[:]
- allheaders = {}
- for item in ("%s" % self.headers).split("\n"):
- headersplit = item.split(":")
- if len(headersplit) == 2:
- allheaders[headersplit[0]] = (headersplit[1][:-1]).strip()
-
- ret = requests.get(url, headers=allheaders)
- print("RESP (%s) - %d - %s" % (url, ret.status_code, ret.text))
-
- self.send_response(ret.status_code)
- self.end_headers()
- self.wfile.write(ret.text)
-
- def do_POST(self):
- url=self.path[:]
- allheaders = {}
- for item in ("%s" % self.headers).split("\n"):
- headersplit = item.split(":")
- if len(headersplit) == 2:
- allheaders[headersplit[0]] = (headersplit[1][:-1]).strip()
-
- length = int(self.headers.getheader('content-length'))
- try:
- message = json.loads(self.rfile.read(length))
- print("Got message: %s" % message)
- ret = requests.post(url, headers=allheaders, json=message)
- except:
- message = self.rfile.read(length)
- print("Got message: %s" % message)
- ret = requests.post(url, headers=allheaders, data=message)
-
- print("RESP (%s) - %d - %s" % (url, ret.status_code, ret.text))
-
- self.send_response(ret.status_code)
- self.end_headers()
- self.wfile.write(ret.text)
-
-httpd = SocketServer.ForkingTCPServer(('', PORT), MyProxy)
-print("Now serving at %d" % PORT)
-httpd.serve_forever()
-
diff --git a/functions/onprem/orborus/run.sh b/functions/onprem/orborus/run.sh
deleted file mode 100755
index d71ac59c..00000000
--- a/functions/onprem/orborus/run.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#docker run \
-# --env DOCKER_API_VERSION=1.40 \
-# --env ENVIRONMENT_NAME="Shuffle" \
-# --env BASE_URL="http://192.168.86.45:5001" \
-# --env HTTP_PROXY="http://192.168.86.45:8082" \
-# --env HTTPS_PROXY="https://192.168.86.45:8082" \
-# --env SHUFFLE_PASS_WORKER_PROXY=true \
-# --env SHUFFLE_PASS_APP_PROXY=true \
-# -v /var/run/docker.sock:/var/run/docker.sock \
-# ghcr.io/frikky/shuffle-orborus:nightly
-
-docker run \
- --env DOCKER_API_VERSION=1.40 \
- --env ENVIRONMENT_NAME="Another env" \
- --env ORG="2e7b6a08-b63b-4fc2-bd70-718091509db1" \
- --env AUTH="env auth" \
- --env BASE_URL="https://shuffler.io" \
- -v /var/run/docker.sock:/var/run/docker.sock \
- ghcr.io/frikky/shuffle-orborus:nightly
diff --git a/functions/onprem/worker/Dockerfile b/functions/onprem/worker/Dockerfile
index 5bfe4fa1..18803619 100755
--- a/functions/onprem/worker/Dockerfile
+++ b/functions/onprem/worker/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.24 as builder
+FROM golang:1.25 as builder
WORKDIR /app
#RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod
index 4b886c0a..59b56f07 100644
--- a/functions/onprem/worker/go.mod
+++ b/functions/onprem/worker/go.mod
@@ -1,8 +1,6 @@
module worker
-go 1.24.0
-
-toolchain go1.24.4
+go 1.25.0
//replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared
//replace github.com/shuffle/singul => ../../../../singul
@@ -11,8 +9,8 @@ require (
github.com/docker/docker v28.3.3+incompatible
github.com/gorilla/mux v1.8.1
github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.9.90
- github.com/shuffle/singul v0.0.26
+ github.com/shuffle/shuffle-shared v1.2.45
+ github.com/shuffle/singul v0.0.32
k8s.io/api v0.34.2
k8s.io/apimachinery v0.34.2
k8s.io/client-go v0.34.2
@@ -21,20 +19,20 @@ require (
require (
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
)
require (
- cel.dev/expr v0.20.0 // indirect
+ cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.121.1 // indirect
- cloud.google.com/go/compute/metadata v0.7.0 // indirect
+ cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/datastore v1.20.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/scheduler v1.11.7 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
dario.cat/mergo v1.0.0 // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
+ github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
@@ -42,14 +40,14 @@ require (
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/adrg/strutil v0.3.1 // indirect
github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect
+ github.com/andybalholm/brotli v1.2.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
- github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
+ github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
- github.com/coreos/go-oidc/v3 v3.17.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
@@ -57,18 +55,18 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
- github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
- github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
+ github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
+ github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/frikky/kin-openapi v0.42.0 // indirect
- github.com/frikky/schemaless v0.0.28 // indirect
+ github.com/frikky/schemaless v0.0.34 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.5 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
- github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
@@ -111,40 +109,39 @@ require (
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/spf13/pflag v1.0.6 // indirect
- github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
+ github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
- github.com/zeebo/errs v1.4.0 // indirect
- go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
- go.opentelemetry.io/otel v1.36.0 // indirect
- go.opentelemetry.io/otel/metric v1.36.0 // indirect
- go.opentelemetry.io/otel/sdk v1.36.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
- go.opentelemetry.io/otel/trace v1.36.0 // indirect
+ go.opentelemetry.io/otel v1.42.0 // indirect
+ go.opentelemetry.io/otel/metric v1.42.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.42.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
+ go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
- golang.org/x/crypto v0.45.0 // indirect
- golang.org/x/net v0.47.0 // indirect
- golang.org/x/oauth2 v0.30.0 // indirect
- golang.org/x/sync v0.18.0 // indirect
- golang.org/x/sys v0.38.0 // indirect
- golang.org/x/term v0.37.0 // indirect
- golang.org/x/text v0.31.0 // indirect
+ golang.org/x/crypto v0.48.0 // indirect
+ golang.org/x/net v0.51.0 // indirect
+ golang.org/x/oauth2 v0.34.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/sys v0.41.0 // indirect
+ golang.org/x/term v0.40.0 // indirect
+ golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/api v0.236.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
- google.golang.org/grpc v1.72.2 // indirect
- google.golang.org/protobuf v1.36.6 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
+ google.golang.org/grpc v1.79.3 // indirect
+ google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/functions/onprem/worker/go.sum b/functions/onprem/worker/go.sum
index 1eb817a4..c140c2d0 100644
--- a/functions/onprem/worker/go.sum
+++ b/functions/onprem/worker/go.sum
@@ -1,5 +1,5 @@
-cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
-cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
+cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
+cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -17,8 +17,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
-cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
+cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
+cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM=
cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew=
@@ -47,8 +47,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
@@ -66,6 +66,8 @@ github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk=
github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk=
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -85,16 +87,14 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
-github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
-github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
+github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
-github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
@@ -117,21 +117,21 @@ github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
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.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
-github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
+github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
+github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
-github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
+github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
+github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0=
github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4=
-github.com/frikky/schemaless v0.0.28 h1:gdurMqBwtvY4Y/5pcxn8bdGCJn/eolKGz+c5DcidLkI=
-github.com/frikky/schemaless v0.0.28/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
+github.com/frikky/schemaless v0.0.34 h1:7w14wtbeBvIyKEA6ZugEPHyDV8maxMm3FOHWTEeIN+M=
+github.com/frikky/schemaless v0.0.34/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -151,8 +151,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -298,8 +298,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -317,10 +318,10 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shuffle/opensearch-go/v4 v4.0.0 h1:Mh85CD1MwOgXiFFYlzS1llnvdqL3CztRdR1ZT/SLIjU=
github.com/shuffle/opensearch-go/v4 v4.0.0/go.mod h1:gVLZKQE5khQWMb68XBtgKrhu78oLGL2zHwAGnFMDwC0=
-github.com/shuffle/shuffle-shared v0.9.87 h1:INA1cZ18MKcMs8kaVBJoQqrdNuqLHfge9elJ17hZfVc=
-github.com/shuffle/shuffle-shared v0.9.87/go.mod h1:AkXajlWWB16WfWjCw9K7y38L8JKABJzVxcX6KI/J1H4=
-github.com/shuffle/singul v0.0.26 h1:P2uZ8YIYQUN4qNfQujoW3Lhod91X6TAfrRHGWiUPNI8=
-github.com/shuffle/singul v0.0.26/go.mod h1:S8GszXL+fT2mTnh7j57V0r/tY5Y/mSW8QowQEnADs3k=
+github.com/shuffle/shuffle-shared v1.2.45 h1:d4gnF6qkrmmZVz4FEraEBzU7Kq8VbfAbiGlBoYeFD7o=
+github.com/shuffle/shuffle-shared v1.2.45/go.mod h1:1DvYhS/4659FdHxzEpHTPuFUW0IfwQSuyluiGTwz9jo=
+github.com/shuffle/singul v0.0.32 h1:EoJjKCWfbtHqjo/UgofSvTp7rcYEH7k3eGXrZgXcMt8=
+github.com/shuffle/singul v0.0.32/go.mod h1:An0DHl+E+bAO1ziIbqUJdLXhjixN6DOyv0GwlXwsKHc=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -330,8 +331,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
-github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
+github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
+github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -364,41 +365,41 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
-github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
-go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
+go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
-go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
-go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
+go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
+go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
-go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
-go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
-go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
-go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
-go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
-go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
-go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
-go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
-go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
-go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
+go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
+go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
+go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
+go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
+go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
+go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
+go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
+go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
@@ -412,8 +413,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
+golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -460,15 +461,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
+golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
+golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
-golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
+golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
+golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -477,8 +478,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -501,13 +502,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
+golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
+golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -517,8 +518,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
+golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
@@ -550,12 +551,14 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
-golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
+golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -588,10 +591,10 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
-google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
+google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -599,12 +602,12 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
-google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go
index 83e49d85..fb7b3e5a 100644
--- a/functions/onprem/worker/worker.go
+++ b/functions/onprem/worker/worker.go
@@ -19,6 +19,7 @@ import (
"os"
"strconv"
"strings"
+ "sync"
"time"
"github.com/docker/docker/api/types"
@@ -110,8 +111,122 @@ var finishedExecutions []string
var imagesDistributed []string
var imagedownloadTimeout = time.Second * 300
+var subflowPollBackoff sync.Map
+
var window = shuffle.NewTimeWindow(10 * time.Second)
+func restoreActionConfig(ctx context.Context, executionID string, action *shuffle.Action, workflowExecution *shuffle.WorkflowExecution) {
+ if len(executionID) == 0 || action == nil {
+ return
+ }
+
+ cacheKey := fmt.Sprintf("workflowexecution_%s_action_snapshot", executionID)
+ cacheDataRaw, err := shuffle.GetCache(ctx, cacheKey)
+ if err != nil {
+ return
+ }
+
+ originalActions := []shuffle.Action{}
+ cacheData := []byte(cacheDataRaw.([]uint8))
+ if err := json.Unmarshal(cacheData, &originalActions); err != nil {
+ return
+ }
+
+ original := shuffle.Action{}
+ for _, candidate := range originalActions {
+ if candidate.ID == action.ID {
+ original = candidate
+ break
+ }
+ }
+
+ if len(original.ID) == 0 {
+ return
+ }
+
+ for index, param := range action.Parameters {
+ if !param.Configuration || len(param.Value) > 0 {
+ continue
+ }
+
+ for _, originalParam := range original.Parameters {
+ if !strings.EqualFold(strings.TrimSpace(param.Name), strings.TrimSpace(originalParam.Name)) {
+ continue
+ }
+
+ if len(originalParam.Value) > 0 {
+ action.Parameters[index].Value = originalParam.Value
+ }
+
+ break
+ }
+ }
+
+ if workflowExecution == nil {
+ return
+ }
+
+ for actionIndex, workflowAction := range workflowExecution.Workflow.Actions {
+ if workflowAction.ID != action.ID {
+ continue
+ }
+
+ for parameterIndex, workflowParam := range workflowAction.Parameters {
+ if !workflowParam.Configuration || len(workflowParam.Value) > 0 {
+ continue
+ }
+
+ for _, originalParam := range original.Parameters {
+ if !strings.EqualFold(strings.TrimSpace(workflowParam.Name), strings.TrimSpace(originalParam.Name)) {
+ continue
+ }
+
+ if len(originalParam.Value) > 0 {
+ workflowExecution.Workflow.Actions[actionIndex].Parameters[parameterIndex].Value = originalParam.Value
+ }
+
+ break
+ }
+ }
+
+ break
+ }
+}
+
+func getAppProxyValue(primaryKey, fallbackKey string) string {
+ value := strings.TrimSpace(os.Getenv(primaryKey))
+ if len(value) > 0 {
+ return value
+ }
+
+ return strings.TrimSpace(os.Getenv(fallbackKey))
+}
+
+func appendAppProxyEnv(env []string) []string {
+ httpProxy := getAppProxyValue("HTTP_PROXY", "SHUFFLE_APP_HTTP_PROXY")
+ httpsProxy := getAppProxyValue("HTTPS_PROXY", "SHUFFLE_APP_HTTPS_PROXY")
+ noProxy := getAppProxyValue("NO_PROXY", "SHUFFLE_APP_NO_PROXY")
+ noProxyLower := getAppProxyValue("no_proxy", "SHUFFLE_APP_no_proxy")
+
+ if len(httpProxy) > 0 {
+ env = append(env, fmt.Sprintf("HTTP_PROXY=%s", httpProxy))
+ }
+
+ if len(httpsProxy) > 0 {
+ env = append(env, fmt.Sprintf("HTTPS_PROXY=%s", httpsProxy))
+ }
+
+ if len(noProxy) > 0 {
+ env = append(env, fmt.Sprintf("NO_PROXY=%s", noProxy))
+ }
+
+ if len(noProxyLower) > 0 {
+ env = append(env, fmt.Sprintf("no_proxy=%s", noProxyLower))
+ }
+
+ return env
+}
+
// Images to be autodeployed in the latest version of Shuffle.
var autoDeploy = map[string]string{
"http:1.4.0": "frikky/shuffle:http_1.4.0",
@@ -139,6 +254,15 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo
return errors.New("ExecutionId can't be empty.")
}
+ if len(workflowExecution.Workflow.Actions) > 0 {
+ snapshotKey := fmt.Sprintf("workflowexecution_%s_action_snapshot", workflowExecution.ExecutionId)
+ if _, err := shuffle.GetCache(ctx, snapshotKey); err != nil {
+ if payload, marshalErr := json.Marshal(workflowExecution.Workflow.Actions); marshalErr == nil {
+ _ = shuffle.SetCache(ctx, snapshotKey, payload, 3600)
+ }
+ }
+ }
+
//log.Printf("[DEBUG][%s] Setting with %d results (pre)", workflowExecution.ExecutionId, len(workflowExecution.Results))
workflowExecution, _ = shuffle.Fixexecution(ctx, workflowExecution)
cacheKey := fmt.Sprintf("workflowexecution_%s", workflowExecution.ExecutionId)
@@ -275,8 +399,6 @@ func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.Workflo
break
}
- // Sleep for 1 second
- time.Sleep(1 * time.Second)
}
} else {
log.Printf("[DEBUG][%s] No need to poll for results. Not polling", workflowExecution.ExecutionId)
@@ -1315,6 +1437,27 @@ func removeIndex(s []string, i int) []string {
return s[:len(s)-1]
}
+func countHealthyServiceTasks(dockercli *dockerclient.Client, serviceID string) (int, error) {
+ ctx := context.Background()
+ filters := filters.NewArgs()
+ filters.Add("service", serviceID)
+ filters.Add("desired-state", "running")
+
+ tasks, err := dockercli.TaskList(ctx, types.TaskListOptions{Filters: filters})
+ if err != nil {
+ return 0, err
+ }
+
+ count := 0
+ for _, task := range tasks {
+ if task.Status.State == swarm.TaskStateRunning {
+ count += 1
+ }
+ }
+
+ return count, nil
+}
+
func getWorkerURLs() ([]string, error) {
workerUrls := []string{}
@@ -1675,10 +1818,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" {
//log.Printf("APPENDING PROXY TO THE APP!")
- env = append(env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY")))
- env = append(env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY")))
- env = append(env, fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY")))
- env = append(env, fmt.Sprintf("no_proxy=%s", os.Getenv("no_proxy")))
+ env = appendAppProxyEnv(env)
}
overrideHttpProxy := os.Getenv("SHUFFLE_INTERNAL_HTTP_PROXY")
@@ -1691,6 +1831,11 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
env = append(env, fmt.Sprintf("SHUFFLE_INTERNAL_HTTPS_PROXY=%s", overrideHttpsProxy))
}
+ proxyConfigOverride := os.Getenv("SHUFFLE_APP_PROXY_CONFIG_OVERRIDE")
+ if proxyConfigOverride != "" {
+ env = append(env, fmt.Sprintf("SHUFFLE_APP_PROXY_CONFIG_OVERRIDE=%s", proxyConfigOverride))
+ }
+
if len(os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")) > 0 {
env = append(env, fmt.Sprintf("SHUFFLE_APP_SDK_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")))
}
@@ -2171,6 +2316,58 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow
}
}
+ key := fmt.Sprintf("%s:%s", workflowExecution.ExecutionId, subflowId)
+ cacheKey := fmt.Sprintf("workflowexecution_%s", workflowExecution.ExecutionId)
+ usedCache := false
+ if cacheData, err := shuffle.GetCache(ctx, cacheKey); err == nil {
+ cachedBytes, ok := cacheData.([]uint8)
+ if ok {
+ cacheWorkflow := shuffle.WorkflowExecution{}
+ if jsonErr := json.Unmarshal([]byte(cachedBytes), &cacheWorkflow); jsonErr == nil {
+ workflowExecution = cacheWorkflow
+ usedCache = true
+ log.Printf("[DEBUG][%s] Using cached workflow execution for subflow poll", workflowExecution.ExecutionId)
+
+ if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" {
+ log.Printf("[INFO][%s] Workflow execution is finished (cache). Exiting worker.", workflowExecution.ExecutionId)
+ log.Printf("[DEBUG] Shutting down (20)")
+ resetSubflowPollDelay(key)
+ if isKubernetes == "true" {
+ clientset, _, err := shuffle.GetKubernetesClient()
+ if err != nil {
+ log.Println("[ERROR] Error getting kubernetes client (2):", err)
+ os.Exit(1)
+ }
+
+ cleanupKubernetesExecution(clientset, workflowExecution, kubernetesNamespace)
+ } else {
+ shutdown(workflowExecution, "", "", true)
+ }
+ }
+
+ for _, result := range workflowExecution.Results {
+ if result.Action.ID != subflowId {
+ continue
+ }
+
+ if result.Status == "SUCCESS" || result.Status == "FINISHED" || result.Status == "FAILURE" || result.Status == "ABORTED" {
+ resetSubflowPollDelay(key)
+ setWorkflowExecution(ctx, workflowExecution, false)
+ return nil
+ }
+ }
+ }
+ }
+ }
+
+ if usedCache {
+ delay := nextSubflowPollDelay(key)
+ attempt := getSubflowPollAttempt(key)
+ log.Printf("[DEBUG][%s] Subflow poll backoff attempt %d for %s (cache hit), sleeping %s", workflowExecution.ExecutionId, attempt, subflowId, delay)
+ time.Sleep(delay)
+ return errors.New("Subflow status not found yet (cache)")
+ }
+
if len(data) == 0 {
log.Printf("[WARNING] Stream result missing execution ID and authorization; injecting them from workflow execution")
data = fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization)
@@ -2186,7 +2383,7 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow
newresp, err := client.Do(req)
if err != nil {
log.Printf("[ERROR] Failed making request (1): %s", err)
- time.Sleep(time.Duration(sleepTime) * time.Second)
+ time.Sleep(nextSubflowPollDelay(key))
return err
}
@@ -2194,7 +2391,7 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow
body, err := ioutil.ReadAll(newresp.Body)
if err != nil {
log.Printf("[ERROR] Failed reading body (1): %s", err)
- time.Sleep(time.Duration(sleepTime) * time.Second)
+ time.Sleep(nextSubflowPollDelay(key))
return err
}
@@ -2206,20 +2403,21 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow
shutdown(workflowExecution, "", "", true)
}
- time.Sleep(time.Duration(sleepTime) * time.Second)
+ time.Sleep(nextSubflowPollDelay(key))
return errors.New(fmt.Sprintf("Bad statuscode: %d", newresp.StatusCode))
}
err = json.Unmarshal(body, &workflowExecution)
if err != nil {
log.Printf("[ERROR] Failed workflowExecution unmarshal: %s", err)
- time.Sleep(time.Duration(sleepTime) * time.Second)
+ time.Sleep(nextSubflowPollDelay(key))
return err
}
if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" {
log.Printf("[INFO][%s] Workflow execution is finished. Exiting worker.", workflowExecution.ExecutionId)
log.Printf("[DEBUG] Shutting down (20)")
+ resetSubflowPollDelay(key)
if isKubernetes == "true" {
// log.Printf("workflow execution: %#v", workflowExecution)
clientset, _, err := shuffle.GetKubernetesClient()
@@ -2235,11 +2433,14 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow
}
hasUserinput := false
+ foundSubflow := false
for _, result := range workflowExecution.Results {
if result.Action.ID != subflowId {
continue
}
+ foundSubflow = true
+
if result.Action.AppName == "User Input" {
hasUserinput = true
}
@@ -2247,21 +2448,66 @@ func handleSubflowPoller(ctx context.Context, workflowExecution shuffle.Workflow
log.Printf("[DEBUG][%s] Found subflow to handle: %s (%s)", workflowExecution.ExecutionId, result.Action.AppName, result.Status)
if result.Status == "SUCCESS" || result.Status == "FINISHED" || result.Status == "FAILURE" || result.Status == "ABORTED" {
// Check for results
-
+ resetSubflowPollDelay(key)
setWorkflowExecution(ctx, workflowExecution, false)
return nil
}
}
- if workflowExecution.Status == "WAITING" && workflowExecution.ExecutionSource != "default" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" {
- log.Printf("[INFO][%s] Workflow execution is waiting. Exiting worker, as backend will restart it.", workflowExecution.ExecutionId)
+ if workflowExecution.Status == "WAITING" && !foundSubflow && workflowExecution.ExecutionSource != "default" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "run" && os.Getenv("SHUFFLE_SWARM_CONFIG") != "swarm" {
+ log.Printf("[INFO][%s] Workflow execution is waiting without subflow progress. Exiting worker, as backend will restart it.", workflowExecution.ExecutionId)
+ resetSubflowPollDelay(key)
shutdown(workflowExecution, "", "", true)
+ return nil
}
log.Printf("[INFO][%s] (2) Status: %s, Results: %d, actions: %d. Userinput: %#v", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra, hasUserinput)
+ delay := nextSubflowPollDelay(key)
+ attempt := getSubflowPollAttempt(key)
+ log.Printf("[DEBUG][%s] Subflow poll backoff attempt %d for %s, sleeping %s", workflowExecution.ExecutionId, attempt, subflowId, delay)
+ time.Sleep(delay)
return errors.New("Subflow status not found yet")
}
+func nextSubflowPollDelay(key string) time.Duration {
+ baseDelay := 250 * time.Millisecond
+ maxDelay := 5 * time.Second
+
+ current := 0
+ if raw, ok := subflowPollBackoff.Load(key); ok {
+ if value, ok := raw.(int); ok {
+ current = value
+ }
+ }
+
+ if current < 8 {
+ current += 1
+ }
+
+ subflowPollBackoff.Store(key, current)
+
+ delay := baseDelay * time.Duration(1< maxDelay {
+ delay = maxDelay
+ }
+
+ return delay
+}
+
+func resetSubflowPollDelay(key string) {
+ subflowPollBackoff.Delete(key)
+}
+
+func getSubflowPollAttempt(key string) int {
+ if raw, ok := subflowPollBackoff.Load(key); ok {
+ if value, ok := raw.(int); ok {
+ return value
+ }
+ }
+
+ return 0
+}
+
func handleDefaultExecutionWrapper(ctx context.Context, workflowExecution shuffle.WorkflowExecution, streamResultUrl string, extra int) error {
if extra == -1 {
extra = 0
@@ -2391,6 +2637,112 @@ func arrayContains(visited []string, id string) bool {
return found
}
+func isTerminalResultStatus(status string) bool {
+ return status == "SUCCESS" || status == "FINISHED" || status == "FAILURE" || status == "ABORTED"
+}
+
+func hasWaitForResultParameter(params []shuffle.WorkflowAppActionParameter) bool {
+ for _, param := range params {
+ if param.Name == "check_result" && strings.ToLower(param.Value) == "true" {
+ return true
+ }
+ }
+
+ return false
+}
+
+func getSubflowBarrierActionIDs(workflowExecution shuffle.WorkflowExecution) map[string]struct{} {
+ actionIDs := map[string]struct{}{}
+
+ for _, action := range workflowExecution.Workflow.Actions {
+ if action.AppName != "shuffle-subflow" && action.AppName != "shuffle-subflow-v2" && action.AppName != "Shuffle Workflow" {
+ continue
+ }
+
+ if !hasWaitForResultParameter(action.Parameters) {
+ continue
+ }
+
+ actionIDs[action.ID] = struct{}{}
+ }
+
+ for _, trigger := range workflowExecution.Workflow.Triggers {
+ if trigger.AppName != "shuffle-subflow" && trigger.AppName != "shuffle-subflow-v2" && trigger.AppName != "Shuffle Workflow" {
+ continue
+ }
+
+ if !hasWaitForResultParameter(trigger.Parameters) {
+ continue
+ }
+
+ actionIDs[trigger.ID] = struct{}{}
+ }
+
+ return actionIDs
+}
+
+func getSubflowBarrierProgress(workflowExecution shuffle.WorkflowExecution) (int, int, int) {
+ barrierActions := getSubflowBarrierActionIDs(workflowExecution)
+ expected := len(barrierActions)
+ if expected == 0 {
+ return 0, 0, 0
+ }
+
+ terminalByAction := map[string]string{}
+ for _, result := range workflowExecution.Results {
+ if _, ok := barrierActions[result.Action.ID]; !ok {
+ continue
+ }
+
+ if !isTerminalResultStatus(result.Status) {
+ continue
+ }
+
+ terminalByAction[result.Action.ID] = result.Status
+ }
+
+ completed := len(terminalByAction)
+ failed := 0
+ for _, status := range terminalByAction {
+ if status == "FAILURE" || status == "ABORTED" {
+ failed += 1
+ }
+ }
+
+ return expected, completed, failed
+}
+
+func enforceSubflowBarrier(workflowExecution *shuffle.WorkflowExecution) (int, int, int) {
+ if workflowExecution == nil {
+ return 0, 0, 0
+ }
+
+ if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "FAILURE" || workflowExecution.Status == "ABORTED" {
+ return 0, 0, 0
+ }
+
+ expected, completed, failed := getSubflowBarrierProgress(*workflowExecution)
+ if expected == 0 {
+ return 0, 0, 0
+ }
+
+ if completed < expected {
+ workflowExecution.Status = "WAITING"
+ return expected, completed, failed
+ }
+
+ if workflowExecution.Status == "WAITING" {
+ if failed > 0 {
+ workflowExecution.Status = "FAILURE"
+ workflowExecution.Result = fmt.Sprintf("Subflow barrier failed: %d of %d subflows failed or aborted", failed, expected)
+ } else {
+ workflowExecution.Status = "EXECUTING"
+ }
+ }
+
+ return expected, completed, failed
+}
+
func getResult(workflowExecution shuffle.WorkflowExecution, id string) shuffle.ActionResult {
for _, actionResult := range workflowExecution.Results {
if actionResult.Action.ID == id {
@@ -3287,11 +3639,16 @@ func webserverSetup(workflowExecution shuffle.WorkflowExecution) net.Listener {
log.Printf("[DEBUG] Starting webserver (1) on port %d with hostname: %s", baseport, hostname)
os.Setenv("WORKER_PORT", fmt.Sprintf("%d", baseport))
- appCallbackUrl = fmt.Sprintf("http://%s:%d", hostname, baseport)
- if os.Getenv("IS_KUBERNETES") == "true" {
+ workerServerUrl := os.Getenv("SHUFFLE_WORKER_SERVER_URL")
+ if len(workerServerUrl) > 0 {
+ appCallbackUrl = workerServerUrl
+ log.Printf("[DEBUG] Using SHUFFLE_WORKER_SERVER_URL for callback: %s", appCallbackUrl)
+ } else if os.Getenv("IS_KUBERNETES") == "true" {
appCallbackUrl = fmt.Sprintf("http://%s:%d", "shuffle-workers", baseport)
log.Printf("[DEBUG] NEW WORKER APP: %s", appCallbackUrl)
hostname = "shuffle-workers"
+ } else {
+ appCallbackUrl = fmt.Sprintf("http://%s:%d", hostname, baseport)
}
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", baseport))
@@ -3479,6 +3836,8 @@ func deploySwarmService(dockercli *dockerclient.Client, name, image string, depl
fmt.Sprintf("SHUFFLE_APP_EXPOSED_PORT=%d", deployport),
fmt.Sprintf("SHUFFLE_SWARM_CONFIG=%s", os.Getenv("SHUFFLE_SWARM_CONFIG")),
fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", logsDisabled),
+ fmt.Sprintf("CALLBACK_URL=%s", baseUrl),
+ fmt.Sprintf("BASE_URL=%s", appCallbackUrl),
},
Hosts: []string{
containerName,
@@ -3500,10 +3859,7 @@ func deploySwarmService(dockercli *dockerclient.Client, name, image string, depl
}
if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" {
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY")))
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY")))
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY")))
- serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("no_proxy=%s", os.Getenv("no_proxy")))
+ serviceSpec.TaskTemplate.ContainerSpec.Env = appendAppProxyEnv(serviceSpec.TaskTemplate.ContainerSpec.Env)
}
overrideHttpProxy := os.Getenv("SHUFFLE_INTERNAL_HTTP_PROXY")
@@ -3517,6 +3873,11 @@ func deploySwarmService(dockercli *dockerclient.Client, name, image string, depl
serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_INTERNAL_HTTPS_PROXY=%s", overrideHttpsProxy))
}
+ proxyConfigOverride := os.Getenv("SHUFFLE_APP_PROXY_CONFIG_OVERRIDE")
+ if proxyConfigOverride != "" {
+ serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_APP_PROXY_CONFIG_OVERRIDE=%s", proxyConfigOverride))
+ }
+
/*
Mounts: []mount.Mount{
mount.Mount{
@@ -3732,13 +4093,24 @@ func findAppInfo(image, name string, redeploy bool) (int, error) {
}
if redeploy {
+ healthyTasks, taskErr := countHealthyServiceTasks(dockercli, service.ID)
+ if taskErr != nil {
+ log.Printf("[WARNING] Failed checking tasks for service %s: %s", name, taskErr)
+ }
+
+ if healthyTasks > 0 {
+ log.Printf("[INFO] Skipping redeploy of app %s since %d task(s) are running", name, healthyTasks)
+ break
+ }
+
log.Printf("[INFO] Found to redeploy! Service: %s with image %s on port %d", name, image, exposedPort)
// Remove the service and redeploy it.
// There are cases where the service doesn't update properly
// Check when the last update happened. If it was within the last few minutes, skip
- if int(time.Since(service.UpdatedAt).Seconds()) > 60 {
+ updateAgeSeconds := int(time.Since(service.UpdatedAt).Seconds())
+ if updateAgeSeconds > 300 {
- log.Printf("[INFO] Attempting redeploy of app %s with image %s since it is more than 10 minutes since last attempt with failure.", name, image)
+ log.Printf("[INFO] Attempting redeploy of app %s with image %s since last update was %d seconds ago.", name, image, updateAgeSeconds)
err = dockercli.ServiceRemove(
context.Background(),
@@ -3767,7 +4139,7 @@ func findAppInfo(image, name string, redeploy bool) (int, error) {
}
}
} else {
- //log.Printf("[INFO] NOT redeploying service %s since it was updated less than 3 minutes ago.", name)
+ log.Printf("[INFO] Skipping redeploy of app %s since last update was %d seconds ago.", name, updateAgeSeconds)
}
}
@@ -4010,9 +4382,16 @@ func sendAppRequest(ctx context.Context, incomingUrl, appName string, port int,
parsedRequest.Url = parsedRequest.BaseUrl
parsedRequest.BaseUrl = tmp
- // Run with proper hostname, but set to shuffle-worker to avoid specific host target.
- // This means running with VIP instead.
- if len(hostname) > 0 {
+ callbackUrl := os.Getenv("SHUFFLE_WORKER_SERVER_URL")
+ if len(callbackUrl) > 0 {
+ parsedRequest.BaseUrl = callbackUrl
+ if parsedRequest.Action.AppName == "shuffle-subflow" || parsedRequest.Action.AppName == "shuffle-subflow-v2" || parsedRequest.Action.AppName == "User Input" {
+ parsedRequest.BaseUrl = fmt.Sprintf("http://%s:%d", hostname, baseport)
+ //parsedRequest.Url = parsedRequest.BaseUrl
+ }
+ } else if len(hostname) > 0 {
+ // Run with proper hostname, but set to shuffle-worker to avoid specific host target.
+ // This means running with VIP instead.
parsedRequest.BaseUrl = fmt.Sprintf("http://%s:%d", hostname, baseport)
//parsedRequest.BaseUrl = fmt.Sprintf("http://shuffle-workers:%d", baseport)
//log.Printf("[DEBUG][%s] Changing hostname to local hostname in Docker network for WORKER URL: %s", workflowExecution.ExecutionId, parsedRequest.BaseUrl)
@@ -4030,6 +4409,9 @@ func sendAppRequest(ctx context.Context, incomingUrl, appName string, port int,
parsedRequest.FullExecution = *exec
}
+ restoreActionConfig(ctx, workflowExecution.ExecutionId, action, &parsedRequest.FullExecution)
+ parsedRequest.Action = *action
+
data, err := json.Marshal(parsedRequest)
if err != nil {
log.Printf("[ERROR] Failed marshalling worker request: %s", err)
@@ -4095,6 +4477,12 @@ func sendAppRequest(ctx context.Context, incomingUrl, appName string, port int,
log.Printf("[DEBUG] Setting client timeout to %d seconds for app request", timeoutInt)
client.Timeout = time.Duration(timeoutInt) * time.Second
}
+ } else if len(os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")) > 0 {
+ timeoutInt, err := strconv.Atoi(os.Getenv("SHUFFLE_APP_SDK_TIMEOUT"))
+ if err == nil && timeoutInt > 0 {
+ log.Printf("[DEBUG] Using SHUFFLE_APP_SDK_TIMEOUT=%d seconds for app request", timeoutInt)
+ client.Timeout = time.Duration(timeoutInt) * time.Second
+ }
}
// Content type required
@@ -4227,10 +4615,7 @@ func baseDeploy() {
if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" {
//log.Printf("APPENDING PROXY TO THE APP!")
- env = append(env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY")))
- env = append(env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY")))
- env = append(env, fmt.Sprintf("NO_PROXY=%s", os.Getenv("NO_PROXY")))
- env = append(env, fmt.Sprintf("no_proxy=%s", os.Getenv("no_proxy")))
+ env = appendAppProxyEnv(env)
}
if len(os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")) > 0 {
@@ -4697,7 +5082,7 @@ func main() {
}
}
-func checkUnfinished(resp http.ResponseWriter, request *http.Request, execRequest shuffle.OrborusExecutionRequest) {
+func checkUnfinished(execRequest shuffle.OrborusExecutionRequest) {
// Meant as a function that periodically checks whether previous executions have finished or not.
// Should probably be based on executedIds and finishedIds
// Schedule a check in the future instead?
@@ -4726,38 +5111,16 @@ func checkUnfinished(resp http.ResponseWriter, request *http.Request, execReques
sendResult(*exec, data)
}
-func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
- defer request.Body.Close()
- body, err := ioutil.ReadAll(request.Body)
- if err != nil {
- log.Printf("[WARNING] Failed reading body for stream result queue")
- resp.WriteHeader(400)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
- return
- }
-
- //log.Printf("[DEBUG] In run execution with body length %d", len(body))
- var execRequest shuffle.OrborusExecutionRequest
- err = json.Unmarshal(body, &execRequest)
- if err != nil {
- log.Printf("[WARNING] Failed shuffle.WorkflowExecution unmarshaling: %s", err)
- resp.WriteHeader(400)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
- return
- }
-
+func processRunExecution(execRequest shuffle.OrborusExecutionRequest) {
// Checks if a workflow is done 30 seconds later, and sends info to backend no matter what
go func() {
time.Sleep(time.Duration(30) * time.Second)
- checkUnfinished(resp, request, execRequest)
+ checkUnfinished(execRequest)
}()
window.AddEvent(time.Now())
ctx := context.Background()
- // FIXME: This should be PER EXECUTION
- //if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" {
- // Is it ok if these are standard? Should they be update-able after launch? Hmm
if len(execRequest.HTTPProxy) > 0 {
log.Printf("[DEBUG] Sending proxy info to child process")
os.Setenv("SHUFFLE_PASS_APP_PROXY", execRequest.ShufflePassProxyToApp)
@@ -4787,9 +5150,7 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
baseUrl = execRequest.BaseUrl
}
- // Setting to just have an auth available.
if len(execRequest.Authorization) > 0 && len(os.Getenv("AUTHORIZATION")) == 0 {
- //log.Printf("[DEBUG] Sending proxy info to child process")
os.Setenv("AUTHORIZATION", execRequest.Authorization)
}
@@ -4802,71 +5163,47 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
)
if err != nil {
- log.Printf("[ERROR][%s] Failed to create a new request", execRequest.ExecutionId)
- resp.WriteHeader(500)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ log.Printf("[ERROR][%s] Failed to create stream results request: %s", execRequest.ExecutionId, err)
return
}
client := shuffle.GetExternalClient(streamResultUrl)
newresp, err := client.Do(req)
if err != nil {
- log.Printf("[ERROR] Failed making request (2): %s", err)
- resp.WriteHeader(500)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ log.Printf("[ERROR][%s] Failed making stream results request: %s", execRequest.ExecutionId, err)
return
}
defer newresp.Body.Close()
- body, err = ioutil.ReadAll(newresp.Body)
+ body, err := ioutil.ReadAll(newresp.Body)
if err != nil {
- log.Printf("[ERROR][%s] Failed reading body (2): %s", execRequest.ExecutionId, err)
- resp.WriteHeader(500)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ log.Printf("[ERROR][%s] Failed reading stream results response body: %s", execRequest.ExecutionId, err)
return
}
if newresp.StatusCode != 200 {
- log.Printf("[ERROR][%s] Bad statuscode: %d, %s", execRequest.ExecutionId, newresp.StatusCode, string(body))
-
- if strings.Contains(string(body), "Workflowexecution is already finished") {
- log.Printf("[DEBUG] Shutting down (19)")
- //shutdown(workflowExecution, "", "", true)
- }
-
- resp.WriteHeader(500)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad statuscode: %d"}`, newresp.StatusCode)))
+ log.Printf("[ERROR][%s] Bad statuscode from stream results: %d, %s", execRequest.ExecutionId, newresp.StatusCode, string(body))
return
}
err = json.Unmarshal(body, &workflowExecution)
if err != nil {
- log.Printf("[ERROR] Failed workflowExecution unmarshal: %s", err)
- resp.WriteHeader(500)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ log.Printf("[ERROR][%s] Failed workflowExecution unmarshal: %s", execRequest.ExecutionId, err)
return
}
- //err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true)
err = setWorkflowExecution(ctx, workflowExecution, true)
if err != nil {
- log.Printf("[ERROR] Failed initializing execution saving for %s: %s", workflowExecution.ExecutionId, err)
+ log.Printf("[ERROR][%s] Failed initializing execution saving: %s", workflowExecution.ExecutionId, err)
}
if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" {
- log.Printf("[DEBUG] Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId)
- log.Printf("[DEBUG] Shutting down (20)")
-
- resp.WriteHeader(200)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad status for execution - already %s. Returning with 200 OK"}`, workflowExecution.Status)))
+ log.Printf("[DEBUG] Workflow %s is finished before dispatch. Exiting async run setup.", workflowExecution.ExecutionId)
return
}
- //startAction, extra, children, parents, visited, executed, nextActions, environments := shuffle.GetExecutionVariables(ctx, workflowExecution.ExecutionId)
-
extra := 0
for _, trigger := range workflowExecution.Workflow.Triggers {
- //log.Printf("Appname trigger (0): %s", trigger.AppName)
if trigger.AppName == "User Input" || trigger.AppName == "Shuffle Workflow" {
extra += 1
}
@@ -4875,15 +5212,10 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
log.Printf("[INFO][%s] (1) Status: %s, Results: %d, actions: %d", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extra)
if workflowExecution.Status != "EXECUTING" {
- log.Printf("[WARNING] Exiting as worker execution has status %s!", workflowExecution.Status)
- log.Printf("[DEBUG] Shutting down (38)")
- resp.WriteHeader(400)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad status %s for the workflow execution %s"}`, workflowExecution.Status, workflowExecution.ExecutionId)))
+ log.Printf("[WARNING][%s] Exiting async run as execution status is %s", workflowExecution.ExecutionId, workflowExecution.Status)
return
}
- //log.Printf("[DEBUG] Starting execution :O")
-
cacheKey := fmt.Sprintf("workflowexecution_%s", workflowExecution.ExecutionId)
execData, err := json.Marshal(workflowExecution)
if err != nil {
@@ -4898,15 +5230,36 @@ func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
err = executionInit(workflowExecution)
if err != nil {
log.Printf("[DEBUG][%s] Shutting down (30) - Workflow setup failed: %s", workflowExecution.ExecutionId, err)
- resp.WriteHeader(500)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error in execution init: %s"}`, err)))
return
- //shutdown(workflowExecution, "", "", true)
}
handleExecutionResult(workflowExecution)
- resp.WriteHeader(200)
- resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
+}
+
+func handleRunExecution(resp http.ResponseWriter, request *http.Request) {
+ defer request.Body.Close()
+ body, err := ioutil.ReadAll(request.Body)
+ if err != nil {
+ log.Printf("[WARNING] Failed reading body for stream result queue")
+ resp.WriteHeader(400)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ return
+ }
+
+ //log.Printf("[DEBUG] In run execution with body length %d", len(body))
+ var execRequest shuffle.OrborusExecutionRequest
+ err = json.Unmarshal(body, &execRequest)
+ if err != nil {
+ log.Printf("[WARNING] Failed shuffle.WorkflowExecution unmarshaling: %s", err)
+ resp.WriteHeader(400)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ return
+ }
+
+ resp.WriteHeader(http.StatusAccepted)
+ resp.Write([]byte(`{"success": true, "accepted": true}`))
+
+ go processRunExecution(execRequest)
}
func handleDownloadImage(resp http.ResponseWriter, request *http.Request) {
diff --git a/python-apps b/python-apps
new file mode 160000
index 00000000..da98c2e3
--- /dev/null
+++ b/python-apps
@@ -0,0 +1 @@
+Subproject commit da98c2e36a275dd8d126b3bf6e11a045d4d89eb0