From fef1e7fefda957f766490078144f2b435bd4dbba Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 15 Dec 2025 11:41:52 +0100 Subject: [PATCH 1/3] incorporate session id into renewal depends on https://github.com/signadot/signadot/pull/6457 and https://github.com/signadot/go-sdk/pull/76 previously, the api re-claimed on renewal, creating a corner case hole where more than 1 local connect could be running at the same time. Please review https://github.com/signadot/signadot/pull/6457 for details and explanation of the flow and what has been tested. Side Effects This change removed the need for dynamic session id updates. Misc Fixes The check for --cluster was pushed to earlier to avoid the possibility of registering and claiming a devbox and then failing due to a mis-specified cluster. --- go.mod | 29 ++++--- go.sum | 60 ++++++------- internal/command/local/connect.go | 14 +-- internal/devbox/session_manager.go | 87 +++++-------------- .../locald/sandboxmanager/sandbox_manager.go | 13 +-- .../sandboxmanager/sandboxes_watcher.go | 48 ++-------- 6 files changed, 82 insertions(+), 169 deletions(-) diff --git a/go.mod b/go.mod index 52fd209..a0f4d1a 100644 --- a/go.mod +++ b/go.mod @@ -23,14 +23,14 @@ require ( github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 github.com/oklog/run v1.1.0 github.com/panta/machineid v1.0.2 - github.com/signadot/go-sdk v0.3.8-0.20251205121950-c0d29d694826 + github.com/signadot/go-sdk v0.3.8-0.20251215103639-626d5e2dd629 github.com/signadot/libconnect v0.1.1-0.20251211124926-c72257c0d572 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 github.com/xeonx/timeago v1.0.0-rc5 github.com/zalando/go-keyring v0.2.6 - golang.org/x/term v0.37.0 + golang.org/x/term v0.38.0 google.golang.org/grpc v1.72.2 google.golang.org/protobuf v1.36.6 k8s.io/client-go v0.33.0 @@ -46,6 +46,7 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect @@ -97,8 +98,8 @@ require ( github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect @@ -115,11 +116,11 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.24.1 // indirect - github.com/go-openapi/errors v0.22.4 // indirect - github.com/go-openapi/jsonpointer v0.22.3 // indirect - github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/errors v0.22.5 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect github.com/go-openapi/loads v0.23.2 // indirect - github.com/go-openapi/spec v0.22.1 // indirect + github.com/go-openapi/spec v0.22.2 // indirect github.com/go-openapi/swag v0.25.4 // indirect github.com/go-openapi/validate v0.25.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -148,13 +149,13 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect go.mongodb.org/mongo-driver v1.17.6 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - golang.org/x/crypto v0.45.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + golang.org/x/crypto v0.46.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 57753bd..43dd7b9 100644 --- a/go.sum +++ b/go.sum @@ -156,18 +156,18 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= -github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM= -github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= -github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= -github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= -github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= -github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= +github.com/go-openapi/errors v0.22.5 h1:Yfv4O/PRYpNF3BNmVkEizcHb3uLVVsrDt3LNdgAKRY4= +github.com/go-openapi/errors v0.22.5/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4= github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= -github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k= -github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA= +github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc= +github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= @@ -421,8 +421,8 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 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/signadot/go-sdk v0.3.8-0.20251205121950-c0d29d694826 h1:rfZYfP43/4VIpGOa8oEgvDi8G1QVO0qv3luXyAy0gKE= -github.com/signadot/go-sdk v0.3.8-0.20251205121950-c0d29d694826/go.mod h1:hqXhjeoK2VC3/4hwoYZbTsuODm6zivao7kK+CYN60F4= +github.com/signadot/go-sdk v0.3.8-0.20251215103639-626d5e2dd629 h1:VG9J6ZmU+FupFH1A6EJkcA96DJecMcfvrJHaFabypJY= +github.com/signadot/go-sdk v0.3.8-0.20251215103639-626d5e2dd629/go.mod h1:6tP+viOzTMufLCThhIImjRySSktbloTpvgiJOF4IYU8= github.com/signadot/libconnect v0.1.1-0.20251211124926-c72257c0d572 h1:rSIQ4k87rJvVy/gDDTxPgdw5/Oao44XcibGhqfGTinQ= github.com/signadot/libconnect v0.1.1-0.20251211124926-c72257c0d572/go.mod h1:TREzo+zEezOeC4gsDzy8LpBy5p5WoVI+/ywclB9vhhM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -485,16 +485,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 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/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -509,8 +509,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 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= @@ -580,8 +580,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 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= @@ -604,8 +604,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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= @@ -653,11 +653,11 @@ golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -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.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= 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= @@ -667,8 +667,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 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.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -723,8 +723,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -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.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 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= diff --git a/internal/command/local/connect.go b/internal/command/local/connect.go index 2e45229..5a4f996 100644 --- a/internal/command/local/connect.go +++ b/internal/command/local/connect.go @@ -64,6 +64,12 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg return err } + // We will pass the connConfig to rootmanager and sandboxmanager + connConfig, err := cfg.GetConnectionConfig(cfg.Cluster) + if err != nil { + return err + } + // Get devbox claim and session ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -105,12 +111,6 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg return err } - // We will pass the connConfig to rootmanager and sandboxmanager - connConfig, err := cfg.GetConnectionConfig(cfg.Cluster) - if err != nil { - return err - } - // Resolve the user user, err := user.Current() if err != nil { @@ -174,7 +174,7 @@ func runConnectImpl(out, errOut io.Writer, log *slog.Logger, localConfig *config return err } if isRunning { - return fmt.Errorf("signadot is already connected\n") + return fmt.Errorf("signadot is already connected") } // Check version skew diff --git a/internal/devbox/session_manager.go b/internal/devbox/session_manager.go index 7fb1205..19cf23f 100644 --- a/internal/devbox/session_manager.go +++ b/internal/devbox/session_manager.go @@ -3,6 +3,7 @@ package devbox import ( "context" "fmt" + "net/http" "sync" "time" @@ -12,7 +13,6 @@ import ( "github.com/signadot/cli/internal/config" "github.com/signadot/cli/internal/locald/sandboxmanager/apiclient" "github.com/signadot/go-sdk/client/devboxes" - "github.com/signadot/go-sdk/models" ) const ( @@ -30,8 +30,6 @@ type SessionManager struct { lastError error lastErrorTime time.Time mu sync.RWMutex - // currentSessionID tracks the current active session ID, which may change - currentSessionID string } func NewSessionManager(log *slog.Logger, ciConfig *config.ConnectInvocationConfig) (*SessionManager, error) { @@ -60,24 +58,16 @@ func NewSessionManager(log *slog.Logger, ciConfig *config.ConnectInvocationConfi // to avoid stale connection errors after sleep periods dsm := &SessionManager{ - log: log.With("component", "devbox-session-manager"), - ciConfig: ciConfig, - doneCh: make(chan struct{}), - currentSessionID: ciConfig.DevboxSessionID, + log: log.With("component", "devbox-session-manager"), + ciConfig: ciConfig, + doneCh: make(chan struct{}), } return dsm, nil } -// getCurrentSessionID returns the current session ID in a thread-safe manner -func (dsm *SessionManager) getCurrentSessionID() string { - dsm.mu.RLock() - defer dsm.mu.RUnlock() - return dsm.currentSessionID -} - func (dsm *SessionManager) Start(ctx context.Context) { - currentSessionID := dsm.getCurrentSessionID() + currentSessionID := dsm.ciConfig.DevboxSessionID dsm.log.Info("Starting devbox session manager", "devboxID", dsm.ciConfig.DevboxID, @@ -133,39 +123,34 @@ func (dsm *SessionManager) renewSession(ctx context.Context) { params := devboxes.NewRenewDevboxParams(). WithContext(ctx). WithOrgName(authInfo.OrgName). - WithDevboxID(dsm.ciConfig.DevboxID) + WithDevboxID(dsm.ciConfig.DevboxID). + WithDevboxSessionID(dsm.ciConfig.DevboxSessionID) - currentSessionID := dsm.getCurrentSessionID() + log := dsm.log.With("devboxID", dsm.ciConfig.DevboxID, + "sessionID", dsm.ciConfig.DevboxSessionID, + "orgName", authInfo.OrgName) - dsm.log.Debug("renewSession: calling RenewDevbox", - "orgName", authInfo.OrgName, - "devboxID", dsm.ciConfig.DevboxID, - "sessionID", currentSessionID) + log.Debug("renewSession: calling RenewDevbox") resp, err := apiClient.Devboxes.RenewDevbox(params) if err != nil { - dsm.log.Error("Failed to renew devbox session", "error", err) + log.Error("Failed to renew devbox session", "error", err) dsm.setError(err) return } - dsm.log.Debug("renewSession: RenewDevbox call succeeded", + log.Debug("renewSession: RenewDevbox call succeeded", "statusCode", resp.Code()) - // Check if the session has changed and update tracking - // Renewals automatically claim a new session if released - dsm.handleSessionChange(resp.Payload) - - updatedSessionID := dsm.getCurrentSessionID() - - dsm.log.Debug("Renewed devbox session", - "devboxID", dsm.ciConfig.DevboxID, - "sessionID", updatedSessionID, - "statusCode", resp.Code()) + if resp.Code() == http.StatusOK { + dsm.setError(nil) + } else { + dsm.setError(fmt.Errorf("error renewing devbox: %d %s", resp.Code(), http.StatusText(resp.Code()))) + } } func (dsm *SessionManager) releaseSession() { - currentSessionID := dsm.getCurrentSessionID() + currentSessionID := dsm.ciConfig.DevboxSessionID dsm.log.Info("Releasing devbox session", "devboxID", dsm.ciConfig.DevboxID, @@ -209,6 +194,7 @@ func (dsm *SessionManager) releaseSession() { "devboxID", dsm.ciConfig.DevboxID, "sessionID", currentSessionID, "statusCode", resp.Code()) + } func (dsm *SessionManager) Stop(ctx context.Context) { @@ -224,37 +210,6 @@ func (dsm *SessionManager) Stop(ctx context.Context) { dsm.releaseSession() } -// handleSessionChange checks if the session has changed and updates tracking. -// Renewals automatically claim a new session if released, so the response always has a session. -func (dsm *SessionManager) handleSessionChange(dbox *models.Devbox) { - session := dbox.Status.Session - if session == nil { - // This shouldn't happen - renewals auto-claim, so there should always be a session - err := fmt.Errorf("unexpected: renewal succeeded but no session in response") - dsm.log.Error("Renewal response missing session", "devboxID", dsm.ciConfig.DevboxID) - dsm.setError(err) - return - } - - // Check if session ID has changed and update if needed - dsm.mu.Lock() - currentID := dsm.currentSessionID - defer dsm.mu.Unlock() - if session.ID != currentID { - // Session ID changed - update our tracking and continue - oldID := currentID - dsm.currentSessionID = session.ID - dsm.lastError = nil // Clear any previous errors since we're now tracking the new session - dsm.log.Info("Devbox session ID changed, updating tracking", - "devboxID", dsm.ciConfig.DevboxID, - "oldSessionID", oldID, - "newSessionID", session.ID) - } else { - // Same session ID - clear any errors on successful renewal - dsm.lastError = nil - } -} - // setError records an error func (dsm *SessionManager) setError(err error) { dsm.mu.Lock() @@ -273,5 +228,5 @@ func (dsm *SessionManager) GetStatus() (healthy bool, devboxID string, sessionID } healthy = dsm.lastError == nil - return healthy, dsm.ciConfig.DevboxID, dsm.currentSessionID, dsm.lastErrorTime, dsm.lastError + return healthy, dsm.ciConfig.DevboxID, dsm.ciConfig.DevboxSessionID, dsm.lastErrorTime, dsm.lastError } diff --git a/internal/locald/sandboxmanager/sandbox_manager.go b/internal/locald/sandboxmanager/sandbox_manager.go index 24c79b5..7febc50 100644 --- a/internal/locald/sandboxmanager/sandbox_manager.go +++ b/internal/locald/sandboxmanager/sandbox_manager.go @@ -14,9 +14,9 @@ import ( "github.com/signadot/cli/internal/config" "github.com/signadot/cli/internal/devbox" - "github.com/signadot/cli/internal/locald/sandboxmanager/apiclient" rootapi "github.com/signadot/cli/internal/locald/api/rootmanager" sbapi "github.com/signadot/cli/internal/locald/api/sandboxmanager" + "github.com/signadot/cli/internal/locald/sandboxmanager/apiclient" tunapiclient "github.com/signadot/libconnect/common/apiclient" "github.com/signadot/libconnect/common/controlplaneproxy" "google.golang.org/grpc" @@ -145,16 +145,7 @@ func (m *sandboxManager) Run(ctx context.Context) error { log: m.log, } - // Create the watcher with a function to get the current session ID - // This allows the watcher to use the updated session ID if it changes - getSessionID := func() string { - if m.devboxSessionMgr == nil { - return m.ciConfig.DevboxSessionID - } - _, _, sessionID, _, _ := m.devboxSessionMgr.GetStatus() - return sessionID - } - sbmWatcher := newSandboxManagerWatcher(m.log, getSessionID, m.revtunClient, oiu, m.shutdownCh) + sbmWatcher := newSandboxManagerWatcher(m.log, m.ciConfig.DevboxSessionID, m.revtunClient, oiu, m.shutdownCh) // Register our service in gRPC server m.sbmServer = newSandboxManagerGRPCServer(m.log, m.ciConfig, m.portForward, m.ctlPlaneProxy, diff --git a/internal/locald/sandboxmanager/sandboxes_watcher.go b/internal/locald/sandboxmanager/sandboxes_watcher.go index 751582a..2ec2f5f 100644 --- a/internal/locald/sandboxmanager/sandboxes_watcher.go +++ b/internal/locald/sandboxmanager/sandboxes_watcher.go @@ -23,10 +23,6 @@ type sbmWatcher struct { log *slog.Logger oiu *operatorInfoUpdater - // getDevboxSessionID is a function that returns the current devbox session ID - // This allows the watcher to use the updated session ID if it changes - getDevboxSessionID func() string - // sandbox controllers sbMu sync.Mutex status svchealth.ServiceHealth @@ -35,19 +31,18 @@ type sbmWatcher struct { tunAPIClient tunapiclient.Client // current session ID being watched (to detect changes) - currentWatchedSessionID string - sessionIDMu sync.RWMutex + devboxSessionID string // shutdown shutdownCh chan struct{} } -func newSandboxManagerWatcher(log *slog.Logger, getDevboxSessionID func() string, revtunClient func() revtun.Client, +func newSandboxManagerWatcher(log *slog.Logger, devboxSessionID string, revtunClient func() revtun.Client, oiu *operatorInfoUpdater, shutdownCh chan struct{}) *sbmWatcher { srv := &sbmWatcher{ - log: log, - oiu: oiu, - getDevboxSessionID: getDevboxSessionID, + log: log, + oiu: oiu, + devboxSessionID: devboxSessionID, status: svchealth.ServiceHealth{ Healthy: false, LastErrorReason: "Starting", @@ -67,21 +62,8 @@ func (sbw *sbmWatcher) run(ctx context.Context, tunAPIClient tunapiclient.Client func (sbw *sbmWatcher) watchSandboxes(ctx context.Context, tunAPIClient tunapiclient.Client) { // watch loop for { - // Get the current session ID - it may have changed - currentSessionID := sbw.getDevboxSessionID() - if currentSessionID == "" { - sbw.setError("devbox session ID is empty", nil) - <-time.After(3 * time.Second) - continue - } - - // Update the current watched session ID - sbw.sessionIDMu.Lock() - sbw.currentWatchedSessionID = currentSessionID - sbw.sessionIDMu.Unlock() - sbwClient, err := tunAPIClient.WatchLocalSandboxes(ctx, &tunapiv1.WatchLocalSandboxesRequest{ - MachineId: currentSessionID, + MachineId: sbw.devboxSessionID, }) if err != nil { // don't retry if the context has been cancelled @@ -95,7 +77,7 @@ func (sbw *sbmWatcher) watchSandboxes(ctx context.Context, tunAPIClient tunapicl <-time.After(3 * time.Second) continue } - sbw.log.Debug("successfully got local sandboxes watch client", "sessionID", currentSessionID) + sbw.log.Debug("successfully got local sandboxes watch client", "sessionID", sbw.devboxSessionID) sbw.readStream(ctx, sbwClient) } } @@ -129,24 +111,8 @@ func (sbw *sbmWatcher) readStream(ctx context.Context, } }() - // Main loop: select between ticker (session ID check) and stream events for { select { - case <-ticker.C: - // Check if session ID has changed - if so, restart the stream - currentSessionID := sbw.getDevboxSessionID() - sbw.sessionIDMu.RLock() - watchedSessionID := sbw.currentWatchedSessionID - sbw.sessionIDMu.RUnlock() - - if currentSessionID != "" && currentSessionID != watchedSessionID { - sbw.log.Info("Devbox session ID changed, restarting watch stream", - "oldSessionID", watchedSessionID, - "newSessionID", currentSessionID) - // Close the current stream to force a restart - sbwClient.CloseSend() - return - } case result, ok := <-streamCh: if !ok { // Channel closed, stream ended From a6d12cb271f686c209c8fdadd16c7f57c752d4aa Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 15 Dec 2025 14:50:17 +0100 Subject: [PATCH 2/3] CR --- .../locald/sandboxmanager/sandbox_manager.go | 22 ----- .../sandboxmanager/sandboxes_watcher.go | 91 +++++++------------ 2 files changed, 31 insertions(+), 82 deletions(-) diff --git a/internal/locald/sandboxmanager/sandbox_manager.go b/internal/locald/sandboxmanager/sandbox_manager.go index 7febc50..6f733ee 100644 --- a/internal/locald/sandboxmanager/sandbox_manager.go +++ b/internal/locald/sandboxmanager/sandbox_manager.go @@ -8,13 +8,11 @@ import ( "os" "os/signal" "syscall" - "time" "log/slog" "github.com/signadot/cli/internal/config" "github.com/signadot/cli/internal/devbox" - rootapi "github.com/signadot/cli/internal/locald/api/rootmanager" sbapi "github.com/signadot/cli/internal/locald/api/sandboxmanager" "github.com/signadot/cli/internal/locald/sandboxmanager/apiclient" tunapiclient "github.com/signadot/libconnect/common/apiclient" @@ -261,23 +259,3 @@ func (m *sandboxManager) revtunClient() revtun.Client { panic(fmt.Errorf("invalid inbound protocol: %s", m.connConfig.Inbound.Protocol)) } } - -// shutdownRootManager calls the root manager's Shutdown API to shut down tunnel and services -func (m *sandboxManager) shutdownRootManager() { - if m.sbmServer == nil { - return - } - rootClient := m.sbmServer.getRootClient() - if rootClient == nil { - m.log.Warn("Could not get root manager client to shutdown") - return - } - shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - _, err := rootClient.Shutdown(shutdownCtx, &rootapi.ShutdownRequest{}) - if err != nil { - m.log.Warn("Failed to shutdown root manager", "error", err) - } else { - m.log.Info("Root manager shutdown requested") - } -} diff --git a/internal/locald/sandboxmanager/sandboxes_watcher.go b/internal/locald/sandboxmanager/sandboxes_watcher.go index 2ec2f5f..7d287d9 100644 --- a/internal/locald/sandboxmanager/sandboxes_watcher.go +++ b/internal/locald/sandboxmanager/sandboxes_watcher.go @@ -84,71 +84,42 @@ func (sbw *sbmWatcher) watchSandboxes(ctx context.Context, tunAPIClient tunapicl func (sbw *sbmWatcher) readStream(ctx context.Context, sbwClient tunapiv1.TunnelAPI_WatchLocalSandboxesClient) { - // Check for session ID changes periodically while reading the stream - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - - // Channel for stream events - type streamResult struct { - event *tunapiv1.WatchLocalSandboxesResponse - err error - } - streamCh := make(chan streamResult, 1) - - // Goroutine to receive from the stream - go func() { - defer close(streamCh) - for { - event, err := sbwClient.Recv() - select { - case streamCh <- streamResult{event: event, err: err}: - if err != nil { - return - } - case <-ctx.Done(): - return - } - } - }() - for { + event, err := sbwClient.Recv() + if err == nil { + sbw.setSuccess(ctx) + sbw.processStreamEvent(event) + continue + } + // just return if the context has been cancelled select { - case result, ok := <-streamCh: - if !ok { - // Channel closed, stream ended - return - } - if result.err == nil { - sbw.setSuccess(ctx) - sbw.processStreamEvent(result.event) - continue - } - // Handle stream error - grpcStatus, ok := status.FromError(result.err) - if !ok { - sbw.setError("sandboxes watch grpc stream error: no status", result.err) - return - } - switch grpcStatus.Code() { - case codes.OK: - sbw.log.Debug("sandboxes watch error code is ok") - sbw.processStreamEvent(result.event) - continue - case codes.Internal: - sbw.setError("sandboxes watch internal grpc error", result.err) - <-time.After(3 * time.Second) - case codes.Unimplemented: - sbw.setError(SandboxesWatcherUnimplemented, nil) - // in this case, check again in 1 minutes - <-time.After(1 * time.Minute) - default: - sbw.setError("sandbox watch error", result.err) - <-time.After(3 * time.Second) - } - return case <-ctx.Done(): return + default: + } + // extract the grpc status + grpcStatus, ok := status.FromError(err) + if !ok { + sbw.setError("sandboxes watch grpc stream error: no status", err) + break + } + switch grpcStatus.Code() { + case codes.OK: + sbw.log.Debug("sandboxes watch error code is ok") + sbw.processStreamEvent(event) + continue + case codes.Internal: + sbw.setError("sandboxes watch internal grpc error", err) + <-time.After(3 * time.Second) + case codes.Unimplemented: + sbw.setError(SandboxesWatcherUnimplemented, nil) + // in this case, check again in 1 minutes + <-time.After(1 * time.Minute) + default: + sbw.setError("sandbox watch error", err) + <-time.After(3 * time.Second) } + break } } From 5679388a3fff2b8f977fc4ae85913e861a6ce35d Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 15 Dec 2025 15:13:10 +0100 Subject: [PATCH 3/3] use merged-to-main new go-sdk --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a0f4d1a..935770e 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 github.com/oklog/run v1.1.0 github.com/panta/machineid v1.0.2 - github.com/signadot/go-sdk v0.3.8-0.20251215103639-626d5e2dd629 + github.com/signadot/go-sdk v0.3.8-0.20251215135046-386e2c51b39c github.com/signadot/libconnect v0.1.1-0.20251211124926-c72257c0d572 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 diff --git a/go.sum b/go.sum index 43dd7b9..28cef4a 100644 --- a/go.sum +++ b/go.sum @@ -421,8 +421,8 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 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/signadot/go-sdk v0.3.8-0.20251215103639-626d5e2dd629 h1:VG9J6ZmU+FupFH1A6EJkcA96DJecMcfvrJHaFabypJY= -github.com/signadot/go-sdk v0.3.8-0.20251215103639-626d5e2dd629/go.mod h1:6tP+viOzTMufLCThhIImjRySSktbloTpvgiJOF4IYU8= +github.com/signadot/go-sdk v0.3.8-0.20251215135046-386e2c51b39c h1:8n1fYxn+FQS8pW/T4ZW+XaMLnLhXWfMrI1X8h3EudoE= +github.com/signadot/go-sdk v0.3.8-0.20251215135046-386e2c51b39c/go.mod h1:6tP+viOzTMufLCThhIImjRySSktbloTpvgiJOF4IYU8= github.com/signadot/libconnect v0.1.1-0.20251211124926-c72257c0d572 h1:rSIQ4k87rJvVy/gDDTxPgdw5/Oao44XcibGhqfGTinQ= github.com/signadot/libconnect v0.1.1-0.20251211124926-c72257c0d572/go.mod h1:TREzo+zEezOeC4gsDzy8LpBy5p5WoVI+/ywclB9vhhM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=