diff --git a/controller/cmd/exporter-set-controller/main.go b/controller/cmd/exporter-set-controller/main.go index 93eb6a9ef..2f121090b 100644 --- a/controller/cmd/exporter-set-controller/main.go +++ b/controller/cmd/exporter-set-controller/main.go @@ -29,6 +29,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" @@ -36,6 +37,7 @@ import ( jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter/controller/api/v1alpha1" virtualtargetv1alpha1 "github.com/jumpstarter-dev/jumpstarter/controller/api/virtualtarget/v1alpha1" "github.com/jumpstarter-dev/jumpstarter/controller/internal/exporterset" + "github.com/jumpstarter-dev/jumpstarter/controller/internal/exporterset/provisioners/kubevirt" "github.com/jumpstarter-dev/jumpstarter/controller/internal/exporterset/provisioners/qemu" ) @@ -101,13 +103,6 @@ func main() { os.Exit(1) } - // Select the provisioner implementation based on the flag - prov, err := selectProvisioner(provisioner) - if err != nil { - setupLog.Error(err, "unsupported provisioner", "provisioner", provisioner) - os.Exit(1) - } - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Cache: cache.Options{ @@ -128,6 +123,12 @@ func main() { os.Exit(1) } + prov, err := selectProvisioner(provisioner, mgr.GetClient()) + if err != nil { + setupLog.Error(err, "unsupported provisioner", "provisioner", provisioner) + os.Exit(1) + } + if err = (&exporterset.ExporterSetReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -157,11 +158,14 @@ func main() { // selectProvisioner returns the Provisioner implementation for the given name. // Add new provisioners here as they are implemented. -func selectProvisioner(name string) (exporterset.Provisioner, error) { +func selectProvisioner(name string, c client.Client) (exporterset.Provisioner, error) { switch name { case qemu.ProvisionerName: return qemu.New(version), nil + case kubevirt.ProvisionerName: + return kubevirt.New(version, c), nil default: - return nil, fmt.Errorf("unknown provisioner %q; supported: %s", name, qemu.ProvisionerName) + return nil, fmt.Errorf("unknown provisioner %q; supported: %s, %s", + name, qemu.ProvisionerName, kubevirt.ProvisionerName) } } diff --git a/controller/deploy/operator/go.mod b/controller/deploy/operator/go.mod index 1ce7ca112..d773c3f89 100644 --- a/controller/deploy/operator/go.mod +++ b/controller/deploy/operator/go.mod @@ -11,11 +11,11 @@ require ( github.com/onsi/gomega v1.36.2 github.com/openshift/api v0.0.0-20251023135607-98e18dae8c7a github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - k8s.io/api v0.34.1 - k8s.io/apimachinery v0.34.1 - k8s.io/apiserver v0.34.1 - k8s.io/client-go v0.34.1 - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + k8s.io/api v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/controller-runtime v0.22.3 sigs.k8s.io/yaml v1.6.0 ) @@ -108,7 +108,7 @@ require ( go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.47.0 // indirect @@ -130,8 +130,8 @@ require ( gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.34.1 // indirect - k8s.io/component-base v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect diff --git a/controller/deploy/operator/go.sum b/controller/deploy/operator/go.sum index 90eb2f8e6..2935c760e 100644 --- a/controller/deploy/operator/go.sum +++ b/controller/deploy/operator/go.sum @@ -241,8 +241,8 @@ 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= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -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/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -323,24 +323,24 @@ 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= -k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= -k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= -k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= -k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= 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-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= diff --git a/controller/go.mod b/controller/go.mod index 5a16fe382..62e43ce9f 100644 --- a/controller/go.mod +++ b/controller/go.mod @@ -9,8 +9,8 @@ require ( github.com/go-logr/logr v1.4.3 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 github.com/zitadel/oidc/v3 v3.34.1 @@ -19,14 +19,16 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 - k8s.io/api v0.33.0 - k8s.io/apiextensions-apiserver v0.33.0 - k8s.io/apimachinery v0.33.0 - k8s.io/apiserver v0.33.0 - k8s.io/client-go v0.33.0 - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 + k8s.io/api v0.34.3 + k8s.io/apiextensions-apiserver v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + kubevirt.io/api v1.8.4 + kubevirt.io/containerized-data-importer-api v1.64.0 sigs.k8s.io/controller-runtime v0.21.0 - sigs.k8s.io/yaml v1.4.0 + sigs.k8s.io/yaml v1.6.0 ) require ( @@ -38,15 +40,17 @@ require ( github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/coreos/go-oidc v2.3.0+incompatible // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-chi/chi/v5 v5.2.0 // indirect @@ -62,8 +66,8 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.23.2 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -75,12 +79,14 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/muhlemmer/gu v0.3.1 // indirect github.com/muhlemmer/httpforwarded v0.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/custom-resource-status v1.1.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect @@ -88,8 +94,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect @@ -97,11 +103,18 @@ require ( github.com/zitadel/logging v0.6.1 // indirect github.com/zitadel/schema v1.3.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.47.0 // indirect golang.org/x/net v0.49.0 // indirect @@ -117,10 +130,12 @@ require ( gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.33.0 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) diff --git a/controller/go.sum b/controller/go.sum index bc4fe72a5..e9c3980e9 100644 --- a/controller/go.sum +++ b/controller/go.sum @@ -1,11 +1,17 @@ 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= filippo.io/bigmod v0.0.3 h1:qmdCFHmEMS+PRwzrW6eUrgA4Q3T8D6bRcjsypDMtWHM= filippo.io/bigmod v0.0.3/go.mod h1:WxGvOYE0OUaBC2N112Dflb3CjOnMBuNRA2UWZc2UbPE= filippo.io/keygen v0.0.0-20240718133620-7f162efbbd87 h1:HlcHAMbI9Xvw3aWnhPngghMl5AKE2GOvjmvSGOKzCcI= filippo.io/keygen v0.0.0-20240718133620-7f162efbbd87/go.mod h1:nAs0+DyACEQGudhkTwlPC9atyqDYC7ZotgZR7D8OwXM= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -18,32 +24,52 @@ github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3z github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +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/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0= github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +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/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= @@ -52,6 +78,9 @@ github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 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 v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -59,11 +88,19 @@ 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-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/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +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.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= 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.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 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= @@ -75,6 +112,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 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.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -83,36 +121,63 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +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 v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= -github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +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.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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -123,6 +188,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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= @@ -134,35 +201,62 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +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.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= +github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= @@ -176,10 +270,14 @@ github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -188,6 +286,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= @@ -205,6 +304,9 @@ 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/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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zitadel/logging v0.6.1 h1:Vyzk1rl9Kq9RCevcpX6ujUaTYFX43aa4LkvV1TvUk+Y= github.com/zitadel/logging v0.6.1/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow= github.com/zitadel/oidc/v3 v3.34.1 h1:/rxx2HxEowd8Sdb8sxcRxTu9pLy3/TXBLrewKOUMTHA= @@ -213,8 +315,14 @@ github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0= github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc= 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/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= 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/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= 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.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= @@ -223,12 +331,18 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= 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.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -237,43 +351,102 @@ 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +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-20180906233101-161cd47e91fd/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.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.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/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.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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -285,56 +458,116 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/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.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= -k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= -k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= -k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= -k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= -k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= -k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= -k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= -k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= -k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= -k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= -k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 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-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +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-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +kubevirt.io/api v1.8.4 h1:L0mULVzwCvcaw16svfDKseqF/c3gIP+Kjs3F90l4avk= +kubevirt.io/api v1.8.4/go.mod h1:EUaPeD1GQK4IIEp8TVtk5dsFlYQGzFfcWi4CV4rs0rY= +kubevirt.io/containerized-data-importer-api v1.64.0 h1:yBLY6qEogUp8F+XSJvabIkx9uEEDeToYDfRr0mbUxJc= +kubevirt.io/containerized-data-importer-api v1.64.0/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/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/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +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.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +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/controller/internal/exporterset/exporterconfig.go b/controller/internal/exporterset/exporterconfig.go index 3d7d6e545..69070ac14 100644 --- a/controller/internal/exporterset/exporterconfig.go +++ b/controller/internal/exporterset/exporterconfig.go @@ -97,7 +97,7 @@ func (r *ExporterSetReconciler) buildExporterConfigSecret( caBase64 := base64.StdEncoding.EncodeToString([]byte(caBundle)) drivers := es.Spec.Template.Spec.Drivers - drivers, err = r.Provisioner.EnrichExporterExport(drivers, mergedParameters) + drivers, err = r.Provisioner.EnrichExporterExport(drivers, mergedParameters, exporter) if err != nil { return nil, fmt.Errorf("enrich drivers for %s: %w", exporter.Name, err) } diff --git a/controller/internal/exporterset/provisioner.go b/controller/internal/exporterset/provisioner.go index a03e4df25..66f87ed8b 100644 --- a/controller/internal/exporterset/provisioner.go +++ b/controller/internal/exporterset/provisioner.go @@ -59,6 +59,7 @@ type Provisioner interface { EnrichExporterExport( drivers []virtualtargetv1alpha1.DriverConfig, mergedParameters map[string]interface{}, + exporter *jumpstarterdevv1alpha1.Exporter, ) ([]virtualtargetv1alpha1.DriverConfig, error) // Cleanup is called when an exporter instance is being diff --git a/controller/internal/exporterset/provisioners/kubevirt/kubevirt.go b/controller/internal/exporterset/provisioners/kubevirt/kubevirt.go new file mode 100644 index 000000000..19d9ee735 --- /dev/null +++ b/controller/internal/exporterset/provisioners/kubevirt/kubevirt.go @@ -0,0 +1,558 @@ +/* +Copyright 2026 The Jumpstarter Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package kubevirt implements the kubevirt.jumpstarter.dev provisioner +// for ExporterSets. It creates VirtualMachine CRs on a remote KubeVirt +// cluster and lightweight exporter-only Pods on the management cluster. +package kubevirt + +import ( + "context" + "encoding/json" + "fmt" + "maps" + "strings" + + jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter/controller/api/v1alpha1" + virtualtargetv1alpha1 "github.com/jumpstarter-dev/jumpstarter/controller/api/virtualtarget/v1alpha1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubevirtv1 "kubevirt.io/api/core/v1" + cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + ProvisionerName = "kubevirt.jumpstarter.dev" + + DefaultExporterImage = "quay.io/jumpstarter-dev/jumpstarter:latest" + + jmpExecBinaryPath = "/jumpstarter/bin/jumpstarter-exec" + configMountPath = "/etc/jumpstarter/exporters" + sharedVolumeName = "shared" + sharedMountPath = "/shared" + + labelManagedBy = "app.kubernetes.io/managed-by" + labelExporter = "jumpstarter.dev/exporter" + labelProvenance = "jumpstarter.dev/provisioner" + + credentialsVolumeName = "kubevirt-credentials" + credentialsMountPath = "/etc/jumpstarter/kubevirt" + kubeconfigFilename = "kubeconfig" + + kubevirtDriverType = "jumpstarter_driver_kubevirt.driver.Kubevirt" + kubevirtPowerType = "jumpstarter_driver_kubevirt.driver.KubevirtPower" + kubevirtConsoleType = "jumpstarter_driver_kubevirt.driver.KubevirtConsole" + kubevirtStorageType = "jumpstarter_driver_kubevirt.driver.KubevirtStorage" + tcpDriverType = "jumpstarter_driver_network.driver.TcpNetwork" +) + +// Provisioner implements the kubevirt.jumpstarter.dev provisioner. +type Provisioner struct { + Version string + Client client.Client + remoteClients *remoteClientCache +} + +// New creates a new KubeVirt provisioner. +func New(version string, c client.Client) *Provisioner { + return &Provisioner{ + Version: version, + Client: c, + remoteClients: newRemoteClientCache(), + } +} + +func (p *Provisioner) Name() string { + return ProvisionerName +} + +func (p *Provisioner) resolveImage(image string) string { + if p.Version == "" || p.Version == "dev" || strings.Contains(p.Version, "-g") { + return image + } + version := strings.TrimPrefix(p.Version, "v") + if base, ok := strings.CutSuffix(image, ":latest"); ok { + return base + ":" + version + } + return image +} + +func (p *Provisioner) resolveImageSpec(spec *virtualtargetv1alpha1.ImageSpec, defaultImage string) (string, corev1.PullPolicy) { + image := p.resolveImage(defaultImage) + pullPolicy := corev1.PullIfNotPresent + + if spec != nil { + if spec.Image != "" { + image = spec.Image + } + if spec.ImagePullPolicy != "" { + pullPolicy = spec.ImagePullPolicy + } + } + + return image, pullPolicy +} + +// RenderPod creates a VirtualMachine on the remote KubeVirt cluster and +// returns a lightweight exporter-only Pod for the management cluster. +func (p *Provisioner) RenderPod( + ctx context.Context, + exporterSet *virtualtargetv1alpha1.ExporterSet, + vtc *virtualtargetv1alpha1.VirtualTargetClass, + mergedParameters map[string]any, + images *virtualtargetv1alpha1.ImageOverrides, + exporter *jumpstarterdevv1alpha1.Exporter, +) (*corev1.Pod, error) { + params, err := parseParams(mergedParameters) + if err != nil { + return nil, fmt.Errorf("parse kubevirt parameters: %w", err) + } + + remoteClient, err := p.getRemoteClient(ctx, vtc) + if err != nil { + return nil, fmt.Errorf("get remote client: %w", err) + } + + vm, err := p.buildVirtualMachine(exporter, params) + if err != nil { + return nil, fmt.Errorf("build VirtualMachine: %w", err) + } + + if err := remoteClient.Create(ctx, vm); err != nil { + if !apierrors.IsAlreadyExists(err) { + return nil, fmt.Errorf("create VirtualMachine %s/%s on remote cluster: %w", + vm.Namespace, vm.Name, err) + } + } + + pod := p.buildExporterPod(exporterSet, vtc, images, exporter) + + if vtc.Spec.Scheduling != nil { + if vtc.Spec.Scheduling.NodeSelector != nil { + pod.Spec.NodeSelector = maps.Clone(vtc.Spec.Scheduling.NodeSelector) + } + if vtc.Spec.Scheduling.Tolerations != nil { + pod.Spec.Tolerations = append([]corev1.Toleration(nil), vtc.Spec.Scheduling.Tolerations...) + } + } + + return pod, nil +} + +// buildVirtualMachine constructs a KubeVirt VirtualMachine CR. +// Created with RunStrategy Halted - the Python driver's power.on() starts it. +func (p *Provisioner) buildVirtualMachine( + exporter *jumpstarterdevv1alpha1.Exporter, + params *KubevirtParams, +) (*kubevirtv1.VirtualMachine, error) { + runStrategy := kubevirtv1.RunStrategyHalted + + memory, err := resource.ParseQuantity(params.Memory) + if err != nil { + return nil, fmt.Errorf("parse memory %q: %w", params.Memory, err) + } + + vm := &kubevirtv1.VirtualMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: exporter.Name, + Namespace: params.Namespace, + Labels: map[string]string{ + labelManagedBy: "jumpstarter", + labelExporter: exporter.Name, + labelProvenance: ProvisionerName, + }, + }, + Spec: kubevirtv1.VirtualMachineSpec{ + RunStrategy: &runStrategy, + Template: &kubevirtv1.VirtualMachineInstanceTemplateSpec{ + Spec: kubevirtv1.VirtualMachineInstanceSpec{ + Domain: kubevirtv1.DomainSpec{ + CPU: &kubevirtv1.CPU{ + Cores: uint32(params.CPU), + }, + Memory: &kubevirtv1.Memory{ + Guest: &memory, + }, + Devices: kubevirtv1.Devices{ + Disks: []kubevirtv1.Disk{ + { + Name: "root", + DiskDevice: kubevirtv1.DiskDevice{ + Disk: &kubevirtv1.DiskTarget{ + Bus: kubevirtv1.DiskBusVirtio, + }, + }, + }, + }, + Interfaces: []kubevirtv1.Interface{ + { + Name: "default", + InterfaceBindingMethod: kubevirtv1.InterfaceBindingMethod{ + Masquerade: &kubevirtv1.InterfaceMasquerade{}, + }, + }, + }, + }, + }, + Networks: []kubevirtv1.Network{ + { + Name: "default", + NetworkSource: kubevirtv1.NetworkSource{ + Pod: &kubevirtv1.PodNetwork{}, + }, + }, + }, + Volumes: []kubevirtv1.Volume{}, + }, + }, + }, + } + + if params.Storage != nil && params.Storage.Type == StorageTypeDataVolume { + dvName := exporter.Name + "-root" + dvSource := &cdiv1beta1.DataVolumeSource{} + if params.Storage.Source.HTTP != nil { + dvSource.HTTP = &cdiv1beta1.DataVolumeSourceHTTP{ + URL: params.Storage.Source.HTTP.URL, + } + } else if params.Storage.Source.Registry != nil { + url := "docker://" + params.Storage.Source.Registry.Image + dvSource.Registry = &cdiv1beta1.DataVolumeSourceRegistry{ + URL: &url, + } + } else if params.Storage.Source.PVC != nil { + dvSource.PVC = &cdiv1beta1.DataVolumeSourcePVC{ + Name: params.Storage.Source.PVC.Name, + Namespace: params.Storage.Source.PVC.Namespace, + } + } + + diskSize := resource.MustParse(params.DiskSize) + accessModes := make([]corev1.PersistentVolumeAccessMode, len(params.Storage.AccessModes)) + for i, m := range params.Storage.AccessModes { + accessModes[i] = corev1.PersistentVolumeAccessMode(m) + } + + dvSpec := cdiv1beta1.DataVolumeSpec{ + Source: dvSource, + Storage: &cdiv1beta1.StorageSpec{ + AccessModes: accessModes, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: diskSize, + }, + }, + }, + } + if params.Storage.StorageClassName != "" { + dvSpec.Storage.StorageClassName = ¶ms.Storage.StorageClassName + } + + vm.Spec.DataVolumeTemplates = []kubevirtv1.DataVolumeTemplateSpec{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: dvName, + }, + Spec: dvSpec, + }, + } + vm.Spec.Template.Spec.Volumes = append(vm.Spec.Template.Spec.Volumes, kubevirtv1.Volume{ + Name: "root", + VolumeSource: kubevirtv1.VolumeSource{ + DataVolume: &kubevirtv1.DataVolumeSource{ + Name: dvName, + }, + }, + }) + } else { + vm.Spec.Template.Spec.Volumes = append(vm.Spec.Template.Spec.Volumes, kubevirtv1.Volume{ + Name: "root", + VolumeSource: kubevirtv1.VolumeSource{ + ContainerDisk: &kubevirtv1.ContainerDiskSource{ + Image: params.Image, + }, + }, + }) + } + + vmiSpec := &vm.Spec.Template.Spec + iface := &vmiSpec.Domain.Devices.Interfaces[0] + if params.SSHAccess != "" { + iface.Ports = append(iface.Ports, kubevirtv1.Port{ + Name: "ssh", + Port: 22, + Protocol: "TCP", + }) + } + for name, port := range params.ForwardPorts { + iface.Ports = append(iface.Ports, kubevirtv1.Port{ + Name: name, + Port: int32(port), + Protocol: "TCP", + }) + } + + if params.CloudInit != "" { + vmiSpec.Volumes = append(vmiSpec.Volumes, kubevirtv1.Volume{ + Name: "cloudinit", + VolumeSource: kubevirtv1.VolumeSource{ + CloudInitNoCloud: &kubevirtv1.CloudInitNoCloudSource{ + UserData: params.CloudInit, + }, + }, + }) + vmiSpec.Domain.Devices.Disks = append(vmiSpec.Domain.Devices.Disks, kubevirtv1.Disk{ + Name: "cloudinit", + DiskDevice: kubevirtv1.DiskDevice{ + Disk: &kubevirtv1.DiskTarget{ + Bus: kubevirtv1.DiskBusVirtio, + }, + }, + }) + } + + return vm, nil +} + +// buildExporterPod creates a lightweight Pod that runs only the Jumpstarter +// exporter sidecar — no target-runtime container. The VM runs on the remote +// cluster; this Pod just hosts the exporter process. +func (p *Provisioner) buildExporterPod( + exporterSet *virtualtargetv1alpha1.ExporterSet, + vtc *virtualtargetv1alpha1.VirtualTargetClass, + images *virtualtargetv1alpha1.ImageOverrides, + exporter *jumpstarterdevv1alpha1.Exporter, +) *corev1.Pod { + restartAlways := corev1.ContainerRestartPolicyAlways + + var exporterSpec *virtualtargetv1alpha1.ImageSpec + if images != nil { + exporterSpec = images.Exporter + } + exporterImage, exporterPullPolicy := p.resolveImageSpec(exporterSpec, DefaultExporterImage) + + podMeta := metav1.ObjectMeta{ + Namespace: exporterSet.Namespace, + Labels: maps.Clone(exporterSet.Spec.Template.Metadata.Labels), + Annotations: maps.Clone(exporterSet.Spec.Template.Metadata.Annotations), + } + if exporter != nil { + podMeta.Name = exporter.Name + } else { + podMeta.GenerateName = fmt.Sprintf("%s-", exporterSet.Name) + } + + sizeLimit := resource.MustParse("10Mi") + + return &corev1.Pod{ + ObjectMeta: podMeta, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "copy-jumpstarter-exec", + Image: exporterImage, + ImagePullPolicy: exporterPullPolicy, + Command: []string{ + "cp", + jmpExecBinaryPath, + sharedMountPath + "/jumpstarter-exec", + }, + VolumeMounts: []corev1.VolumeMount{ + {Name: sharedVolumeName, MountPath: sharedMountPath}, + }, + }, + { + Name: "exporter", + Image: exporterImage, + ImagePullPolicy: exporterPullPolicy, + RestartPolicy: &restartAlways, + Command: []string{"jmp", "run", "--exporter-config", configMountPath + "/config.yaml"}, + VolumeMounts: []corev1.VolumeMount{ + {Name: sharedVolumeName, MountPath: sharedMountPath}, + {Name: credentialsVolumeName, MountPath: credentialsMountPath, ReadOnly: true}, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "keepalive", + Image: exporterImage, + ImagePullPolicy: exporterPullPolicy, + Command: []string{"sleep", "infinity"}, + }, + }, + Volumes: []corev1.Volume{ + { + Name: sharedVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: &sizeLimit, + }, + }, + }, + { + Name: credentialsVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: vtc.Spec.CredentialsSecretRef.Name, + }, + }, + }, + }, + }, + } +} + +// EnrichExporterExport injects KubeVirt-specific driver configuration +// so the Python driver knows how to reach the remote VM. +func (p *Provisioner) EnrichExporterExport( + drivers []virtualtargetv1alpha1.DriverConfig, + mergedParameters map[string]any, + exporter *jumpstarterdevv1alpha1.Exporter, +) ([]virtualtargetv1alpha1.DriverConfig, error) { + params, err := parseParams(mergedParameters) + if err != nil { + return nil, fmt.Errorf("parse kubevirt parameters: %w", err) + } + + // VM name matches exporter name (set in buildVirtualMachine). + vmName := exporter.Name + + result := make([]virtualtargetv1alpha1.DriverConfig, 0, len(drivers)+4) + + hasKubevirt := false + for _, d := range drivers { + if d.Type == kubevirtDriverType { + hasKubevirt = true + } + result = append(result, d) + } + + kubeconfigPath := credentialsMountPath + "/" + kubeconfigFilename + + if !hasKubevirt { + result = append(result, + virtualtargetv1alpha1.DriverConfig{ + Name: "power", + Type: kubevirtPowerType, + Config: mustJSON(map[string]any{ + "namespace": params.Namespace, + "vm_name": vmName, + "kubeconfig": kubeconfigPath, + }), + }, + virtualtargetv1alpha1.DriverConfig{ + Name: "console", + Type: kubevirtConsoleType, + Config: mustJSON(map[string]any{ + "namespace": params.Namespace, + "vm_name": vmName, + "kubeconfig": kubeconfigPath, + }), + }, + virtualtargetv1alpha1.DriverConfig{ + Name: "storage", + Type: kubevirtStorageType, + Config: mustJSON(map[string]any{ + "namespace": params.Namespace, + "vm_name": vmName, + "kubeconfig": kubeconfigPath, + }), + }, + ) + + // TODO: SSH and forwardPorts TcpNetwork drivers need a + // port-forward sidecar or proxy to reach the remote VMI. + // For now these are omitted — access the VM via virtctl + // or kubectl port-forward from outside the exporter pod. + } + + return result, nil +} + +// Cleanup deletes the VirtualMachine and any SSH Service on the remote cluster +// when an exporter instance is being removed. +func (p *Provisioner) Cleanup( + ctx context.Context, + exporterSet *virtualtargetv1alpha1.ExporterSet, + exporter *jumpstarterdevv1alpha1.Exporter, +) error { + vtc := &virtualtargetv1alpha1.VirtualTargetClass{} + vtcKey := client.ObjectKey{ + Name: exporterSet.Spec.VirtualTargetClassName, + Namespace: exporterSet.Namespace, + } + if err := p.Client.Get(ctx, vtcKey, vtc); err != nil { + return fmt.Errorf("read VirtualTargetClass %s: %w", vtcKey, err) + } + + remoteClient, err := p.getRemoteClient(ctx, vtc) + if err != nil { + return fmt.Errorf("get remote client for cleanup: %w", err) + } + + params, err := parseParams(mergeVTCAndESParams(vtc, exporterSet)) + if err != nil { + return fmt.Errorf("parse parameters for cleanup: %w", err) + } + + vm := &kubevirtv1.VirtualMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: exporter.Name, + Namespace: params.Namespace, + }, + } + if err := remoteClient.Delete(ctx, vm); client.IgnoreNotFound(err) != nil { + return fmt.Errorf("delete VirtualMachine %s/%s: %w", params.Namespace, exporter.Name, err) + } + + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: exporter.Name + "-ssh", + Namespace: params.Namespace, + }, + } + if err := remoteClient.Delete(ctx, svc); client.IgnoreNotFound(err) != nil { + return fmt.Errorf("delete SSH Service %s/%s: %w", params.Namespace, svc.Name, err) + } + + return nil +} + +// mergeVTCAndESParams does a shallow merge of VTC and ExporterSet parameters, +// mirroring what the reconciler does before calling Cleanup. +func mergeVTCAndESParams(vtc *virtualtargetv1alpha1.VirtualTargetClass, es *virtualtargetv1alpha1.ExporterSet) map[string]any { + merged := make(map[string]any) + if vtc.Spec.Parameters != nil && vtc.Spec.Parameters.Raw != nil { + _ = json.Unmarshal(vtc.Spec.Parameters.Raw, &merged) + } + if es.Spec.Parameters != nil && es.Spec.Parameters.Raw != nil { + var esParams map[string]any + if err := json.Unmarshal(es.Spec.Parameters.Raw, &esParams); err == nil { + maps.Copy(merged, esParams) + } + } + return merged +} + +func mustJSON(v any) *apiextensionsv1.JSON { + raw, _ := json.Marshal(v) + return &apiextensionsv1.JSON{Raw: raw} +} diff --git a/controller/internal/exporterset/provisioners/kubevirt/kubevirt_test.go b/controller/internal/exporterset/provisioners/kubevirt/kubevirt_test.go new file mode 100644 index 000000000..0cfc85580 --- /dev/null +++ b/controller/internal/exporterset/provisioners/kubevirt/kubevirt_test.go @@ -0,0 +1,277 @@ +/* +Copyright 2026 The Jumpstarter Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubevirt + +import ( + "encoding/json" + "maps" + "testing" + + jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter/controller/api/v1alpha1" + virtualtargetv1alpha1 "github.com/jumpstarter-dev/jumpstarter/controller/api/virtualtarget/v1alpha1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var testExporter = &jumpstarterdevv1alpha1.Exporter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vm", + Namespace: "test-ns", + }, +} + +func TestParseParams_defaults(t *testing.T) { + params := map[string]any{ + "namespace": "test-ns", + "image": "quay.io/test/image:latest", + } + + p, err := parseParams(params) + if err != nil { + t.Fatal(err) + } + + if p.Namespace != "test-ns" { + t.Errorf("namespace = %q, want test-ns", p.Namespace) + } + if p.CPU != defaultCPU { + t.Errorf("cpu = %d, want %d", p.CPU, defaultCPU) + } + if p.Memory != defaultMemory { + t.Errorf("memory = %q, want %q", p.Memory, defaultMemory) + } + if p.DiskSize != defaultDiskSize { + t.Errorf("diskSize = %q, want %q", p.DiskSize, defaultDiskSize) + } + if p.Image != "quay.io/test/image:latest" { + t.Errorf("image = %q", p.Image) + } +} + +func TestParseParams_requiresNamespace(t *testing.T) { + params := map[string]any{ + "image": "quay.io/test/image:latest", + } + + _, err := parseParams(params) + if err == nil { + t.Fatal("expected error for missing namespace") + } +} + +func TestParseParams_requiresImage(t *testing.T) { + params := map[string]any{ + "namespace": "test-ns", + } + + _, err := parseParams(params) + if err == nil { + t.Fatal("expected error for missing image") + } +} + +func TestParseParams_overridesResources(t *testing.T) { + params := map[string]any{ + "namespace": "test-ns", + "image": "quay.io/test/image:latest", + "resources": map[string]any{ + "cpu": 8, + "memory": "16Gi", + "storage": "100Gi", + }, + } + + p, err := parseParams(params) + if err != nil { + t.Fatal(err) + } + + if p.CPU != 8 { + t.Errorf("cpu = %d, want 8", p.CPU) + } + if p.Memory != "16Gi" { + t.Errorf("memory = %q, want 16Gi", p.Memory) + } + if p.DiskSize != "100Gi" { + t.Errorf("diskSize = %q, want 100Gi", p.DiskSize) + } +} + +func TestParseParams_validatesSSHAccess(t *testing.T) { + base := map[string]any{ + "namespace": "test-ns", + "image": "quay.io/test/image:latest", + } + + for _, valid := range []string{"", "NodePort", "LoadBalancer"} { + params := copyMap(base) + params["sshAccess"] = valid + if _, err := parseParams(params); err != nil { + t.Errorf("sshAccess=%q should be valid: %v", valid, err) + } + } + + params := copyMap(base) + params["sshAccess"] = "Invalid" + if _, err := parseParams(params); err == nil { + t.Error("sshAccess=Invalid should fail") + } +} + +func TestParseParams_rejectsInvalidMemory(t *testing.T) { + params := map[string]any{ + "namespace": "test-ns", + "image": "quay.io/test/image:latest", + "resources": map[string]any{ + "memory": "not-a-quantity", + }, + } + + _, err := parseParams(params) + if err == nil { + t.Fatal("expected error for invalid memory") + } +} + +func TestEnrichExporterExport_injectsAllDrivers(t *testing.T) { + prov := New("dev", nil) + + params := map[string]any{ + "namespace": "test-ns", + "image": "quay.io/test/image:latest", + "sshAccess": "NodePort", + } + + result, err := prov.EnrichExporterExport(nil, params, testExporter) + if err != nil { + t.Fatal(err) + } + + assertDriverExists(t, result, "power", kubevirtPowerType) + assertDriverExists(t, result, "console", kubevirtConsoleType) + assertDriverExists(t, result, "storage", kubevirtStorageType) +} + +func TestEnrichExporterExport_preservesExistingDrivers(t *testing.T) { + prov := New("dev", nil) + + existing := []virtualtargetv1alpha1.DriverConfig{ + {Name: "custom", Type: "my.custom.Driver"}, + } + + params := map[string]any{ + "namespace": "test-ns", + "image": "quay.io/test/image:latest", + } + + result, err := prov.EnrichExporterExport(existing, params, testExporter) + if err != nil { + t.Fatal(err) + } + + if findDriver(result, "custom") == nil { + t.Error("existing custom driver was lost") + } +} + +func TestEnrichExporterExport_doesNotDuplicateKubevirt(t *testing.T) { + prov := New("dev", nil) + + existing := []virtualtargetv1alpha1.DriverConfig{ + {Name: "kubevirt", Type: kubevirtDriverType}, + } + + params := map[string]any{ + "namespace": "test-ns", + "image": "quay.io/test/image:latest", + } + + result, err := prov.EnrichExporterExport(existing, params, testExporter) + if err != nil { + t.Fatal(err) + } + + if len(result) != 1 { + t.Errorf("expected 1 driver (user-provided kubevirt only), got %d", len(result)) + } +} + +func TestEnrichExporterExport_namespaceInConfig(t *testing.T) { + prov := New("dev", nil) + + params := map[string]any{ + "namespace": "remote-ns", + "image": "quay.io/test/image:latest", + } + + result, err := prov.EnrichExporterExport(nil, params, testExporter) + if err != nil { + t.Fatal(err) + } + + powerDriver := findDriver(result, "power") + if powerDriver == nil { + t.Fatal("power driver not found") + } + + config := unmarshalConfig(t, powerDriver.Config) + if got := config["namespace"]; got != "remote-ns" { + t.Errorf("power namespace = %v, want remote-ns", got) + } + if got := config["vm_name"]; got != "test-vm" { + t.Errorf("power vm_name = %v, want test-vm", got) + } +} + +// --- helpers --- + +func findDriver(drivers []virtualtargetv1alpha1.DriverConfig, name string) *virtualtargetv1alpha1.DriverConfig { + for i := range drivers { + if drivers[i].Name == name { + return &drivers[i] + } + } + return nil +} + +func assertDriverExists(t *testing.T, drivers []virtualtargetv1alpha1.DriverConfig, name, expectedType string) { + t.Helper() + d := findDriver(drivers, name) + if d == nil { + t.Errorf("driver %q not found", name) + return + } + if d.Type != expectedType { + t.Errorf("driver %q type = %q, want %q", name, d.Type, expectedType) + } +} + +func unmarshalConfig(t *testing.T, raw *apiextensionsv1.JSON) map[string]any { + t.Helper() + if raw == nil || raw.Raw == nil { + t.Fatal("config is nil") + } + var config map[string]any + if err := json.Unmarshal(raw.Raw, &config); err != nil { + t.Fatalf("unmarshal config: %v", err) + } + return config +} + +func copyMap(m map[string]any) map[string]any { + return maps.Clone(m) +} diff --git a/controller/internal/exporterset/provisioners/kubevirt/params.go b/controller/internal/exporterset/provisioners/kubevirt/params.go new file mode 100644 index 000000000..e9aa29948 --- /dev/null +++ b/controller/internal/exporterset/provisioners/kubevirt/params.go @@ -0,0 +1,232 @@ +/* +Copyright 2026 The Jumpstarter Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubevirt + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/resource" +) + +const ( + defaultCPU = 2 + defaultMemory = "4Gi" + defaultDiskSize = "20Gi" + + StorageTypeContainerDisk = "containerDisk" + StorageTypeDataVolume = "dataVolume" +) + +type HTTPSource struct { + URL string +} + +type RegistrySource struct { + Image string +} + +type PVCSource struct { + Name string + Namespace string +} + +type StorageSource struct { + HTTP *HTTPSource + Registry *RegistrySource + PVC *PVCSource +} + +type StorageConfig struct { + Type string + Size string + StorageClassName string + AccessModes []string + Source StorageSource +} + +// KubevirtParams holds parsed and validated parameters from VTC/ExporterSet +// mergedParameters for the KubeVirt provisioner. +type KubevirtParams struct { + Namespace string + CPU int + Memory string + DiskSize string + Image string + SSHAccess string + ForwardPorts map[string]int + CloudInit string + Storage *StorageConfig +} + +// parseParams extracts and validates KubevirtParams from merged parameters. +func parseParams(params map[string]any) (*KubevirtParams, error) { + p := &KubevirtParams{ + CPU: defaultCPU, + Memory: defaultMemory, + DiskSize: defaultDiskSize, + } + + if ns, ok := params["namespace"].(string); ok && ns != "" { + p.Namespace = ns + } + if p.Namespace == "" { + return nil, fmt.Errorf("required parameter \"namespace\" not set") + } + + if img, ok := params["image"].(string); ok && img != "" { + p.Image = img + } + + if resources, ok := params["resources"].(map[string]any); ok { + if cpu, ok := toInt(resources["cpu"]); ok && cpu > 0 { + p.CPU = cpu + } + if mem, ok := resources["memory"].(string); ok && mem != "" { + p.Memory = mem + } + if disk, ok := resources["storage"].(string); ok && disk != "" { + p.DiskSize = disk + } + } + + if ssh, ok := params["sshAccess"].(string); ok { + switch ssh { + case "", "NodePort", "LoadBalancer": + p.SSHAccess = ssh + default: + return nil, fmt.Errorf("invalid sshAccess %q; must be \"NodePort\", \"LoadBalancer\", or empty", ssh) + } + } + + if fwd, ok := params["forwardPorts"].(map[string]any); ok { + p.ForwardPorts = make(map[string]int, len(fwd)) + for name, v := range fwd { + if port, ok := toInt(v); ok && port > 0 && port <= 65535 { + p.ForwardPorts[name] = port + } + } + } + + if ci, ok := params["cloudInit"].(string); ok { + p.CloudInit = ci + } + + if stor, ok := params["storage"].(map[string]any); ok { + sc, err := parseStorageConfig(stor) + if err != nil { + return nil, err + } + p.Storage = sc + if p.Storage.Size != "" { + p.DiskSize = p.Storage.Size + } + } + + // Require either image (for containerDisk) or storage config (for dataVolume). + if p.Image == "" && (p.Storage == nil || p.Storage.Type == StorageTypeContainerDisk) { + return nil, fmt.Errorf("required parameter \"image\" not set (needed for containerDisk storage)") + } + + if _, err := resource.ParseQuantity(p.Memory); err != nil { + return nil, fmt.Errorf("invalid memory %q: %w", p.Memory, err) + } + if _, err := resource.ParseQuantity(p.DiskSize); err != nil { + return nil, fmt.Errorf("invalid diskSize %q: %w", p.DiskSize, err) + } + + return p, nil +} + +func parseStorageConfig(stor map[string]any) (*StorageConfig, error) { + sc := &StorageConfig{ + Type: StorageTypeContainerDisk, + Size: defaultDiskSize, + AccessModes: []string{"ReadWriteOnce"}, + } + + if t, ok := stor["type"].(string); ok && t != "" { + switch t { + case StorageTypeContainerDisk, StorageTypeDataVolume: + sc.Type = t + default: + return nil, fmt.Errorf("invalid storage type %q; must be %q or %q", + t, StorageTypeContainerDisk, StorageTypeDataVolume) + } + } + + if size, ok := stor["size"].(string); ok && size != "" { + sc.Size = size + } + + if scn, ok := stor["storageClassName"].(string); ok && scn != "" { + sc.StorageClassName = scn + } + + if am, ok := stor["accessModes"].([]any); ok && len(am) > 0 { + sc.AccessModes = make([]string, 0, len(am)) + for _, v := range am { + if s, ok := v.(string); ok { + sc.AccessModes = append(sc.AccessModes, s) + } + } + } + + if src, ok := stor["source"].(map[string]any); ok { + if httpSrc, ok := src["http"].(map[string]any); ok { + url, _ := httpSrc["url"].(string) + if url == "" { + return nil, fmt.Errorf("storage source http requires \"url\"") + } + sc.Source.HTTP = &HTTPSource{URL: url} + } + if regSrc, ok := src["registry"].(map[string]any); ok { + img, _ := regSrc["image"].(string) + if img == "" { + return nil, fmt.Errorf("storage source registry requires \"image\"") + } + sc.Source.Registry = &RegistrySource{Image: img} + } + if pvcSrc, ok := src["pvc"].(map[string]any); ok { + name, _ := pvcSrc["name"].(string) + ns, _ := pvcSrc["namespace"].(string) + if name == "" { + return nil, fmt.Errorf("storage source pvc requires \"name\"") + } + sc.Source.PVC = &PVCSource{Name: name, Namespace: ns} + } + } + + if sc.Type == StorageTypeDataVolume && sc.Source == (StorageSource{}) { + return nil, fmt.Errorf("storage type %q requires a source (http, registry, or pvc)", StorageTypeDataVolume) + } + + return sc, nil +} + +// toInt converts a numeric interface{} (float64 from JSON, int, etc.) to int. +func toInt(v any) (int, bool) { + switch n := v.(type) { + case int: + return n, true + case int64: + return int(n), true + case float64: + return int(n), true + default: + return 0, false + } +} diff --git a/controller/internal/exporterset/provisioners/kubevirt/remote.go b/controller/internal/exporterset/provisioners/kubevirt/remote.go new file mode 100644 index 000000000..3c2f1d442 --- /dev/null +++ b/controller/internal/exporterset/provisioners/kubevirt/remote.go @@ -0,0 +1,109 @@ +/* +Copyright 2026 The Jumpstarter Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubevirt + +import ( + "context" + "fmt" + "sync" + + virtualtargetv1alpha1 "github.com/jumpstarter-dev/jumpstarter/controller/api/virtualtarget/v1alpha1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/clientcmd" + kubevirtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// remoteClientCache caches controller-runtime clients keyed by VTC name. +type remoteClientCache struct { + mu sync.RWMutex + clients map[string]client.Client +} + +func newRemoteClientCache() *remoteClientCache { + return &remoteClientCache{ + clients: make(map[string]client.Client), + } +} + +func (c *remoteClientCache) get(vtcName string) (client.Client, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + cl, ok := c.clients[vtcName] + return cl, ok +} + +func (c *remoteClientCache) set(vtcName string, cl client.Client) { + c.mu.Lock() + defer c.mu.Unlock() + c.clients[vtcName] = cl +} + +// remoteScheme registers KubeVirt types for the remote cluster client. +func remoteScheme() *runtime.Scheme { + s := runtime.NewScheme() + _ = corev1.AddToScheme(s) + _ = kubevirtv1.AddToScheme(s) + return s +} + +// getRemoteClient returns a controller-runtime client for the remote KubeVirt +// cluster. It reads the kubeconfig from the Secret referenced by the VTC's +// CredentialsSecretRef and caches the client per VTC name. +func (p *Provisioner) getRemoteClient( + ctx context.Context, + vtc *virtualtargetv1alpha1.VirtualTargetClass, +) (client.Client, error) { + if vtc.Spec.CredentialsSecretRef == nil { + return nil, fmt.Errorf("VirtualTargetClass %q has no credentialsSecretRef", vtc.Name) + } + + if cl, ok := p.remoteClients.get(vtc.Name); ok { + return cl, nil + } + + secret := &corev1.Secret{} + key := types.NamespacedName{ + Name: vtc.Spec.CredentialsSecretRef.Name, + Namespace: vtc.Namespace, + } + if err := p.Client.Get(ctx, key, secret); err != nil { + return nil, fmt.Errorf("read credentials Secret %s/%s: %w", key.Namespace, key.Name, err) + } + + kubeconfig, ok := secret.Data["kubeconfig"] + if !ok { + return nil, fmt.Errorf("credentials Secret %s/%s missing \"kubeconfig\" key", key.Namespace, key.Name) + } + + restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig) + if err != nil { + return nil, fmt.Errorf("parse kubeconfig from Secret %s/%s: %w", key.Namespace, key.Name, err) + } + + cl, err := client.New(restConfig, client.Options{ + Scheme: remoteScheme(), + }) + if err != nil { + return nil, fmt.Errorf("create remote client for VTC %q: %w", vtc.Name, err) + } + + p.remoteClients.set(vtc.Name, cl) + return cl, nil +} diff --git a/controller/internal/exporterset/provisioners/qemu/enrich_test.go b/controller/internal/exporterset/provisioners/qemu/enrich_test.go index 019207d9b..d3ba88c6c 100644 --- a/controller/internal/exporterset/provisioners/qemu/enrich_test.go +++ b/controller/internal/exporterset/provisioners/qemu/enrich_test.go @@ -37,7 +37,7 @@ func TestEnrichExporterExport_injectsLauncherSocket(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, nil) + result, err := New("dev").EnrichExporterExport(drivers, nil, nil) if err != nil { t.Fatal(err) } @@ -64,7 +64,7 @@ func TestEnrichExporterExport_injectsDefaultPartitionsX86(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, nil) + result, err := New("dev").EnrichExporterExport(drivers, nil, nil) if err != nil { t.Fatal(err) } @@ -93,7 +93,7 @@ func TestEnrichExporterExport_injectsDefaultPartitionsAarch64(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, nil) + result, err := New("dev").EnrichExporterExport(drivers, nil, nil) if err != nil { t.Fatal(err) } @@ -127,7 +127,7 @@ func TestEnrichExporterExport_respectsUserDefaultPartitions(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, nil) + result, err := New("dev").EnrichExporterExport(drivers, nil, nil) if err != nil { t.Fatal(err) } @@ -153,7 +153,7 @@ func TestEnrichExporterExport_injectsHostfwdSSH(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, nil) + result, err := New("dev").EnrichExporterExport(drivers, nil, nil) if err != nil { t.Fatal(err) } @@ -189,7 +189,7 @@ func TestEnrichExporterExport_autoInjectsTCPDriver(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, nil) + result, err := New("dev").EnrichExporterExport(drivers, nil, nil) if err != nil { t.Fatal(err) } @@ -219,7 +219,7 @@ func TestEnrichExporterExport_doesNotDuplicateExistingTCP(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, nil) + result, err := New("dev").EnrichExporterExport(drivers, nil, nil) if err != nil { t.Fatal(err) } @@ -253,7 +253,7 @@ func TestEnrichExporterExport_defaultsFromMergedParameters(t *testing.T) { }, } - result, err := New("dev").EnrichExporterExport(drivers, params) + result, err := New("dev").EnrichExporterExport(drivers, params, nil) if err != nil { t.Fatal(err) } diff --git a/controller/internal/exporterset/provisioners/qemu/qemu.go b/controller/internal/exporterset/provisioners/qemu/qemu.go index 5b540f41d..028ffce81 100644 --- a/controller/internal/exporterset/provisioners/qemu/qemu.go +++ b/controller/internal/exporterset/provisioners/qemu/qemu.go @@ -281,6 +281,7 @@ func (p *Provisioner) RenderPod( func (p *Provisioner) EnrichExporterExport( drivers []virtualtargetv1alpha1.DriverConfig, mergedParameters map[string]interface{}, + _ *jumpstarterdevv1alpha1.Exporter, ) ([]virtualtargetv1alpha1.DriverConfig, error) { result := make([]virtualtargetv1alpha1.DriverConfig, 0, len(drivers)+1) hasTCP := false diff --git a/python/packages/jumpstarter-driver-kubevirt/README.md b/python/packages/jumpstarter-driver-kubevirt/README.md new file mode 100644 index 000000000..1e7ad5c8c --- /dev/null +++ b/python/packages/jumpstarter-driver-kubevirt/README.md @@ -0,0 +1,10 @@ +# jumpstarter-driver-kubevirt + +Jumpstarter driver for managing KubeVirt virtual machines on remote clusters. + +## Drivers + +- **KubevirtPower** — Start/stop VirtualMachines via KubeVirt API +- **KubevirtConsole** — Serial console via WebSocket subresource +- **KubevirtStorage** — Flash images via CDI DataVolumes +- **Kubevirt** — Composite root driver diff --git a/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/__init__.py b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/client.py b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/client.py new file mode 100644 index 000000000..9c51b630c --- /dev/null +++ b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/client.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import os +import sys +import termios +import tty + +import click +from anyio import EndOfStream, create_task_group, wait_readable +from anyio._backends._asyncio import BrokenResourceError +from jumpstarter_driver_composite.client import CompositeClient +from jumpstarter_driver_network.client import NetworkClient +from jumpstarter_driver_power.client import PowerClient + + +class KubevirtPowerClient(PowerClient): + pass + + +class ConsoleExit(Exception): + pass + + +class KubevirtConsoleClient(NetworkClient): + + def cli(self): + base = super().cli() + + @base.command() + def pipe(): + """Interactive serial console (Ctrl+B x3 to exit)""" + click.echo("Connected to KubeVirt serial console (Ctrl+B x3 to exit)", err=True) + old_settings = None + if sys.stdin.isatty(): + old_settings = termios.tcgetattr(sys.stdin.fileno()) + tty.setraw(sys.stdin.fileno()) + try: + self.portal.call(self._pipe_console) + except (KeyboardInterrupt, ConsoleExit): + pass + finally: + if old_settings: + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_settings) + click.echo("\nDisconnected.", err=True) + + return base + + async def _pipe_console(self): + async with self.stream_async(method="connect") as stream: + try: + async with create_task_group() as tg: + tg.start_soon(self._stdin_to_stream, stream) + await self._stream_to_stdout(stream) + tg.cancel_scope.cancel() + except* ConsoleExit: + pass + + @staticmethod + async def _stream_to_stdout(stream): + try: + while True: + data = await stream.receive() + sys.stdout.buffer.write(data) + sys.stdout.buffer.flush() + except (EndOfStream, BrokenResourceError): + pass + + @staticmethod + async def _stdin_to_stream(stream): + fd = sys.stdin.fileno() + os.set_blocking(fd, False) + ctrl_b_count = 0 + try: + while True: + await wait_readable(fd) + data = os.read(fd, 1024) + if not data: + break + for byte in data: + if byte == 0x02: # Ctrl+B + ctrl_b_count += 1 + if ctrl_b_count >= 3: + raise ConsoleExit + else: + ctrl_b_count = 0 + await stream.send(data) + except (EndOfStream, BrokenResourceError, OSError): + pass + except ConsoleExit: + raise + finally: + os.set_blocking(fd, True) + + +class KubevirtStorageClient(CompositeClient): + def flash(self, image_url: str) -> None: + self.call("flash", image_url) + + +class KubevirtClient(CompositeClient): + pass diff --git a/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py new file mode 100644 index 000000000..96b6a9874 --- /dev/null +++ b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/driver.py @@ -0,0 +1,294 @@ +from __future__ import annotations + +import asyncio +import logging +import ssl +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +import websockets +from jumpstarter_driver_composite.driver import CompositeInterface +from jumpstarter_driver_network.driver import NetworkInterface +from jumpstarter_driver_network.streams.websocket import WebsocketClientStream +from jumpstarter_driver_power.driver import PowerInterface, PowerReading +from websockets.exceptions import InvalidStatus + +from jumpstarter.driver import Driver, export, exportstream + +if TYPE_CHECKING: + from lightkube import AsyncClient + +try: + from lightkube import AsyncClient + from lightkube.config.kubeconfig import KubeConfig + from lightkube.core.exceptions import ApiError + from lightkube.generic_resource import create_namespaced_resource + from lightkube.models.meta_v1 import ObjectMeta + from lightkube.types import PatchType + + _HAS_LIGHTKUBE = True +except ImportError: + _HAS_LIGHTKUBE = False + +logger = logging.getLogger(__name__) + +VirtualMachine = create_namespaced_resource( + group="kubevirt.io", + version="v1", + kind="VirtualMachine", + plural="virtualmachines", +) + +VirtualMachineInstance = create_namespaced_resource( + group="kubevirt.io", + version="v1", + kind="VirtualMachineInstance", + plural="virtualmachineinstances", +) + +DataVolume = create_namespaced_resource( + group="cdi.kubevirt.io", + version="v1beta1", + kind="DataVolume", + plural="datavolumes", +) + + +def _build_client(kubeconfig_path: str | None) -> AsyncClient: + if kubeconfig_path: + config = KubeConfig.from_file(kubeconfig_path) + return AsyncClient(config=config) + return AsyncClient() + + +@dataclass(kw_only=True) +class KubevirtPower(PowerInterface, Driver): + namespace: str = "" + kubeconfig: str | None = None + vm_name: str = "" + + _client: AsyncClient | None = field(init=False, default=None, repr=False) + + @classmethod + def client(cls) -> str: + return "jumpstarter_driver_kubevirt.client.KubevirtPowerClient" + + def _get_client(self) -> AsyncClient: + if self._client is None: + self._client = _build_client(self.kubeconfig) + return self._client + + def _resolve_vm_name(self) -> str: + if self.vm_name: + return self.vm_name + raise ValueError("vm_name not set in driver config") + + @export + async def on(self) -> None: + client = self._get_client() + name = self._resolve_vm_name() + + patch = {"spec": {"runStrategy": "Always"}} + await client.patch( + VirtualMachine, + name=name, + namespace=self.namespace, + obj=patch, + patch_type=PatchType.MERGE, + ) + logger.info("started VM %s/%s", self.namespace, name) + + @export + async def off(self, destroy: bool = False) -> None: + client = self._get_client() + name = self._resolve_vm_name() + + if destroy: + await client.delete( + VirtualMachine, + name=name, + namespace=self.namespace, + ) + logger.info("destroyed VM %s/%s", self.namespace, name) + else: + patch = {"spec": {"runStrategy": "Halted"}} + await client.patch( + VirtualMachine, + name=name, + namespace=self.namespace, + obj=patch, + patch_type=PatchType.MERGE, + ) + logger.info("stopped VM %s/%s", self.namespace, name) + + @export + async def read(self) -> AsyncGenerator[PowerReading, None]: + client = self._get_client() + name = self._resolve_vm_name() + + try: + vmi = await client.get( + VirtualMachineInstance, + name=name, + namespace=self.namespace, + ) + phase = vmi.get("status", {}).get("phase", "Unknown") + if phase == "Running": + yield PowerReading(voltage=5.0, current=1.0) + else: + yield PowerReading(voltage=0.0, current=0.0) + except Exception: + yield PowerReading(voltage=0.0, current=0.0) + + +def _build_console_ws_url( + kubeconfig_path: str | None, namespace: str, vm_name: str +) -> tuple[str, dict, ssl.SSLContext | None]: + """Build WebSocket URL, headers, and SSL context for KubeVirt console. + + Returns (ws_url, extra_headers, ssl_context). + """ + if not kubeconfig_path: + raise ValueError("kubeconfig required for console WebSocket connection") + + config = KubeConfig.from_file(kubeconfig_path) + single = config.get() + if single is None: + raise ValueError("no current context in kubeconfig") + + server = single.cluster.server.rstrip("/") + ws_scheme = "wss" if server.startswith("https") else "ws" + host = server.replace("https://", "").replace("http://", "") + ws_url = ( + f"{ws_scheme}://{host}/apis/subresources.kubevirt.io/v1/" + f"namespaces/{namespace}/virtualmachineinstances/{vm_name}/console" + ) + + headers = {} + if single.user and single.user.token: + headers["Authorization"] = f"Bearer {single.user.token}" + + ssl_ctx = None + if ws_scheme == "wss": + ssl_ctx = ssl.create_default_context() + if single.cluster.insecure: + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + + return ws_url, headers, ssl_ctx + + +@dataclass(kw_only=True) +class KubevirtConsole(NetworkInterface, Driver): + namespace: str = "" + kubeconfig: str | None = None + vm_name: str = "" + + @classmethod + def client(cls) -> str: + return "jumpstarter_driver_kubevirt.client.KubevirtConsoleClient" + + def _resolve_vm_name(self) -> str: + if self.vm_name: + return self.vm_name + raise ValueError("vm_name not set in driver config") + + @exportstream + @asynccontextmanager + async def connect(self): + name = self._resolve_vm_name() + ws_url, headers, ssl_ctx = _build_console_ws_url( + self.kubeconfig, self.namespace, name, + ) + + for attempt in range(30): + try: + self.logger.info("Connecting console WebSocket to %s", ws_url) + async with websockets.connect( + ws_url, + additional_headers=headers, + ssl=ssl_ctx, + subprotocols=["plain.kubevirt.io"], + ) as websocket: + async with WebsocketClientStream(conn=websocket) as stream: + yield stream + self.logger.info("Console WebSocket disconnected") + return + except InvalidStatus as e: + if e.response.status_code == 400 and b"not running" in e.response.body: + self.logger.info("VMI not running yet, retrying in 2s (attempt %d/30)", attempt + 1) + await asyncio.sleep(2) + else: + raise + + raise TimeoutError(f"VMI {name} did not become ready for console within 60s") + + +@dataclass(kw_only=True) +class KubevirtStorage(Driver): + namespace: str = "" + kubeconfig: str | None = None + vm_name: str = "" + + _client: AsyncClient | None = field(init=False, default=None, repr=False) + + @classmethod + def client(cls) -> str: + return "jumpstarter_driver_kubevirt.client.KubevirtStorageClient" + + def _get_client(self) -> AsyncClient: + if self._client is None: + self._client = _build_client(self.kubeconfig) + return self._client + + def _resolve_vm_name(self) -> str: + if self.vm_name: + return self.vm_name + raise ValueError("vm_name not set in driver config") + + @export + async def flash(self, image_url: str) -> None: + client = self._get_client() + name = self._resolve_vm_name() + dv_name = f"{name}-root" + + dv = DataVolume( + metadata=ObjectMeta( + name=dv_name, + namespace=self.namespace, + ), + spec={ + "source": { + "http": { + "url": image_url, + }, + }, + "pvc": { + "accessModes": ["ReadWriteOnce"], + "resources": { + "requests": { + "storage": "20Gi", + }, + }, + }, + }, + ) + + try: + await client.replace(dv) + except ApiError as e: + if e.status.code == 404: + await client.create(dv) + else: + raise + + logger.info("created/updated DataVolume %s/%s from %s", self.namespace, dv_name, image_url) + + +@dataclass(kw_only=True) +class Kubevirt(CompositeInterface, Driver): + @classmethod + def client(cls) -> str: + return "jumpstarter_driver_kubevirt.client.KubevirtClient" diff --git a/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/py.typed b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_client.py b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_client.py new file mode 100644 index 000000000..2354470b2 --- /dev/null +++ b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_client.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from anyio import EndOfStream + +from jumpstarter_driver_kubevirt.client import ( + ConsoleExit, + KubevirtClient, + KubevirtConsoleClient, + KubevirtPowerClient, + KubevirtStorageClient, +) + + +class TestKubevirtPowerClient: + def test_is_power_client_subclass(self): + from jumpstarter_driver_power.client import PowerClient + + assert issubclass(KubevirtPowerClient, PowerClient) + + +class TestConsoleExit: + def test_is_exception(self): + assert issubclass(ConsoleExit, Exception) + + +class TestKubevirtConsoleClient: + def test_is_network_client_subclass(self): + from jumpstarter_driver_network.client import NetworkClient + + assert issubclass(KubevirtConsoleClient, NetworkClient) + + def test_has_cli_method(self): + assert hasattr(KubevirtConsoleClient, "cli") + assert callable(KubevirtConsoleClient.cli) + + @pytest.mark.asyncio + async def test_stream_to_stdout_reads_until_eos(self): + stream = AsyncMock() + stream.receive = AsyncMock(side_effect=[b"hello", b"world", EndOfStream()]) + + with patch("sys.stdout") as mock_stdout: + mock_stdout.buffer.write = MagicMock() + mock_stdout.buffer.flush = MagicMock() + + await KubevirtConsoleClient._stream_to_stdout(stream) + + assert mock_stdout.buffer.write.call_count == 2 + mock_stdout.buffer.write.assert_any_call(b"hello") + mock_stdout.buffer.write.assert_any_call(b"world") + + @pytest.mark.asyncio + async def test_stdin_to_stream_ctrl_b_exit(self): + stream = AsyncMock() + + with ( + patch("os.set_blocking") as mock_set_blocking, + patch("jumpstarter_driver_kubevirt.client.wait_readable", new_callable=AsyncMock), + patch("os.read", return_value=bytes([0x02, 0x02, 0x02])), + ): + with pytest.raises(ConsoleExit): + await KubevirtConsoleClient._stdin_to_stream(stream) + + mock_set_blocking.assert_called() + + @pytest.mark.asyncio + async def test_stdin_to_stream_normal_data(self): + stream = AsyncMock() + counter = [0] + + async def mock_wait_readable(_fd): + counter[0] += 1 + if counter[0] > 2: + raise EndOfStream() + + with ( + patch("os.set_blocking"), + patch("jumpstarter_driver_kubevirt.client.wait_readable", side_effect=mock_wait_readable), + patch("os.read", return_value=b"hello"), + patch("sys.stdin") as mock_stdin, + ): + mock_stdin.fileno.return_value = 0 + await KubevirtConsoleClient._stdin_to_stream(stream) + + assert stream.send.call_count >= 1 + + @pytest.mark.asyncio + async def test_stdin_to_stream_empty_data_exits(self): + stream = AsyncMock() + + with ( + patch("os.set_blocking"), + patch("jumpstarter_driver_kubevirt.client.wait_readable", new_callable=AsyncMock), + patch("os.read", return_value=b""), + patch("sys.stdin") as mock_stdin, + ): + mock_stdin.fileno.return_value = 0 + await KubevirtConsoleClient._stdin_to_stream(stream) + + stream.send.assert_not_called() + + def test_pipe_command_setup(self): + client = object.__new__(KubevirtConsoleClient) + client.portal = MagicMock() + client.stack = MagicMock() + + with patch("jumpstarter_driver_kubevirt.client.NetworkClient.cli") as mock_super: + import click + + group = click.Group(name="test") + mock_super.return_value = group + result = client.cli() + assert result is group + cmd_names = list(result.commands.keys()) + assert "pipe" in cmd_names + + +class TestKubevirtStorageClient: + def test_is_composite_client_subclass(self): + from jumpstarter_driver_composite.client import CompositeClient + + assert issubclass(KubevirtStorageClient, CompositeClient) + + def test_has_flash_method(self): + assert hasattr(KubevirtStorageClient, "flash") + assert callable(KubevirtStorageClient.flash) + + def test_flash_calls_call(self): + client = object.__new__(KubevirtStorageClient) + client.call = MagicMock() + client.stack = MagicMock() + + client.flash("http://example.com/image.qcow2") + client.call.assert_called_once_with("flash", "http://example.com/image.qcow2") + + +class TestKubevirtClient: + def test_is_composite_client_subclass(self): + from jumpstarter_driver_composite.client import CompositeClient + + assert issubclass(KubevirtClient, CompositeClient) diff --git a/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_driver.py b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_driver.py new file mode 100644 index 000000000..75c734a7a --- /dev/null +++ b/python/packages/jumpstarter-driver-kubevirt/jumpstarter_driver_kubevirt/test_driver.py @@ -0,0 +1,430 @@ +from __future__ import annotations + +import ssl +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + + +class TestBuildClient: + def test_with_kubeconfig(self): + mock_config = MagicMock() + with ( + patch("jumpstarter_driver_kubevirt.driver.KubeConfig") as mock_kc, + patch("jumpstarter_driver_kubevirt.driver.AsyncClient") as mock_ac, + ): + mock_kc.from_file.return_value = mock_config + from jumpstarter_driver_kubevirt.driver import _build_client + + _build_client("/path/to/kubeconfig") + mock_kc.from_file.assert_called_once_with("/path/to/kubeconfig") + mock_ac.assert_called_once_with(config=mock_config) + + def test_without_kubeconfig(self): + with patch("jumpstarter_driver_kubevirt.driver.AsyncClient") as mock_ac: + from jumpstarter_driver_kubevirt.driver import _build_client + + _build_client(None) + mock_ac.assert_called_once_with() + + +class TestKubevirtPower: + def _make_power(self, **kwargs): + from jumpstarter_driver_kubevirt.driver import KubevirtPower + + defaults = {"namespace": "test-ns", "vm_name": "test-vm"} + defaults.update(kwargs) + return KubevirtPower(**defaults) + + def test_resolve_vm_name(self): + power = self._make_power(vm_name="my-vm") + assert power._resolve_vm_name() == "my-vm" + + def test_resolve_vm_name_empty_raises(self): + power = self._make_power(vm_name="") + with pytest.raises(ValueError, match="vm_name not set"): + power._resolve_vm_name() + + def test_get_client_lazy_init(self): + power = self._make_power() + with patch("jumpstarter_driver_kubevirt.driver._build_client") as mock_build: + mock_client = MagicMock() + mock_build.return_value = mock_client + + result = power._get_client() + assert result is mock_client + mock_build.assert_called_once_with(None) + + result2 = power._get_client() + assert result2 is mock_client + assert mock_build.call_count == 1 + + def test_client_class_method(self): + from jumpstarter_driver_kubevirt.driver import KubevirtPower + + assert KubevirtPower.client() == "jumpstarter_driver_kubevirt.client.KubevirtPowerClient" + + @pytest.mark.asyncio + async def test_on(self): + power = self._make_power() + mock_client = AsyncMock() + power._client = mock_client + + await power.on() + + mock_client.patch.assert_called_once() + call_kwargs = mock_client.patch.call_args + assert call_kwargs.kwargs["name"] == "test-vm" + assert call_kwargs.kwargs["namespace"] == "test-ns" + + @pytest.mark.asyncio + async def test_off_halt(self): + power = self._make_power() + mock_client = AsyncMock() + power._client = mock_client + + await power.off(destroy=False) + + mock_client.patch.assert_called_once() + call_kwargs = mock_client.patch.call_args + assert call_kwargs.kwargs["name"] == "test-vm" + + @pytest.mark.asyncio + async def test_off_destroy(self): + power = self._make_power() + mock_client = AsyncMock() + power._client = mock_client + + await power.off(destroy=True) + + mock_client.delete.assert_called_once() + mock_client.patch.assert_not_called() + + @pytest.mark.asyncio + async def test_read_running(self): + power = self._make_power() + mock_client = AsyncMock() + mock_vmi = MagicMock() + + def vmi_get(key, default=None): + if key == "status": + status = MagicMock() + status.get.return_value = "Running" + return status + return default + + mock_vmi.get = vmi_get + mock_client.get = AsyncMock(return_value=mock_vmi) + power._client = mock_client + + readings = [] + async for reading in power.read(): + readings.append(reading) + + assert len(readings) == 1 + assert readings[0].voltage == 5.0 + assert readings[0].current == 1.0 + + @pytest.mark.asyncio + async def test_read_stopped(self): + power = self._make_power() + mock_client = AsyncMock() + mock_vmi = MagicMock() + + def vmi_get(key, default=None): + if key == "status": + status = MagicMock() + status.get.return_value = "Stopped" + return status + return default + + mock_vmi.get = vmi_get + mock_client.get = AsyncMock(return_value=mock_vmi) + power._client = mock_client + + readings = [] + async for reading in power.read(): + readings.append(reading) + + assert len(readings) == 1 + assert readings[0].voltage == 0.0 + assert readings[0].current == 0.0 + + @pytest.mark.asyncio + async def test_read_exception(self): + power = self._make_power() + mock_client = AsyncMock() + mock_client.get = AsyncMock(side_effect=Exception("connection failed")) + power._client = mock_client + + readings = [] + async for reading in power.read(): + readings.append(reading) + + assert len(readings) == 1 + assert readings[0].voltage == 0.0 + + +class TestBuildConsoleWsUrl: + def test_no_kubeconfig_raises(self): + from jumpstarter_driver_kubevirt.driver import _build_console_ws_url + + with pytest.raises(ValueError, match="kubeconfig required"): + _build_console_ws_url(None, "ns", "vm") + + def test_https_server(self): + mock_single = MagicMock() + mock_single.cluster.server = "https://api.example.com:6443" + mock_single.cluster.insecure = False + mock_single.user.token = "my-token" + + mock_config = MagicMock() + mock_config.get.return_value = mock_single + + with patch("jumpstarter_driver_kubevirt.driver.KubeConfig") as mock_kc: + mock_kc.from_file.return_value = mock_config + from jumpstarter_driver_kubevirt.driver import _build_console_ws_url + + ws_url, headers, ssl_ctx = _build_console_ws_url("/path/kc", "test-ns", "my-vm") + + assert ws_url.startswith("wss://") + assert "test-ns" in ws_url + assert "my-vm" in ws_url + assert headers["Authorization"] == "Bearer my-token" + assert ssl_ctx is not None + assert ssl_ctx.check_hostname is True + + def test_https_insecure(self): + mock_single = MagicMock() + mock_single.cluster.server = "https://api.example.com:6443" + mock_single.cluster.insecure = True + mock_single.user.token = None + + mock_config = MagicMock() + mock_config.get.return_value = mock_single + + with patch("jumpstarter_driver_kubevirt.driver.KubeConfig") as mock_kc: + mock_kc.from_file.return_value = mock_config + from jumpstarter_driver_kubevirt.driver import _build_console_ws_url + + ws_url, headers, ssl_ctx = _build_console_ws_url("/path/kc", "ns", "vm") + + assert ssl_ctx is not None + assert ssl_ctx.check_hostname is False + assert ssl_ctx.verify_mode == ssl.CERT_NONE + assert "Authorization" not in headers + + def test_http_server(self): + mock_single = MagicMock() + mock_single.cluster.server = "http://localhost:8080" + mock_single.cluster.insecure = False + mock_single.user = None + + mock_config = MagicMock() + mock_config.get.return_value = mock_single + + with patch("jumpstarter_driver_kubevirt.driver.KubeConfig") as mock_kc: + mock_kc.from_file.return_value = mock_config + from jumpstarter_driver_kubevirt.driver import _build_console_ws_url + + ws_url, headers, ssl_ctx = _build_console_ws_url("/path/kc", "ns", "vm") + + assert ws_url.startswith("ws://") + assert ssl_ctx is None + + def test_no_current_context_raises(self): + mock_config = MagicMock() + mock_config.get.return_value = None + + with patch("jumpstarter_driver_kubevirt.driver.KubeConfig") as mock_kc: + mock_kc.from_file.return_value = mock_config + from jumpstarter_driver_kubevirt.driver import _build_console_ws_url + + with pytest.raises(ValueError, match="no current context"): + _build_console_ws_url("/path/kc", "ns", "vm") + + +class TestKubevirtConsole: + def test_client_class_method(self): + from jumpstarter_driver_kubevirt.driver import KubevirtConsole + + assert KubevirtConsole.client() == "jumpstarter_driver_kubevirt.client.KubevirtConsoleClient" + + def test_resolve_vm_name(self): + from jumpstarter_driver_kubevirt.driver import KubevirtConsole + + console = KubevirtConsole(namespace="ns", vm_name="my-vm") + assert console._resolve_vm_name() == "my-vm" + + def test_resolve_vm_name_empty_raises(self): + from jumpstarter_driver_kubevirt.driver import KubevirtConsole + + console = KubevirtConsole(namespace="ns", vm_name="") + with pytest.raises(ValueError, match="vm_name not set"): + console._resolve_vm_name() + + @pytest.mark.asyncio + async def test_connect_success(self): + from jumpstarter_driver_kubevirt.driver import KubevirtConsole + + console = KubevirtConsole(namespace="test-ns", vm_name="my-vm", kubeconfig="/path/kc") + mock_stream = MagicMock() + + with ( + patch("jumpstarter_driver_kubevirt.driver._build_console_ws_url") as mock_build_url, + patch("jumpstarter_driver_kubevirt.driver.websockets") as mock_ws, + patch("jumpstarter_driver_kubevirt.driver.WebsocketClientStream") as mock_wss, + ): + mock_build_url.return_value = ("wss://example.com/console", {}, None) + mock_websocket = AsyncMock() + mock_ws_cm = AsyncMock() + mock_ws_cm.__aenter__ = AsyncMock(return_value=mock_websocket) + mock_ws_cm.__aexit__ = AsyncMock(return_value=False) + mock_ws.connect.return_value = mock_ws_cm + + mock_stream_cm = AsyncMock() + mock_stream_cm.__aenter__ = AsyncMock(return_value=mock_stream) + mock_stream_cm.__aexit__ = AsyncMock(return_value=False) + mock_wss.return_value = mock_stream_cm + + async with console.connect() as stream: + assert stream is mock_stream + + @pytest.mark.asyncio + async def test_connect_retry_on_not_running(self): + from websockets.exceptions import InvalidStatus + + from jumpstarter_driver_kubevirt.driver import KubevirtConsole + + console = KubevirtConsole(namespace="test-ns", vm_name="my-vm", kubeconfig="/path/kc") + mock_stream = MagicMock() + + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.body = b"VMI is not running" + + error = InvalidStatus(mock_response) + counter = [0] + + def make_connect_cm(*_args, **_kwargs): + counter[0] += 1 + if counter[0] <= 2: + cm = MagicMock() + cm.__aenter__ = AsyncMock(side_effect=error) + cm.__aexit__ = AsyncMock(return_value=False) + return cm + cm = MagicMock() + cm.__aenter__ = AsyncMock(return_value=AsyncMock()) + cm.__aexit__ = AsyncMock(return_value=False) + return cm + + with ( + patch("jumpstarter_driver_kubevirt.driver._build_console_ws_url") as mock_build_url, + patch("jumpstarter_driver_kubevirt.driver.websockets") as mock_ws, + patch("jumpstarter_driver_kubevirt.driver.WebsocketClientStream") as mock_wss, + patch("jumpstarter_driver_kubevirt.driver.asyncio.sleep", new_callable=AsyncMock) as mock_sleep, + ): + mock_build_url.return_value = ("wss://example.com/console", {}, None) + mock_ws.connect = make_connect_cm + + mock_stream_cm = MagicMock() + mock_stream_cm.__aenter__ = AsyncMock(return_value=mock_stream) + mock_stream_cm.__aexit__ = AsyncMock(return_value=False) + mock_wss.return_value = mock_stream_cm + + async with console.connect(): + pass + + assert mock_sleep.call_count == 2 + + +class TestKubevirtStorage: + def _make_storage(self, **kwargs): + from jumpstarter_driver_kubevirt.driver import KubevirtStorage + + defaults = {"namespace": "test-ns", "vm_name": "test-vm"} + defaults.update(kwargs) + return KubevirtStorage(**defaults) + + def test_client_class_method(self): + from jumpstarter_driver_kubevirt.driver import KubevirtStorage + + assert KubevirtStorage.client() == "jumpstarter_driver_kubevirt.client.KubevirtStorageClient" + + def test_resolve_vm_name(self): + storage = self._make_storage(vm_name="my-vm") + assert storage._resolve_vm_name() == "my-vm" + + def test_resolve_vm_name_empty_raises(self): + storage = self._make_storage(vm_name="") + with pytest.raises(ValueError, match="vm_name not set"): + storage._resolve_vm_name() + + def test_get_client_lazy_init(self): + storage = self._make_storage() + with patch("jumpstarter_driver_kubevirt.driver._build_client") as mock_build: + mock_client = MagicMock() + mock_build.return_value = mock_client + + result = storage._get_client() + assert result is mock_client + + result2 = storage._get_client() + assert result2 is mock_client + assert mock_build.call_count == 1 + + @pytest.mark.asyncio + async def test_flash_replace_success(self): + storage = self._make_storage() + mock_client = AsyncMock() + storage._client = mock_client + + await storage.flash("http://example.com/image.qcow2") + + mock_client.replace.assert_called_once() + mock_client.create.assert_not_called() + + @pytest.mark.asyncio + async def test_flash_replace_404_creates(self): + storage = self._make_storage() + mock_client = AsyncMock() + + api_error = MagicMock() + api_error.status.code = 404 + + from lightkube.core.exceptions import ApiError + + error = ApiError.__new__(ApiError) + error.status = MagicMock() + error.status.code = 404 + + mock_client.replace = AsyncMock(side_effect=error) + storage._client = mock_client + + await storage.flash("http://example.com/image.qcow2") + + mock_client.create.assert_called_once() + + @pytest.mark.asyncio + async def test_flash_replace_other_error_raises(self): + from lightkube.core.exceptions import ApiError + + storage = self._make_storage() + mock_client = AsyncMock() + + error = ApiError.__new__(ApiError) + error.status = MagicMock() + error.status.code = 500 + + mock_client.replace = AsyncMock(side_effect=error) + storage._client = mock_client + + with pytest.raises(ApiError): + await storage.flash("http://example.com/image.qcow2") + + +class TestKubevirt: + def test_client_class_method(self): + from jumpstarter_driver_kubevirt.driver import Kubevirt + + assert Kubevirt.client() == "jumpstarter_driver_kubevirt.client.KubevirtClient" diff --git a/python/packages/jumpstarter-driver-kubevirt/pyproject.toml b/python/packages/jumpstarter-driver-kubevirt/pyproject.toml new file mode 100644 index 000000000..49fb6bf04 --- /dev/null +++ b/python/packages/jumpstarter-driver-kubevirt/pyproject.toml @@ -0,0 +1,47 @@ +[project] +name = "jumpstarter-driver-kubevirt" +dynamic = ["version", "urls"] +description = "Jumpstarter driver for KubeVirt virtual machines" +authors = [ + { name = "Benny Zlotnik", email = "bzlotnik@redhat.com" }, +] +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.11" +dependencies = [ + "jumpstarter", + "jumpstarter-driver-composite", + "jumpstarter-driver-power", + "jumpstarter-driver-network", + "lightkube>=0.15", + "websockets>=13.0", +] + +[project.entry-points."jumpstarter.drivers"] +Kubevirt = "jumpstarter_driver_kubevirt.driver:Kubevirt" +KubevirtPower = "jumpstarter_driver_kubevirt.driver:KubevirtPower" +KubevirtConsole = "jumpstarter_driver_kubevirt.driver:KubevirtConsole" +KubevirtStorage = "jumpstarter_driver_kubevirt.driver:KubevirtStorage" + +[dependency-groups] +dev = ["pytest>=8.3.2", "pytest-cov>=5.0.0"] + +[tool.hatch.metadata.hooks.vcs.urls] +Homepage = "https://jumpstarter.dev" +source_archive = "https://github.com/jumpstarter-dev/repo/archive/{commit_hash}.zip" + +[tool.hatch.version] +source = "vcs" +raw-options = { 'root' = '../../../' } + +[tool.uv.sources] +jumpstarter-driver-composite = { workspace = true } +jumpstarter-driver-network = { workspace = true } +jumpstarter-driver-power = { workspace = true } + +[build-system] +requires = ["hatchling", "hatch-vcs", "hatch-pin-jumpstarter"] +build-backend = "hatchling.build" + +[tool.hatch.build.hooks.pin_jumpstarter] +name = "pin_jumpstarter" diff --git a/python/pyproject.toml b/python/pyproject.toml index f0f1dcef4..d9629e4ce 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -25,6 +25,7 @@ jumpstarter-driver-gpiod = { workspace = true } jumpstarter-driver-ridesx = { workspace = true } jumpstarter-driver-network = { workspace = true } jumpstarter-driver-obd = { workspace = true } +jumpstarter-driver-kubevirt = { workspace = true } jumpstarter-driver-opendal = { workspace = true } jumpstarter-driver-power = { workspace = true } jumpstarter-driver-pi-pico = { workspace = true } diff --git a/python/uv.lock b/python/uv.lock index 1fa9a180c..9d177c988 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -32,6 +32,7 @@ members = [ "jumpstarter-driver-http", "jumpstarter-driver-http-power", "jumpstarter-driver-iscsi", + "jumpstarter-driver-kubevirt", "jumpstarter-driver-mitmproxy", "jumpstarter-driver-network", "jumpstarter-driver-noyito-relay", @@ -268,7 +269,7 @@ version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings", version = "21.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, - { name = "argon2-cffi-bindings", version = "25.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and python_full_version < '3.14'" }, + { name = "argon2-cffi-bindings", version = "25.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } wheels = [ @@ -283,7 +284,7 @@ resolution-markers = [ "python_full_version >= '3.14'", ] dependencies = [ - { name = "cffi", marker = "python_full_version >= '3.14'" }, + { name = "cffi" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" } wheels = [ @@ -307,7 +308,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "cffi", marker = "python_full_version >= '3.12' and python_full_version < '3.14'" }, + { name = "cffi" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } wheels = [ @@ -1106,7 +1107,7 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "cffi", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } wheels = [ @@ -1151,7 +1152,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "cffi", marker = "python_full_version >= '3.12' and platform_python_implementation != 'PyPy'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890, upload-time = "2025-06-10T00:03:51.297Z" } wheels = [ @@ -1378,11 +1379,11 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "blinker", marker = "python_full_version < '3.12'" }, - { name = "click", marker = "python_full_version < '3.12'" }, - { name = "itsdangerous", marker = "python_full_version < '3.12'" }, - { name = "jinja2", marker = "python_full_version < '3.12'" }, - { name = "werkzeug", marker = "python_full_version < '3.12'" }, + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, ] sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824, upload-time = "2024-11-13T18:24:38.127Z" } wheels = [ @@ -1398,12 +1399,12 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "blinker", marker = "python_full_version >= '3.12'" }, - { name = "click", marker = "python_full_version >= '3.12'" }, - { name = "itsdangerous", marker = "python_full_version >= '3.12'" }, - { name = "jinja2", marker = "python_full_version >= '3.12'" }, - { name = "markupsafe", marker = "python_full_version >= '3.12'" }, - { name = "werkzeug", marker = "python_full_version >= '3.12'" }, + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } wheels = [ @@ -1656,8 +1657,8 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "hpack", marker = "python_full_version < '3.12'" }, - { name = "hyperframe", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "hpack" }, + { name = "hyperframe", version = "6.0.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/32/fec683ddd10629ea4ea46d206752a95a2d8a48c22521edd70b142488efe1/h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb", size = 2145593, upload-time = "2021-10-05T18:27:47.18Z" } wheels = [ @@ -1673,8 +1674,8 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "hpack", marker = "python_full_version >= '3.12'" }, - { name = "hyperframe", version = "6.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "hpack" }, + { name = "hyperframe", version = "6.1.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } wheels = [ @@ -1753,8 +1754,8 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "certifi", marker = "python_full_version < '3.12'" }, - { name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "certifi" }, + { name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385, upload-time = "2025-04-11T14:42:46.661Z" } wheels = [ @@ -1770,8 +1771,8 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "certifi", marker = "python_full_version >= '3.12'" }, - { name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "certifi" }, + { name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ @@ -1794,6 +1795,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[package.optional-dependencies] +http2 = [ + { name = "h2", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "h2", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] + [[package]] name = "httpx-sse" version = "0.4.3" @@ -1803,6 +1810,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] +[[package]] +name = "httpx-ws" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore", version = "1.0.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "httpcore", version = "1.0.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "httpx" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/cd/ca91a07ae446451f7476bf3fcc909e98cb942ff032ebfda0e3fe449aca7b/httpx_ws-0.9.0.tar.gz", hash = "sha256:797373326f70eec1ae96f6e43ae9f12002fd7d73aee139a4985eaab964338a08", size = 107105, upload-time = "2026-03-28T14:11:10.781Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/f8/a6bc80313a9e93c888fa10534dfce2ad76ff86911b6f485777ce6de6a073/httpx_ws-0.9.0-py3-none-any.whl", hash = "sha256:71640d2fb1bf9a225775015b33cd755cfd4c5f7e21c885192fe3adc4c387b248", size = 15759, upload-time = "2026-03-28T14:11:11.887Z" }, +] + [[package]] name = "hyperframe" version = "6.0.1" @@ -2790,6 +2813,40 @@ dev = [ { name = "pytest-cov", specifier = ">=6.0.0" }, ] +[[package]] +name = "jumpstarter-driver-kubevirt" +source = { editable = "packages/jumpstarter-driver-kubevirt" } +dependencies = [ + { name = "jumpstarter" }, + { name = "jumpstarter-driver-composite" }, + { name = "jumpstarter-driver-network" }, + { name = "jumpstarter-driver-power" }, + { name = "lightkube" }, + { name = "websockets" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "jumpstarter", editable = "packages/jumpstarter" }, + { name = "jumpstarter-driver-composite", editable = "packages/jumpstarter-driver-composite" }, + { name = "jumpstarter-driver-network", editable = "packages/jumpstarter-driver-network" }, + { name = "jumpstarter-driver-power", editable = "packages/jumpstarter-driver-power" }, + { name = "lightkube", specifier = ">=0.15" }, + { name = "websockets", specifier = ">=13.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.2" }, + { name = "pytest-cov", specifier = ">=5.0.0" }, +] + [[package]] name = "jumpstarter-driver-mitmproxy" source = { editable = "packages/jumpstarter-driver-mitmproxy" } @@ -4080,6 +4137,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/f6/71d6ec9f18da0b2201287ce9db6afb1a1f637dedb3f0703409558981c723/ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", size = 432192, upload-time = "2021-07-18T06:34:12.905Z" }, ] +[[package]] +name = "lightkube" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "httpx-ws" }, + { name = "lightkube-models" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/3c/433a6402eea7b26c74db55aab394d6e9be7bd919ee97c577eaa9a89f9d9b/lightkube-0.22.0.tar.gz", hash = "sha256:8de27f9015fa8263569b725b39d51829f83040e15c9b344a54a51babaf55fb5f", size = 37325, upload-time = "2026-07-09T09:39:42.398Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/72/55121a42a4735666c623ac70ecb1f7cfadade74c675af117dfb7262b1730/lightkube-0.22.0-py3-none-any.whl", hash = "sha256:5e35a46c216cae694aa6f75f5bcbfb7dd2999424ac910eab605bebc065fdb2de", size = 47385, upload-time = "2026-07-09T09:39:44.025Z" }, +] + +[[package]] +name = "lightkube-models" +version = "1.36.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/7b/1bf9091a50db1e00dd95624211dd9131d5f65f021128f38e706a0c9e9144/lightkube_models-1.36.3.8.tar.gz", hash = "sha256:f1305191951baa9ea16a6603b70f1f7d156c23a7a2777d755d2e4bd59979cb83", size = 261343, upload-time = "2026-07-26T08:40:47.755Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/7f/57524764347c39a9d523c4eef68522b693523d7dbda13a2a14f9a39f315b/lightkube_models-1.36.3.8-py3-none-any.whl", hash = "sha256:a938eed82e61ae3083e7cac15802e58cf8f7e5d7d6a9a9576d5cbb043427db46", size = 322569, upload-time = "2026-07-26T08:40:45.697Z" }, +] + [[package]] name = "line-profiler" version = "5.0.2" @@ -4278,31 +4359,31 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "aioquic", marker = "python_full_version < '3.12'" }, - { name = "asgiref", version = "3.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "brotli", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "certifi", marker = "python_full_version < '3.12'" }, - { name = "cryptography", version = "44.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "flask", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "h2", version = "4.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "hyperframe", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "kaitaistruct", version = "0.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ldap3", marker = "python_full_version < '3.12'" }, - { name = "mitmproxy-rs", version = "0.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "msgpack", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "passlib", marker = "python_full_version < '3.12'" }, - { name = "publicsuffix2", marker = "python_full_version < '3.12'" }, - { name = "pydivert", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, - { name = "pyopenssl", version = "24.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "pyparsing", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "pyperclip", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ruamel-yaml", version = "0.18.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "sortedcontainers", marker = "python_full_version < '3.12'" }, - { name = "tornado", version = "6.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "urwid", version = "2.6.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "wsproto", marker = "python_full_version < '3.12'" }, - { name = "zstandard", version = "0.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "aioquic" }, + { name = "asgiref", version = "3.8.1", source = { registry = "https://pypi.org/simple" } }, + { name = "brotli", version = "1.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "certifi" }, + { name = "cryptography", version = "44.0.3", source = { registry = "https://pypi.org/simple" } }, + { name = "flask", version = "3.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "h11", version = "0.14.0", source = { registry = "https://pypi.org/simple" } }, + { name = "h2", version = "4.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "hyperframe", version = "6.0.1", source = { registry = "https://pypi.org/simple" } }, + { name = "kaitaistruct", version = "0.10", source = { registry = "https://pypi.org/simple" } }, + { name = "ldap3" }, + { name = "mitmproxy-rs", version = "0.10.7", source = { registry = "https://pypi.org/simple" } }, + { name = "msgpack", version = "1.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "passlib" }, + { name = "publicsuffix2" }, + { name = "pydivert", marker = "sys_platform == 'win32'" }, + { name = "pyopenssl", version = "24.3.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pyparsing", version = "3.2.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pyperclip", version = "1.9.0", source = { registry = "https://pypi.org/simple" } }, + { name = "ruamel-yaml", version = "0.18.6", source = { registry = "https://pypi.org/simple" } }, + { name = "sortedcontainers" }, + { name = "tornado", version = "6.4.2", source = { registry = "https://pypi.org/simple" } }, + { name = "urwid", version = "2.6.16", source = { registry = "https://pypi.org/simple" } }, + { name = "wsproto" }, + { name = "zstandard", version = "0.23.0", source = { registry = "https://pypi.org/simple" } }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/66/88/5f503d5dd63aa8e0e6d788380e8e8b5d172b682eb5770da625bf70a5f0a7/mitmproxy-11.0.2-py3-none-any.whl", hash = "sha256:95db7b57b21320a0c76e59e1d6644daaa431291cdf89419608301424651199b4", size = 1658730, upload-time = "2024-12-05T09:38:10.269Z" }, @@ -4317,33 +4398,33 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "aioquic", marker = "python_full_version >= '3.12'" }, - { name = "argon2-cffi", marker = "python_full_version >= '3.12'" }, - { name = "asgiref", version = "3.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "bcrypt", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "brotli", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "certifi", marker = "python_full_version >= '3.12'" }, - { name = "cryptography", version = "45.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "flask", version = "3.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "h2", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "hyperframe", version = "6.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "kaitaistruct", version = "0.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "ldap3", marker = "python_full_version >= '3.12'" }, - { name = "mitmproxy-rs", version = "0.12.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "msgpack", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "publicsuffix2", marker = "python_full_version >= '3.12'" }, - { name = "pydivert", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "pyopenssl", version = "25.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pyparsing", version = "3.2.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pyperclip", version = "1.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "ruamel-yaml", version = "0.18.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "sortedcontainers", marker = "python_full_version >= '3.12'" }, - { name = "tornado", version = "6.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "typing-extensions", marker = "python_full_version == '3.12.*'" }, - { name = "urwid", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "wsproto", marker = "python_full_version >= '3.12'" }, - { name = "zstandard", version = "0.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "aioquic" }, + { name = "argon2-cffi" }, + { name = "asgiref", version = "3.10.0", source = { registry = "https://pypi.org/simple" } }, + { name = "bcrypt", version = "5.0.0", source = { registry = "https://pypi.org/simple" } }, + { name = "brotli", version = "1.2.0", source = { registry = "https://pypi.org/simple" } }, + { name = "certifi" }, + { name = "cryptography", version = "45.0.4", source = { registry = "https://pypi.org/simple" } }, + { name = "flask", version = "3.1.2", source = { registry = "https://pypi.org/simple" } }, + { name = "h11", version = "0.16.0", source = { registry = "https://pypi.org/simple" } }, + { name = "h2", version = "4.3.0", source = { registry = "https://pypi.org/simple" } }, + { name = "hyperframe", version = "6.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "kaitaistruct", version = "0.11", source = { registry = "https://pypi.org/simple" } }, + { name = "ldap3" }, + { name = "mitmproxy-rs", version = "0.12.9", source = { registry = "https://pypi.org/simple" } }, + { name = "msgpack", version = "1.1.1", source = { registry = "https://pypi.org/simple" } }, + { name = "publicsuffix2" }, + { name = "pydivert", marker = "sys_platform == 'win32'" }, + { name = "pyopenssl", version = "25.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pyparsing", version = "3.2.5", source = { registry = "https://pypi.org/simple" } }, + { name = "pyperclip", version = "1.11.0", source = { registry = "https://pypi.org/simple" } }, + { name = "ruamel-yaml", version = "0.18.16", source = { registry = "https://pypi.org/simple" } }, + { name = "sortedcontainers" }, + { name = "tornado", version = "6.5.2", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "urwid", version = "3.0.3", source = { registry = "https://pypi.org/simple" } }, + { name = "wsproto" }, + { name = "zstandard", version = "0.25.0", source = { registry = "https://pypi.org/simple" } }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/54/d4/2acc254beec19403269652ead42735c98baf6d56d060ef9dfe34256bda22/mitmproxy-12.2.1-py3-none-any.whl", hash = "sha256:7a508cc9fb906253eb26460d99b3572bf5a7b4a185ab62534379ac1915677dd2", size = 1650400, upload-time = "2025-11-24T19:01:11.712Z" }, @@ -4390,8 +4471,8 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "mitmproxy-macos", version = "0.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' and sys_platform == 'darwin'" }, - { name = "mitmproxy-windows", version = "0.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' and os_name == 'nt'" }, + { name = "mitmproxy-macos", version = "0.10.7", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "mitmproxy-windows", version = "0.10.7", source = { registry = "https://pypi.org/simple" }, marker = "os_name == 'nt'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/64/114311494f8fb689343ce348b7f046bbc67a88247ffc655dc4c3440286fb/mitmproxy_rs-0.10.7.tar.gz", hash = "sha256:0959a540766403222464472b64122ac8ccbca66b5f019154496b98e62482277f", size = 1183834, upload-time = "2024-10-28T11:56:39.622Z" } wheels = [ @@ -4410,9 +4491,9 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "mitmproxy-linux", marker = "python_full_version >= '3.12' and sys_platform == 'linux'" }, - { name = "mitmproxy-macos", version = "0.12.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and sys_platform == 'darwin'" }, - { name = "mitmproxy-windows", version = "0.12.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and os_name == 'nt'" }, + { name = "mitmproxy-linux", marker = "sys_platform == 'linux'" }, + { name = "mitmproxy-macos", version = "0.12.9", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "mitmproxy-windows", version = "0.12.9", source = { registry = "https://pypi.org/simple" }, marker = "os_name == 'nt'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5d/5c/16a61303da76cd34aa6ddbd7ef6ac66d9ef8514c4d3a5b71831169d63236/mitmproxy_rs-0.12.9.tar.gz", hash = "sha256:c6ffc35c002c675cac534442d92d1cdebd66fafd63754ad33b92ae968ea6e449", size = 1334424, upload-time = "2026-01-30T14:54:15.043Z" } wheels = [ @@ -5353,7 +5434,7 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "cryptography", version = "44.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "cryptography", version = "44.0.3", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/c1/d4/1067b82c4fc674d6f6e9e8d26b3dff978da46d351ca3bac171544693e085/pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36", size = 178944, upload-time = "2024-11-27T20:43:12.755Z" } wheels = [ @@ -5369,8 +5450,8 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "cryptography", version = "45.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "typing-extensions", marker = "python_full_version == '3.12.*'" }, + { name = "cryptography", version = "45.0.4", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937, upload-time = "2025-05-17T16:28:31.31Z" } wheels = [ @@ -6036,7 +6117,7 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "ruamel-yaml-clib", marker = "python_full_version < '3.12' and platform_python_implementation == 'CPython'" }, + { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/81/4dfc17eb6ebb1aac314a3eb863c1325b907863a1b8b1382cdffcb6ac0ed9/ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b", size = 143362, upload-time = "2024-02-07T06:47:20.283Z" } wheels = [ @@ -6052,7 +6133,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "ruamel-yaml-clib", marker = "python_full_version >= '3.12' and python_full_version < '3.14' and platform_python_implementation == 'CPython'" }, + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a", size = 147269, upload-time = "2025-10-22T17:54:02.346Z" } wheels = [ @@ -6772,8 +6853,8 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, - { name = "wcwidth", marker = "python_full_version < '3.12'" }, + { name = "typing-extensions" }, + { name = "wcwidth" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/21/ad23c9e961b2d36d57c63686a6f86768dd945d406323fb58c84f09478530/urwid-2.6.16.tar.gz", hash = "sha256:93ad239939e44c385e64aa00027878b9e5c486d59e855ec8ab5b1e1adcdb32a2", size = 848179, upload-time = "2024-10-15T16:07:24.297Z" } wheels = [ @@ -6789,7 +6870,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "wcwidth", marker = "python_full_version >= '3.12'" }, + { name = "wcwidth" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bb/d3/09683323e2290732a39dc92ca5031d5e5ddda56f8d236f885a400535b29a/urwid-3.0.3.tar.gz", hash = "sha256:300804dd568cda5aa1c5b204227bd0cfe7a62cef2d00987c5eb2e4e64294ed9b", size = 855817, upload-time = "2025-09-15T10:26:17.089Z" } wheels = [ @@ -7329,7 +7410,7 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "cffi", marker = "python_full_version < '3.12' and platform_python_implementation == 'PyPy'" }, + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } wheels = [