diff --git a/Makefile b/Makefile index 544f86a9d..2bfd9f061 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ endif # Push/Load configuration - can be overridden by PUSH_IMAGES env var or make parameter ifeq ($(PUSH_IMAGES),false) # Load images locally when PUSH_IMAGES is not true (default) -DOCKER_BUILDER = +DOCKER_BUILDER = --builder ${BUILDX_BUILDER} --load MULTIARCH_PLATFORMS = $(DOCKER_PLATFORM) else # Push images to registry when PUSH_IMAGES is true @@ -173,7 +173,7 @@ build-all-images: setup-buildx build-session-manager build-training-portal \ build-conda-environment build-docker-registry \ build-pause-container build-secrets-manager build-tunnel-manager \ build-image-cache build-assets-server build-lookup-service \ - build-cli-image + build-cli-image build-docker-extension build-core-images: setup-buildx build-session-manager build-training-portal \ build-base-environment build-docker-registry build-pause-container \ @@ -343,7 +343,7 @@ push-client-programs: build-client-programs (cd client-programs; GOOS=linux GOARCH=arm64 go build -o bin/educates-linux-arm64 cmd/educates/main.go) imgpkg push -i $(IMAGE_REPOSITORY)/educates-client-programs:$(PACKAGE_VERSION) -f client-programs/bin -build-cli-image: +build-cli-image: build-base-environment docker build --progress plain --platform $(MULTIARCH_PLATFORMS) \ $(DOCKER_BUILDER) \ -t $(IMAGE_REPOSITORY)/educates-cli:$(PACKAGE_VERSION) \ @@ -361,7 +361,7 @@ update-docker-extension : build-docker-extension project-docs/venv : python3 -m venv project-docs/venv project-docs/venv/bin/pip install -r project-docs/requirements.txt - + build-project-docs : project-docs/venv source project-docs/venv/bin/activate && make -C project-docs html @@ -418,4 +418,4 @@ clean-buildx: ## Clean up builder # Multiarch utility targets list-platforms: ## List available platforms for multiarch builds - @echo "Supported platforms: $(MULTIARCH_PLATFORMS)" \ No newline at end of file + @echo "Supported platforms: $(MULTIARCH_PLATFORMS)" diff --git a/assets-server/Dockerfile b/assets-server/Dockerfile index d67b1e023..cfbcd2300 100644 --- a/assets-server/Dockerfile +++ b/assets-server/Dockerfile @@ -1,11 +1,16 @@ -FROM golang:1.19-buster AS builder-image +FROM golang:1.25.6 AS builder-image WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod tidy +RUN go mod download +COPY . . +ARG TARGETOS +ARG TARGETARCH +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \ + -o assets-server \ + main.go -COPY . /app/ - -RUN go mod download && \ - go build -o assets-server main.go FROM fedora:42 diff --git a/assets-server/go.mod b/assets-server/go.mod index ba1b0c6ca..db5a10560 100644 --- a/assets-server/go.mod +++ b/assets-server/go.mod @@ -1,6 +1,6 @@ module github.com/educates/educates-training-platform/assets-server -go 1.20 +go 1.25.6 require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/client-programs/Dockerfile b/client-programs/Dockerfile index db8ccfd10..21654245d 100644 --- a/client-programs/Dockerfile +++ b/client-programs/Dockerfile @@ -5,10 +5,7 @@ ARG TAG=latest FROM ${REPOSITORY}/educates-base-environment:${TAG} AS themes-source # Multi-stage build for client-programs -FROM golang:1.24.10-alpine AS builder - -# Install build dependencies -RUN apk add --no-cache git ca-certificates tzdata +FROM golang:1.25.6 AS builder # Set working directory WORKDIR /src @@ -16,6 +13,9 @@ WORKDIR /src # Copy go mod files COPY go.mod go.sum ./ +# Tidy up dependencies +RUN go mod tidy + # Download dependencies RUN go mod download @@ -40,9 +40,8 @@ RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \ # Runtime stage - minimal image with binaries FROM scratch -# Build for multiple architectures ARG TARGETOS ARG TARGETARCH # Copy binaries from builder COPY --from=builder /src/bin/educates-${TARGETOS}-${TARGETARCH} /educates -ENTRYPOINT ["/educates"] \ No newline at end of file +ENTRYPOINT ["/educates"] diff --git a/client-programs/go.mod b/client-programs/go.mod index 494e17e4d..e677d6dde 100644 --- a/client-programs/go.mod +++ b/client-programs/go.mod @@ -1,12 +1,6 @@ module github.com/educates/educates-training-platform/client-programs -go 1.24.10 - -// replace cloud.google.com/go/compute/metadata => cloud.google.com/go/compute/metadata v0.2.3 - -// replace github.com/google/cel-go => github.com/google/cel-go v0.22.1 - -// replace github.com/docker/docker => github.com/docker/docker v27.5.1+incompatible +go 1.25.6 require ( carvel.dev/imgpkg v0.46.1 @@ -15,11 +9,9 @@ require ( carvel.dev/vendir v0.44.0 carvel.dev/ytt v0.52.1 github.com/adrg/xdg v0.5.3 - github.com/compose-spec/compose-go v1.20.2 + github.com/compose-spec/compose-go/v2 v2.10.1 github.com/cppforlife/go-cli-ui v0.0.0-20250603184554-47874c9078ad - // Every time we update below version, we need to update Docker Desktop client to match the required version - // or else downgrade CLI support via export DOCKER_API_VERSION=1.xx - // Version compabitility: https://github.com/moby/moby/blob/master/docs/api/version-history.md + // Docker packages must be kept aligned with docker/compose v5.0.1 requirements. This still relies on docker/docker v28.5.2 github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.6.0 github.com/go-logr/logr v1.4.3 @@ -27,24 +19,31 @@ require ( github.com/joho/godotenv v1.5.1 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.34.2 k8s.io/apimachinery v0.34.2 k8s.io/cli-runtime v0.34.2 k8s.io/client-go v0.34.2 - k8s.io/controller-manager v0.33.5 // indirect k8s.io/klog/v2 v2.130.1 + // Keep kubectl aligned with DefaultKubernetesVersion in constants/kubernetes.go and k8s.io/api, k8s.io/apimachinery, k8s.io/cli-runtime, k8s.io/client-go, sigs.k8s.io/kind, sigs.k8s.io/controller-runtime k8s.io/kubectl v0.34.2 sigs.k8s.io/controller-runtime v0.22.4 - sigs.k8s.io/kind v0.29.0 + sigs.k8s.io/kind v0.31.0 sigs.k8s.io/yaml v1.6.0 ) +require ( + // Keep docker/cli aligned with docker/docker - see comment above + github.com/docker/cli v28.5.2+incompatible + // This still relies on docker/docker v28.5.2 so we need to align docker/docker and docker/cli to the same version + github.com/docker/compose/v5 v5.0.1 + go.yaml.in/yaml/v2 v2.4.3 +) + require ( al.essio.dev/pkg/shellescape v1.6.0 // indirect - cel.dev/expr v0.25.1 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect @@ -57,10 +56,11 @@ require ( github.com/Azure/go-autorest/logger v0.2.2 // indirect github.com/Azure/go-autorest/tracing v0.6.1 // indirect github.com/BurntSushi/toml v1.5.0 // indirect + github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/VividCortex/ewma v1.2.0 // indirect - github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect github.com/aws/aws-sdk-go-v2/config v1.31.19 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.23 // indirect @@ -78,8 +78,8 @@ require ( github.com/aws/smithy-go v1.23.2 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.11.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/buger/goterm v1.0.4 // indirect github.com/carvel-dev/semver/v4 v4.0.1-0.20240402203627-beb83fbf25e4 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -87,23 +87,37 @@ require ( github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect + github.com/containerd/console v1.0.5 // indirect + github.com/containerd/containerd/api v1.10.0 // indirect + github.com/containerd/containerd/v2 v2.2.1-0.20251115011841-efd86f2b0bc2 // indirect + github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v1.0.0-rc.2 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect + github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/cppforlife/cobrautil v0.0.0-20221130162803-acdfead391ef // indirect github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835 // indirect github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v29.0.0+incompatible // indirect + github.com/docker/buildx v0.30.1 // indirect + github.com/docker/cli-docs-tool v0.11.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.4 // indirect + github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect + github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsevents v0.2.0 // indirect + github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect @@ -120,19 +134,28 @@ require ( github.com/go-openapi/swag/stringutils v0.25.1 // indirect github.com/go-openapi/swag/typeutils v0.25.1 // indirect github.com/go-openapi/swag/yamlutils v0.25.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/google/cel-go v0.26.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.20.6 // indirect github.com/google/go-github v17.0.0+incompatible // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect + github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/k14s/difflib v0.0.0-20240118055029-596a7a5585c3 // indirect github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect @@ -143,74 +166,96 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/moby/buildkit v0.26.3 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect + github.com/moby/sys/symlink v0.3.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/morikuni/aec v1.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/openshift/crd-schema-checker v0.0.0-20250905140724-c313b6407231 // indirect github.com/otiai10/copy v1.14.1 // indirect github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml v1.9.5 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.2 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/stoewer/go-strcase v1.3.1 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/theupdateframework/notary v0.7.0 // indirect + github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect + github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect + github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect + github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect + github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/vbatts/tar-split v0.12.2 // indirect github.com/vito/go-interact v1.0.2 // indirect github.com/vmware-tanzu/carvel-kapp-controller v0.51.3 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/grpc v1.76.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.34.1 // indirect - k8s.io/apiserver v0.34.1 // indirect - k8s.io/component-base v0.34.2 // indirect - k8s.io/component-helpers v0.34.2 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/kubernetes v1.34.2 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // 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/v6 v6.3.0 // indirect + tags.cncf.io/container-device-interface v1.1.0 // indirect ) diff --git a/client-programs/go.sum b/client-programs/go.sum index 4355849be..85248def8 100644 --- a/client-programs/go.sum +++ b/client-programs/go.sum @@ -10,10 +10,10 @@ carvel.dev/vendir v0.44.0 h1:vfq5KgGbbLlxHrE0prY7gZgiEQpjwo4lS2akCaVkcxA= carvel.dev/vendir v0.44.0/go.mod h1:gslrJ0HPiy8gtJYsQZHzIVuGfOG0nfDKDupEm7uBWVQ= carvel.dev/ytt v0.52.1 h1:I9rCwIunzClas2MH5nVGtCK5ujZdiGaqAfGol/wiRKQ= carvel.dev/ytt v0.52.1/go.mod h1:lzkMguCvSVvxT2My9RG3gRMgTws97NpNXufKZ6iiP5E= -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= @@ -44,18 +44,29 @@ github.com/Azure/go-autorest/logger v0.2.2/go.mod h1:I5fg9K52o+iuydlWfa9T5K6WFos github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.1 h1:YUMSrC/CeD1ZnnXcNYU4a/fzsO35u2Fsful9L/2nyR0= github.com/Azure/go-autorest/tracing v0.6.1/go.mod h1:/3EgjbsjraOqiicERAeu3m7/z0x1TzjQGAwDrJrXGkc= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0= +github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ= +github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c= +github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6DKrR4/ljxn6SF6nURyu785wKMuQcjt7H3VCQ= +github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= -github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= -github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= @@ -90,16 +101,26 @@ github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.11.0 h1:GOPttfOAf5qAgx7r6b+zCWZrvCsfKffkL4H6mSYx1kA= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.11.0/go.mod h1:a2HN6+p7k0JLDO8514sMr0l4cnrR52z4sWoZ/Uc82ho= +github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= +github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= +github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0 h1:s7+5BfS4WFJoVF9pnB8kBk03S7pZXRdKamnV0FOl5Sc= +github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/carvel-dev/semver/v4 v4.0.1-0.20240402203627-beb83fbf25e4 h1:F4rZiMGZyC66j9VB7doVOE4tFHF1yNEihQlOuht4jmM= github.com/carvel-dev/semver/v4 v4.0.1-0.20240402203627-beb83fbf25e4/go.mod h1:4cFTBLAr/U11ykiEEQMccu4uJ1i0GS+atJmeETHCFtI= -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/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -115,20 +136,42 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= -github.com/compose-spec/compose-go v1.20.2 h1:u/yfZHn4EaHGdidrZycWpxXgFffjYULlTbRfJ51ykjQ= -github.com/compose-spec/compose-go v1.20.2/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= +github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= +github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU= +github.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg= +github.com/containerd/cgroups/v3 v3.1.0 h1:azxYVj+91ZgSnIBp2eI3k9y2iYQSR/ZQIgh9vKO+HSY= +github.com/containerd/cgroups/v3 v3.1.0/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= +github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= +github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= +github.com/containerd/containerd/v2 v2.2.1-0.20251115011841-efd86f2b0bc2 h1:WcvXNS/OmpiitTVdzRAudKwvShKxcOP4Elf2FyxSoTg= +github.com/containerd/containerd/v2 v2.2.1-0.20251115011841-efd86f2b0bc2/go.mod h1:YCMjKjA4ZA7egdHNi3/93bJR1+2oniYlnS+c0N62HdE= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/nydus-snapshotter v0.15.4 h1:l59kGRVMtwMLDLh322HsWhEsBCkRKMkGWYV5vBeLYCE= +github.com/containerd/nydus-snapshotter v0.15.4/go.mod h1:eRJqnxQDr48HNop15kZdLZpFF5B6vf6Q11Aq1K0E4Ms= +github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= +github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= +github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/cppforlife/cobrautil v0.0.0-20221130162803-acdfead391ef h1:de10GNLe45JTMghl2qf9WH17H/BjGShK41X3vKAsPJA= github.com/cppforlife/cobrautil v0.0.0-20221130162803-acdfead391ef/go.mod h1:2w+qxVu2KSGW78Ex/XaIqfh/OvBgjEsmN53S4T8vEyA= github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835 h1:mYQweUIBD+TBRjIeQnJmXr0GSVMpI6O0takyb/aaOgo= @@ -138,40 +181,71 @@ github.com/cppforlife/go-cli-ui v0.0.0-20250603184554-47874c9078ad/go.mod h1:xZh github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b h1:+8LQctLhaj+63L/37l8IK/5Q3odN6RzWlglonUwrKok= github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= 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/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 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/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v29.0.0+incompatible h1:KgsN2RUFMNM8wChxryicn4p46BdQWpXOA1XLGBGPGAw= -github.com/docker/cli v29.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/buildx v0.30.1 h1:3vthfaTQOLt5QfN2nl7rKuPLUvx69nL5ZikFIXp//c8= +github.com/docker/buildx v0.30.1/go.mod h1:8nwT0V6UNYNo9rXq6WO/BQd9KrJ0JYcY/QX6x0y1Oro= +github.com/docker/cli v28.5.2+incompatible h1:XmG99IHcBmIAoC1PPg9eLBZPlTrNUAijsHLm8PjhBlg= +github.com/docker/cli v28.5.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli-docs-tool v0.11.0 h1:7d8QARFb7QEobizqxmEM7fOteZEHwH/zWgHQtHZEcfE= +github.com/docker/cli-docs-tool v0.11.0/go.mod h1:ma8BKiisUo8D6W05XEYIh3oa1UbgrZhi1nowyKFJa8Q= +github.com/docker/compose/v5 v5.0.1 h1:5yCjDJbwUqcuI+6WNFHNWz2/3vyBDsNnfe8LlFjyxEc= +github.com/docker/compose/v5 v5.0.1/go.mod h1:vuKBtnRuvsVIlYHzdPkF3SToljqR+pFJseO5lDBuF18= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI= github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= +github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c= +github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQwKBr1qH/w= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 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/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= +github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= 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/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 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= @@ -209,8 +283,17 @@ github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91o github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE= +github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -218,14 +301,22 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= -github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= +github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= 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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2/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= @@ -238,28 +329,53 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.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-20250315033105-103756e64e1d h1:tx51Lf+wdE+aavqH8TcPJoCjTf4cE8hrMzROghCely0= -github.com/google/pprof v0.0.0-20250315033105-103756e64e1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 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/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= +github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= +github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE= +github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc= +github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k14s/difflib v0.0.0-20240118055029-596a7a5585c3 h1:q2ikACDbDDbyUcN9JkDcNMGhIx1EBRkctAsPZMr35qM= github.com/k14s/difflib v0.0.0-20240118055029-596a7a5585c3/go.mod h1:B0xN2MiNBGWOWi9CcfAo9LBI8IU4J1utlbOIJCsmKr4= github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 h1:4bcRTTSx+LKSxMWibIwzHnDNmaN1x52oEpvnjCy+8vk= @@ -270,16 +386,26 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -291,48 +417,90 @@ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byF github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/buildkit v0.26.3 h1:D+ruZVAk/3ipRq5XRxBH9/DIFpRjSlTtMbghT5gQP9g= +github.com/moby/buildkit v0.26.3/go.mod h1:4T4wJzQS4kYWIfFRjsbJry4QoxDBjK+UGOEOs1izL7w= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= +github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= +github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU= +github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= +github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 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.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ= github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= 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/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/openshift/crd-schema-checker v0.0.0-20250905140724-c313b6407231 h1:8lSGufji9rfiyDxtUl7A4uOyeeP4x0UOOXcsDBFfkGI= -github.com/openshift/crd-schema-checker v0.0.0-20250905140724-c313b6407231/go.mod h1:sTxJ4ZFW9r9fEdbW2v0yMRi6NcyTbx0fII4p83IQ+L8= +github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= +github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= +github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= @@ -341,45 +509,107 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= +github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 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.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= +github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= +github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.9/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.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= -github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= +github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= +github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA= +github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= +github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI= +github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vito/go-interact v1.0.2 h1:viJuANio3WH9utUG4rKbJC9V3JR5JgYNS+i0efeA+GU= @@ -388,59 +618,60 @@ github.com/vmware-tanzu/carvel-kapp-controller v0.51.3 h1:TBeFKz1cmdI8vreaWT8waR github.com/vmware-tanzu/carvel-kapp-controller v0.51.3/go.mod h1:Ndy9tru0vO/UwChzM8GL6OHHCLCSL7InFB82Qxdyc8Q= 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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 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/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel 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/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +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= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +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.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= 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.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= 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= +go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= +go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= @@ -452,7 +683,12 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -465,32 +701,44 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/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-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -523,69 +771,73 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= +gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM= +gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -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.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/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/cli-runtime v0.34.2 h1:cct1GEuWc3IyVT8MSCoIWzRGw9HJ/C5rgP32H60H6aE= k8s.io/cli-runtime v0.34.2/go.mod h1:X13tsrYexYUCIq8MarCBy8lrm0k0weFPTpcaNo7lms4= k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= -k8s.io/component-helpers v0.34.2 h1:RIUGDdU+QFzeVKLZ9f05sXTNAtJrRJ3bnbMLrogCrvM= -k8s.io/component-helpers v0.34.2/go.mod h1:pLi+GByuRTeFjjcezln8gHL7LcT6HImkwVQ3A2SQaEE= -k8s.io/controller-manager v0.33.5 h1:abmssknXnhOhW533583v2SYQObD5RhYiSL7Za1rezGM= -k8s.io/controller-manager v0.33.5/go.mod h1:KuQeAlf4vI2+qj5fwPVLaDlbtrTBA/8L/LqQvI74Ow0= 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/kubectl v0.34.2 h1:+fWGrVlDONMUmmQLDaGkQ9i91oszjjRAa94cr37hzqA= k8s.io/kubectl v0.34.2/go.mod h1:X2KTOdtZZNrTWmUD4oHApJ836pevSl+zvC5sI6oO2YQ= -k8s.io/kubernetes v1.34.2 h1:WQdDvYJazkmkwSncgNwGvVtaCt4TYXIU3wSMRgvp3MI= -k8s.io/kubernetes v1.34.2/go.mod h1:m6pZk6a179pRo2wsTiCPORJ86iOEQmfIzUvtyEF8BwA= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= 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/kind v0.29.0 h1:3TpCsyh908IkXXpcSnsMjWdwdWjIl7o9IMZImZCWFnI= -sigs.k8s.io/kind v0.29.0/go.mod h1:ldWQisw2NYyM6k64o/tkZng/1qQW7OlzcN5a8geJX3o= +sigs.k8s.io/kind v0.31.0 h1:UcT4nzm+YM7YEbqiAKECk+b6dsvc/HRZZu9U0FolL1g= +sigs.k8s.io/kind v0.31.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +tags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY= +tags.cncf.io/container-device-interface v1.1.0/go.mod h1:76Oj0Yqp9FwTx/pySDc8Bxjpg+VqXfDb50cKAXVJ34Q= diff --git a/client-programs/pkg/cluster/cluster.go b/client-programs/pkg/cluster/cluster.go index bf4b80d89..00f5c402f 100644 --- a/client-programs/pkg/cluster/cluster.go +++ b/client-programs/pkg/cluster/cluster.go @@ -50,7 +50,7 @@ func GetConfig(kubeconfigPath string, context string) (*rest.Config, error) { if kubeconfigPath != "" { if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) { // If kubeconfig is provided but not available, fail - return nil, errors.Wrap(err, "kubeconfig file does not exist") + return nil, fmt.Errorf("kubeconfig file does not exist: %s", kubeconfigPath) } } diff --git a/client-programs/pkg/cluster/kindcluster.go b/client-programs/pkg/cluster/kindcluster.go index c6b6ab4ac..9f23907cb 100644 --- a/client-programs/pkg/cluster/kindcluster.go +++ b/client-programs/pkg/cluster/kindcluster.go @@ -8,16 +8,21 @@ import ( "html/template" "os" "path/filepath" + "strings" "time" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" "github.com/pkg/errors" "golang.org/x/exp/slices" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "github.com/educates/educates-training-platform/client-programs/pkg/config" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/docker" "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) @@ -53,7 +58,7 @@ func (o *KindClusterConfig) ClusterExists() (bool, error) { return false, errors.Wrap(err, "unable to get list of clusters") } - if slices.Contains(clusters, "educates") { + if slices.Contains(clusters, constants.EducatesClusterName) { return true, errors.New("cluster for Educates already exists") } @@ -89,7 +94,7 @@ func (o *KindClusterConfig) CreateCluster(config *config.InstallationConfig, ima return errors.Wrapf(err, "unable to create config directory") } - kindConfigPath := filepath.Join(configFileDir, "educates-cluster-config.yaml") + kindConfigPath := filepath.Join(configFileDir, fmt.Sprintf("%s-cluster-config.yaml", constants.EducatesClusterName)) err = os.WriteFile(kindConfigPath, clusterConfigData.Bytes(), 0644) if err != nil { return errors.Wrap(err, "failed to write cluster config to file") @@ -98,7 +103,7 @@ func (o *KindClusterConfig) CreateCluster(config *config.InstallationConfig, ima fmt.Println("Cluster config used is saved to: ", kindConfigPath) if err := o.provider.Create( - "educates", + constants.EducatesClusterName, cluster.CreateWithRawConfig(clusterConfigData.Bytes()), cluster.CreateWithNodeImage(image), cluster.CreateWithWaitForReady(time.Duration(time.Duration(60)*time.Second)), @@ -122,7 +127,7 @@ func (o *KindClusterConfig) DeleteCluster() error { fmt.Println("Deleting cluster educates ...") - if err := o.provider.Delete("educates", o.Config.Kubeconfig); err != nil { + if err := o.provider.Delete(constants.EducatesClusterName, o.Config.Kubeconfig); err != nil { return errors.Wrapf(err, "failed to delete cluster") } @@ -145,25 +150,40 @@ func (o *KindClusterConfig) StopCluster() error { return errors.Wrap(err, "unable to create docker client") } - _, err = cli.ContainerInspect(ctx, "educates-control-plane") + // Get all kind node containers for the educates cluster + nodeFilters := filters.NewArgs() + nodeFilters.Add("label", fmt.Sprintf("io.x-k8s.kind.cluster=%s", constants.EducatesClusterName)) + containers, err := cli.ContainerList(ctx, container.ListOptions{ + Filters: nodeFilters, + }) if err != nil { - return errors.Wrap(err, "no container for Educates cluster") + return errors.Wrap(err, "failed to list kind node containers") + } + + if len(containers) == 0 { + return errors.New("no containers found for Educates cluster") } fmt.Println("Stopping cluster educates ...") timeout := 30 - if err := cli.ContainerStop(ctx, "educates-control-plane", container.StopOptions{Timeout: &timeout}); err != nil { - return errors.Wrapf(err, "failed to stop cluster") - } - - // timeout := time.Duration(30) * time.Second + // Stop all containers (control-plane and workers) + for _, c := range containers { + containerName := c.Names[0] + if len(c.Names) > 0 { + // Remove leading slash from container name + if len(containerName) > 0 && containerName[0] == '/' { + containerName = containerName[1:] + } + } - // if err := cli.ContainerStop(ctx, "educates-control-plane", &timeout); err != nil { - // return errors.Wrapf(err, "failed to stop cluster") - // } + if err := cli.ContainerStop(ctx, c.ID, container.StopOptions{Timeout: &timeout}); err != nil { + return errors.Wrapf(err, "failed to stop container %s", containerName) + } + fmt.Printf(" Stopped %s\n", containerName) + } return nil } @@ -184,16 +204,42 @@ func (o *KindClusterConfig) StartCluster() error { return errors.Wrap(err, "unable to create docker client") } - _, err = cli.ContainerInspect(ctx, "educates-control-plane") + // Get all kind node containers for the educates cluster + nodeFilters := filters.NewArgs() + nodeFilters.Add("label", fmt.Sprintf("io.x-k8s.kind.cluster=%s", constants.EducatesClusterName)) + containers, err := cli.ContainerList(ctx, container.ListOptions{ + All: true, // Include stopped containers + Filters: nodeFilters, + }) if err != nil { - return errors.Wrap(err, "no container for Educates cluster") + return errors.Wrap(err, "failed to list kind node containers") + } + + if len(containers) == 0 { + return errors.New("no containers found for Educates cluster") } fmt.Println("Starting cluster educates ...") - if err := cli.ContainerStart(ctx, "educates-control-plane", container.StartOptions{}); err != nil { - return errors.Wrapf(err, "failed to start cluster") + // Start all containers (control-plane and workers) + for _, c := range containers { + containerName := c.Names[0] + if len(c.Names) > 0 { + // Remove leading slash from container name + if len(containerName) > 0 && containerName[0] == '/' { + containerName = containerName[1:] + } + } + + if c.State != "running" { + if err := cli.ContainerStart(ctx, c.ID, container.StartOptions{}); err != nil { + return errors.Wrapf(err, "failed to start container %s", containerName) + } + fmt.Printf(" Started %s\n", containerName) + } else { + fmt.Printf(" %s already running\n", containerName) + } } return nil @@ -215,20 +261,115 @@ func (o *KindClusterConfig) ClusterStatus() error { return errors.Wrap(err, "unable to create docker client") } - containerJSON, err := cli.ContainerInspect(ctx, "educates-control-plane") + // Get all kind node containers for the educates cluster + nodeFilters := filters.NewArgs() + nodeFilters.Add("label", fmt.Sprintf("io.x-k8s.kind.cluster=%s", constants.EducatesClusterName)) + containers, err := cli.ContainerList(ctx, container.ListOptions{ + All: true, + Filters: nodeFilters, + }) if err != nil { - return errors.Wrap(err, "no container for Educates cluster") + return errors.Wrap(err, "failed to list kind node containers") + } + + if len(containers) == 0 { + return errors.New("no containers found for Educates cluster") } - if containerJSON.State.Running { + // Check if all containers are running + allRunning := true + for _, c := range containers { + if c.State != "running" { + allRunning = false + break + } + } + + if allRunning { fmt.Println("Educates cluster is Running") - // if ip, err := config.HostIP(); err == nil { - // fmt.Println(" Cluster IP: ", ip) - // } } else { - fmt.Println("Educates cluster is NOT Running") + fmt.Println("Educates cluster is NOT Running (some containers stopped)") + return nil } + // Get Kubernetes client to query nodes + k8sClient, err := o.Config.GetClient() + if err != nil { + fmt.Println(" Warning: Unable to connect to Kubernetes API") + return nil + } + + // List nodes from Kubernetes API + nodes, err := k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + fmt.Println(" Warning: Unable to list nodes from Kubernetes") + return nil + } + + var formattedData [][]string + + for _, node := range nodes.Items { + var customLabelsData []string + var taintsData []string + // Determine role + role := "worker" + if _, ok := node.Labels["node-role.kubernetes.io/control-plane"]; ok { + role = "control-plane" + } else if _, ok := node.Labels["node-role.kubernetes.io/master"]; ok { + role = "control-plane" + } + + // Get status + status := "Unknown" + for _, condition := range node.Status.Conditions { + if condition.Type == corev1.NodeReady { + if condition.Status == corev1.ConditionTrue { + status = "Ready" + } else { + status = "NotReady" + } + break + } + } + + // Get version + version := node.Status.NodeInfo.KubeletVersion + + // Show custom labels (exclude system labels) + customLabels := make(map[string]string) + for k, v := range node.Labels { + if !strings.HasPrefix(k, "node-role.kubernetes.io/") && + !strings.HasPrefix(k, "kubernetes.io/") && + !strings.HasPrefix(k, "beta.kubernetes.io/"){ + // && k != "ingress-ready" { + customLabels[k] = v + } + } + + if len(customLabels) > 0 { + for k, v := range customLabels { + customLabelsData = append(customLabelsData, fmt.Sprintf("%s=%s", k, v)) + } + } + + // Show taints + if len(node.Spec.Taints) > 0 { + for _, taint := range node.Spec.Taints { + if taint.Value != "" { + taintsData = append(taintsData, fmt.Sprintf("%s=%s:%s", taint.Key, taint.Value, taint.Effect)) + } else { + taintsData = append(taintsData, fmt.Sprintf("%s:%s", taint.Key, taint.Effect)) + } + } + } + formattedData = append(formattedData, []string{node.Name, role, status, version, strings.Join(customLabelsData, ", "), strings.Join(taintsData, ", ")}) + } + + fmt.Println(utils.PrintTable( + []string{"NODE", "ROLE", "STATUS", "VERSION", "LABELS", "TAINTS"}, + formattedData, + )) + return nil } diff --git a/client-programs/pkg/cluster/kindclusterconfig.yaml.tpl b/client-programs/pkg/cluster/kindclusterconfig.yaml.tpl index fecad8584..840ba626a 100644 --- a/client-programs/pkg/cluster/kindclusterconfig.yaml.tpl +++ b/client-programs/pkg/cluster/kindclusterconfig.yaml.tpl @@ -21,6 +21,58 @@ networking: {{- end }} {{- end }} nodes: +{{- if .LocalKindCluster.Nodes }} +{{- range .LocalKindCluster.Nodes }} +- role: {{ .Role }} +{{- if or .Labels .Taints (eq .Role "control-plane") }} + kubeadmConfigPatches: +{{- if eq .Role "control-plane" }} + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: +{{- if .Labels }} + node-labels: "{{- range $key, $value := .Labels }}{{ $key }}={{ $value }},{{- end }}ingress-ready=true" +{{- else }} + node-labels: "ingress-ready=true" +{{- end }} +{{- else }} + - | + kind: JoinConfiguration + nodeRegistration: + kubeletExtraArgs: +{{- if .Labels }} + node-labels: "{{- $first := true }}{{- range $key, $value := .Labels }}{{- if not $first }},{{- end }}{{ $key }}={{ $value }}{{- $first = false }}{{- end }}" +{{- end }} +{{- if .Taints }} + register-with-taints: "{{- range $i, $taint := .Taints }}{{- if $i }},{{- end }}{{ $taint.Key }}={{- if $taint.Value }}{{ $taint.Value }}{{- end }}:{{ $taint.Effect }}{{- end }}" +{{- end }} +{{- end }} +{{- end }} +{{- if eq .Role "control-plane" }} + extraPortMappings: + - containerPort: 80 +{{- if $.LocalKindCluster.ListenAddress }} + listenAddress: {{ $.LocalKindCluster.ListenAddress }} +{{- end }} + hostPort: 80 + protocol: TCP + - containerPort: 443 +{{- if $.LocalKindCluster.ListenAddress }} + listenAddress: {{ $.LocalKindCluster.ListenAddress }} +{{- end }} + hostPort: 443 + protocol: TCP +{{- if $.LocalKindCluster.VolumeMounts }} + extraMounts: +{{- range $.LocalKindCluster.VolumeMounts }} + - hostPath: {{ .HostPath }} + containerPath: {{ .ContainerPath }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- else }} - role: control-plane kubeadmConfigPatches: - | @@ -39,24 +91,25 @@ nodes: {{- end }} extraPortMappings: - containerPort: 80 - {{- if .LocalKindCluster.ListenAddress }} +{{- if .LocalKindCluster.ListenAddress }} listenAddress: {{ .LocalKindCluster.ListenAddress }} - {{- end }} +{{- end }} hostPort: 80 protocol: TCP - containerPort: 443 - {{- if .LocalKindCluster.ListenAddress }} +{{- if .LocalKindCluster.ListenAddress }} listenAddress: {{ .LocalKindCluster.ListenAddress }} - {{- end }} +{{- end }} hostPort: 443 protocol: TCP - {{- if .LocalKindCluster.VolumeMounts }} +{{- if .LocalKindCluster.VolumeMounts }} extraMounts: - {{- range .LocalKindCluster.VolumeMounts }} +{{- range .LocalKindCluster.VolumeMounts }} - hostPath: {{ .HostPath }} containerPath: {{ .ContainerPath }} - {{- end }} - {{- end }} +{{- end }} +{{- end }} +{{- end }} containerdConfigPatches: - |- [plugins."io.containerd.grpc.v1.cri".registry] diff --git a/client-programs/pkg/cmd/admin_diagnostics_analyze_cmd.go b/client-programs/pkg/cmd/admin_diagnostics_analyze_cmd.go index 62dc7d21a..e4a34e85d 100644 --- a/client-programs/pkg/cmd/admin_diagnostics_analyze_cmd.go +++ b/client-programs/pkg/cmd/admin_diagnostics_analyze_cmd.go @@ -11,6 +11,11 @@ type AdminDiagnosticsAnalyzeOptions struct { Dir string } +const adminDiagnosticsAnalyzeExample = ` + # Analyze diagnostic information for current Educates cluster in current directory + educates admin diagnostics analyze --file ./diagnostics.tar.gz +` + func (o *AdminDiagnosticsAnalyzeOptions) Run() error { // clusterConfig := cluster.NewClusterConfig(o.Kubeconfig, "") @@ -31,6 +36,7 @@ func (p *ProjectInfo) NewAdminDiagnosticsAnalyzeCmd() *cobra.Command { Use: "analyze", Short: "Analyze diagnostic information for an Educates cluster", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: adminDiagnosticsAnalyzeExample, } c.Flags().StringVar( @@ -47,7 +53,7 @@ func (p *ProjectInfo) NewAdminDiagnosticsAnalyzeCmd() *cobra.Command { "Path to the directory where the diagnostics files are located", ) - // c.MarkFlagRequired("dest") + //c.MarkFlagRequired("file") return c } diff --git a/client-programs/pkg/cmd/admin_diagnostics_collect_cmd.go b/client-programs/pkg/cmd/admin_diagnostics_collect_cmd.go index 98f202175..cc29a780a 100644 --- a/client-programs/pkg/cmd/admin_diagnostics_collect_cmd.go +++ b/client-programs/pkg/cmd/admin_diagnostics_collect_cmd.go @@ -4,10 +4,10 @@ import ( "os" "path/filepath" - "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" "github.com/educates/educates-training-platform/client-programs/pkg/diagnostics" + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" ) type AdminDiagnosticsCollectOptions struct { @@ -16,6 +16,20 @@ type AdminDiagnosticsCollectOptions struct { Verbose bool } +const adminDiagnosticsCollectExample = ` + # Collect diagnostic information for current Educates cluster in current directory + educates admin diagnostics collect + + # Collect diagnostic information ffor current Educates cluster in current directory with verbose output + educates admin diagnostics collect --verbose + + # Collect diagnostic information for an Educates cluster and save to a specific directory + educates admin diagnostics collect --dest ./diagnostics + + # Collect diagnostic information for a specific Educates Cluster in current directory + educates admin diagnostics collect --kubeconfig /path/to/kubeconfig --context my-cluster +` + func (o *AdminDiagnosticsCollectOptions) Run() error { clusterConfig := cluster.NewClusterConfig(o.Kubeconfig, o.Context) @@ -36,6 +50,7 @@ func (p *ProjectInfo) NewAdminDiagnosticsCollectCmd() *cobra.Command { Use: "collect", Short: "Collect diagnostic information for an Educates cluster", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: adminDiagnosticsCollectExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/admin_lookup_kubeconfig_cmd.go b/client-programs/pkg/cmd/admin_lookup_kubeconfig_cmd.go index 3f9c5eace..ec2b5bbf9 100644 --- a/client-programs/pkg/cmd/admin_lookup_kubeconfig_cmd.go +++ b/client-programs/pkg/cmd/admin_lookup_kubeconfig_cmd.go @@ -1,15 +1,12 @@ package cmd import ( - "context" - "encoding/base64" "fmt" - "io/ioutil" + "os" + "github.com/educates/educates-training-platform/client-programs/pkg/lookup" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type LookupConfigOptions struct { @@ -17,85 +14,16 @@ type LookupConfigOptions struct { OutputPath string } -func (o *LookupConfigOptions) Run() error { - var err error +const adminLookupKubeconfigExample = ` + # Fetch kubeconfig for lookup service remote access + educates admin lookup kubeconfig - clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context) - if err != nil { - return err - } - - client, err := clusterConfig.GetClient() - - if err != nil { - return err - } - - // We need to fetch the secret called "remote-access-token" from the - // "educates" namespace. This contains a Kubernetes access token secret - // giving access to just the Educates custom resources. - - secretsClient := client.CoreV1().Secrets("educates") - - secret, err := secretsClient.Get(context.TODO(), "remote-access-token", metav1.GetOptions{}) - - if err != nil { - return errors.Wrapf(err, "unable to fetch remote-access secret") - } - - // Within the secret are data fields for "ca.crt" and "token". We need to - // extract these and use them to create a kubeconfig file. Note that there - // is no "server" property in the secret, so when constructing the kubeconfig - // we need to use the server from the same cluster as we are requesting the - // secret from. - - caCrt := secret.Data["ca.crt"] - token := secret.Data["token"] + # Fetch kubeconfig for lookup service remote access and save to a specific file + educates admin lookup kubeconfig --output ./lookup-kubeconfig.yaml - // Get the server from the client for Kubernetes cluster access. - - serverScheme := client.CoreV1().RESTClient().Get().URL().Scheme - serverHost := client.CoreV1().RESTClient().Get().URL().Host - - serverUrl := fmt.Sprintf("%s://%s", serverScheme, serverHost) - - // Construct the kubeconfig file. We need to base64 encode the ca.crt file - // as it is a binary file. - - kubeconfig := fmt.Sprintf(`apiVersion: v1 -kind: Config -clusters: -- name: training-platform - cluster: - server: %s - certificate-authority-data: %s -contexts: -- name: training-platform - context: - cluster: training-platform - user: remote-access -current-context: training-platform -users: -- name: remote-access - user: - token: %s -`, serverUrl, base64.StdEncoding.EncodeToString(caCrt), token) - - // Write out the kubeconfig to the output path if provided, otherwise - // print it to stdout. - - if o.OutputPath != "" { - err = ioutil.WriteFile(o.OutputPath, []byte(kubeconfig), 0644) - - if err != nil { - return errors.Wrapf(err, "unable to write kubeconfig to %s", o.OutputPath) - } - } else { - fmt.Print(kubeconfig) - } - - return nil -} + # Fetch kubeconfig for lookup service remote access for a specific cluster + educates admin lookup kubeconfig --kubeconfig /path/to/kubeconfig --context my-cluster +` func (p *ProjectInfo) NewAdminLookupKubeconfigCmd() *cobra.Command { var o LookupConfigOptions @@ -105,8 +33,27 @@ func (p *ProjectInfo) NewAdminLookupKubeconfigCmd() *cobra.Command { Use: "kubeconfig", Short: "Fetch kubeconfig for lookup service remote access", RunE: func(cmd *cobra.Command, _ []string) error { - return o.Run() + config := lookup.LookupConfig{ + Kubeconfig: o.Kubeconfig, + Context: o.Context, + } + kubeconfig, err := lookup.NewLookupService().RemoteAccessKubeconfig(&config) + if err != nil { + return err + } + if o.OutputPath != "" { + err = os.WriteFile(o.OutputPath, []byte(kubeconfig), 0644) + + if err != nil { + return errors.Wrapf(err, "unable to write kubeconfig to %s", o.OutputPath) + } + } else { + fmt.Print(kubeconfig) + } + + return nil }, + Example: adminLookupKubeconfigExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/admin_platform_config_cmd.go b/client-programs/pkg/cmd/admin_platform_config_cmd.go index 5e7d2b490..b02a5c1bd 100644 --- a/client-programs/pkg/cmd/admin_platform_config_cmd.go +++ b/client-programs/pkg/cmd/admin_platform_config_cmd.go @@ -9,7 +9,7 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/installer" ) -var ( +const ( adminPlatformConfigExample = ` # Show configuration config for local deployment educates admin platform config --local-config @@ -18,7 +18,7 @@ var ( educates admin platform config --config config.yaml # Get configuration used to deploy to the current cluster - educates admin platform config --from-cluster + educates admin platform config --from-cluster educates admin platform config --from-cluster --kubeconfig /path/to/kubeconfig --context my-cluster # Get configuration config with different domain (to make copies of the config) diff --git a/client-programs/pkg/cmd/admin_platform_delete_cmd.go b/client-programs/pkg/cmd/admin_platform_delete_cmd.go index 6dfdb03a3..a141d2404 100644 --- a/client-programs/pkg/cmd/admin_platform_delete_cmd.go +++ b/client-programs/pkg/cmd/admin_platform_delete_cmd.go @@ -16,6 +16,14 @@ type PlatformDeleteOptions struct { Verbose bool } +const adminPlatformDeleteExample = ` + # Delete Educates and related cluster services from your cluster + educates admin platform delete + + # Delete Educates and related cluster services from your cluster for a specific cluster + educates admin platform delete --kubeconfig /path/to/kubeconfig --context my-cluster +` + func (o *PlatformDeleteOptions) Run() error { fullConfig := config.NewDefaultInstallationConfig() @@ -44,6 +52,7 @@ func (p *ProjectInfo) NewAdminPlatformDeleteCmd() *cobra.Command { RunE: func(cmd *cobra.Command, _ []string) error { return o.Run() }, + Example: adminPlatformDeleteExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/admin_platform_deploy_cmd.go b/client-programs/pkg/cmd/admin_platform_deploy_cmd.go index 51d4bf15f..f3dcf925c 100644 --- a/client-programs/pkg/cmd/admin_platform_deploy_cmd.go +++ b/client-programs/pkg/cmd/admin_platform_deploy_cmd.go @@ -12,7 +12,7 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/secrets" ) -var ( +const ( adminPlatformDeployExample = ` # Deploy educates platform educates admin platform deploy --config config.yaml @@ -109,7 +109,7 @@ func (o *PlatformDeployOptions) Run() error { } } - fmt.Println("\nEducates has been installed succesfully") + fmt.Println("\n🎓 Educates has been installed succesfully") return nil } diff --git a/client-programs/pkg/cmd/admin_platform_values_cmd.go b/client-programs/pkg/cmd/admin_platform_values_cmd.go index 95f57bde5..20ebe5c1b 100644 --- a/client-programs/pkg/cmd/admin_platform_values_cmd.go +++ b/client-programs/pkg/cmd/admin_platform_values_cmd.go @@ -10,7 +10,7 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/installer" ) -var ( +const ( adminPlatformValuesExample = ` # Show configuration values for local deployment educates admin platform values --local-config @@ -19,7 +19,7 @@ var ( educates admin platform values --config config.yaml # Get configuration used to deploy to the current cluster - educates admin platform values --from-cluster + educates admin platform values --from-cluster educates admin platform values --from-cluster --kubeconfig /path/to/kubeconfig --context my-cluster # Get configuration values using locally built educates package (version latest does the same and skips image resolution) diff --git a/client-programs/pkg/cmd/cluster_portal_create_cmd.go b/client-programs/pkg/cmd/cluster_portal_create_cmd.go index 393191c1c..61c54f468 100644 --- a/client-programs/pkg/cmd/cluster_portal_create_cmd.go +++ b/client-programs/pkg/cmd/cluster_portal_create_cmd.go @@ -1,16 +1,11 @@ package cmd import ( - "context" - "strings" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/portal" "github.com/pkg/errors" "github.com/spf13/cobra" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/dynamic" ) type ClusterConfigViewOptions struct { @@ -25,13 +20,26 @@ type ClusterConfigViewOptions struct { Labels []string } +const clusterPortalCreateExample = ` +# Create TrainingPortal in Educates cluster with default name +educates cluster portal create + +# Create TrainingPortal in Educates cluster with specific name +educates cluster portal create --portal=my-portal + +# Create TrainingPortal in Educates cluster with specific name and capacity and theme +educates cluster portal create --portal=my-portal --capacity=10 --theme-name=my-theme + +# Create given TrainingPortal in given Educates cluster +educates cluster portal create --portal=my-portal --kubeconfig ~/.kube/config --context=my-context +` + func (o *ClusterConfigViewOptions) Run(isPasswordSet bool) error { var err error // Ensure have portal name. - if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context) @@ -46,9 +54,21 @@ func (o *ClusterConfigViewOptions) Run(isPasswordSet bool) error { return errors.Wrapf(err, "unable to create Kubernetes client") } - // Update the training portal, creating it if necessary. + config := portal.TrainingPortalCreateConfig{ + Portal: o.Portal, + Hostname: o.Hostname, + Repository: o.Repository, + Capacity: o.Capacity, + Password: o.Password, + IsPasswordSet: isPasswordSet, + ThemeName: o.ThemeName, + CookieDomain: o.CookieDomain, + Labels: o.Labels, + } + + manager := portal.NewPortalManager(dynamicClient) - err = createTrainingPortal(dynamicClient, o.Portal, o.Hostname, o.Repository, o.Capacity, o.Password, isPasswordSet, o.ThemeName, o.CookieDomain, o.Labels) + err = manager.CreateTrainingPortal(&config) if err != nil { return err @@ -69,6 +89,7 @@ func (p *ProjectInfo) NewClusterPortalCreateCmd() *cobra.Command { return o.Run(isPasswordSet) }, + Example: clusterPortalCreateExample, } c.Flags().StringVar( @@ -87,7 +108,7 @@ func (p *ProjectInfo) NewClusterPortalCreateCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) c.Flags().StringVar( @@ -105,7 +126,7 @@ func (p *ProjectInfo) NewClusterPortalCreateCmd() *cobra.Command { c.Flags().UintVar( &o.Capacity, "capacity", - 5, + constants.DefaultPortalCapacity, "maximum number of current sessions for the training portal", ) c.Flags().StringVar( @@ -136,122 +157,3 @@ func (p *ProjectInfo) NewClusterPortalCreateCmd() *cobra.Command { return c } - -func createTrainingPortal(client dynamic.Interface, portal string, hostname string, registry string, capacity uint, password string, isPasswordSet bool, themeName string, cookieDomain string, labels []string) error { - trainingPortalClient := client.Resource(trainingPortalResource) - - _, err := trainingPortalClient.Get(context.TODO(), portal, metav1.GetOptions{}) - - if err != nil { - if !k8serrors.IsNotFound(err) { - return errors.Wrap(err, "unable to query training portal") - } - } else { - return errors.New("training portal already exists") - } - - trainingPortal := &unstructured.Unstructured{} - - if !isPasswordSet { - password = randomPassword(12) - } - - type LabelDetails struct { - Name string `json:"name"` - Value string `json:"value"` - } - - var labelOverrides []LabelDetails - - for _, value := range labels { - parts := strings.SplitN(value, "=", 2) - labelOverrides = append(labelOverrides, LabelDetails{ - Name: parts[0], - Value: parts[1], - }) - } - - type RegistryDetails struct { - Host string `json:"host"` - Namespace string `json:"namespace"` - } - - registryHost := "" - registryNamespace := "" - - if registry != "" { - parts := strings.SplitN(registry, "/", 2) - - registryHost = parts[0] - - if len(parts) > 1 { - registryNamespace = parts[1] - } - - } - - trainingPortal.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "training.educates.dev/v1beta1", - "kind": "TrainingPortal", - "metadata": map[string]interface{}{ - "name": portal, - }, - "spec": map[string]interface{}{ - "portal": map[string]interface{}{ - "password": password, - "registration": struct { - Type string `json:"type"` - }{ - Type: "anonymous", - }, - "updates": struct { - Workshop bool `json:"workshop"` - }{ - Workshop: true, - }, - "sessions": struct { - Maximum int64 `json:"maximum"` - }{ - Maximum: int64(capacity), - }, - "workshop": map[string]interface{}{ - "defaults": struct { - Reserved int `json:"reserved"` - Registry RegistryDetails `json:"registry"` - }{ - Reserved: 0, - Registry: RegistryDetails{ - Host: registryHost, - Namespace: registryNamespace, - }, - }, - }, - "ingress": struct { - Hostname string `json:"hostname"` - }{ - Hostname: hostname, - }, - "theme": struct { - Name string `json:"name"` - }{ - Name: themeName, - }, - "cookies": struct { - Domain string `json:"domain"` - }{ - Domain: cookieDomain, - }, - "labels": labelOverrides, - }, - "workshops": []interface{}{}, - }, - }) - - _, err = trainingPortalClient.Create(context.TODO(), trainingPortal, metav1.CreateOptions{FieldManager: "educates-cli"}) - - if err != nil { - return errors.Wrapf(err, "unable to create training portal %q in cluster", portal) - } - - return nil -} diff --git a/client-programs/pkg/cmd/cluster_portal_delete_cmd.go b/client-programs/pkg/cmd/cluster_portal_delete_cmd.go index dc94d06cb..24f503dcb 100644 --- a/client-programs/pkg/cmd/cluster_portal_delete_cmd.go +++ b/client-programs/pkg/cmd/cluster_portal_delete_cmd.go @@ -1,13 +1,11 @@ package cmd import ( - "context" - + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/portal" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ClusterPortalDeleteOptions struct { @@ -15,13 +13,24 @@ type ClusterPortalDeleteOptions struct { Portal string } +const clusterPortalDeleteExample = ` +# Delete TrainingPortal from Educates cluster with default name +educates cluster portal delete + +# Delete TrainingPortal from Educates cluster with specific name +educates cluster portal delete --portal=my-portal + +# Delete given TrainingPortal from given Educates cluster +educates cluster portal delete --portal=my-portal --kubeconfig ~/.kube/config --context=my-context +` + func (o *ClusterPortalDeleteOptions) Run() error { var err error // Ensure have portal name. if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context) @@ -36,18 +45,16 @@ func (o *ClusterPortalDeleteOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } - trainingPortalClient := dynamicClient.Resource(trainingPortalResource) - - _, err = trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) - - if k8serrors.IsNotFound(err) { - return errors.New("no portal found") + config := portal.TrainingPortalDeleteConfig{ + Portal: o.Portal, } - err = trainingPortalClient.Delete(context.TODO(), o.Portal, metav1.DeleteOptions{}) + manager := portal.NewPortalManager(dynamicClient) + + err = manager.DeleteTrainingPortal(&config) if err != nil { - return errors.Wrap(err, "unable to delete portal") + return err } return nil @@ -61,6 +68,7 @@ func (p *ProjectInfo) NewClusterPortalDeleteCmd() *cobra.Command { Use: "delete", Short: "Delete portal from Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterPortalDeleteExample, } c.Flags().StringVar( @@ -79,7 +87,7 @@ func (p *ProjectInfo) NewClusterPortalDeleteCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) diff --git a/client-programs/pkg/cmd/cluster_portal_list_cmd.go b/client-programs/pkg/cmd/cluster_portal_list_cmd.go index 70d777e82..94a4e139d 100644 --- a/client-programs/pkg/cmd/cluster_portal_list_cmd.go +++ b/client-programs/pkg/cmd/cluster_portal_list_cmd.go @@ -1,23 +1,26 @@ package cmd import ( - "context" "fmt" - "os" - "text/tabwriter" + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/portal" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) type ClusterPortalListOptions struct { KubeconfigOptions } +const clusterPortalListExample = ` +# List TrainingPortals deployed to Educates cluster +educates cluster portal list + +# List TrainingPortals deployed to Educaets cluster and save to file +educates cluster portal list --kubeconfig ~/.kube/config --context=my-context +` + func (o *ClusterPortalListOptions) Run() error { var err error @@ -33,37 +36,15 @@ func (o *ClusterPortalListOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } - trainingPortalClient := dynamicClient.Resource(trainingPortalResource) + manager := portal.NewPortalManager(dynamicClient) - trainingPortals, err := trainingPortalClient.List(context.TODO(), metav1.ListOptions{}) + list, err := manager.ListTrainingPortals(nil) - if k8serrors.IsNotFound(err) { - fmt.Println("No portals found.") - return nil + if err != nil { + return err } - w := new(tabwriter.Writer) - w.Init(os.Stdout, 8, 8, 3, ' ', 0) - - defer w.Flush() - - fmt.Fprintf(w, "%s\t%s\t%s\n", "NAME", "CAPACITY", "URL") - - for _, item := range trainingPortals.Items { - name := item.GetName() - - sessionsMaximum, propertyExists, err := unstructured.NestedInt64(item.Object, "spec", "portal", "sessions", "maximum") - - var capacity string - - if err == nil && propertyExists { - capacity = fmt.Sprintf("%d", sessionsMaximum) - } - - url, _, _ := unstructured.NestedString(item.Object, "status", "educates", "url") - - fmt.Fprintf(w, "%s\t%s\t%s\n", name, capacity, url) - } + fmt.Println(list) return nil } @@ -76,6 +57,7 @@ func (p *ProjectInfo) NewClusterPortalListCmd() *cobra.Command { Use: "list", Short: "List portals deployed to Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterPortalListExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/cluster_portal_open_cmd.go b/client-programs/pkg/cmd/cluster_portal_open_cmd.go index 82b0321c0..baf5cee04 100644 --- a/client-programs/pkg/cmd/cluster_portal_open_cmd.go +++ b/client-programs/pkg/cmd/cluster_portal_open_cmd.go @@ -1,21 +1,17 @@ package cmd import ( - "context" "fmt" "io" "net/http" - "net/url" - "os/exec" - "runtime" "time" + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/portal" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) type ClusterPortalOpenOptions struct { @@ -24,13 +20,26 @@ type ClusterPortalOpenOptions struct { Portal string } +const clusterPortalOpenExample = ` +# Open TrainingPortal in Educates cluster with default name +educates cluster portal open + +# Open TrainingPortal in Educates cluster with specific name +educates cluster portal open --portal=my-portal + +# Open admin interface of specific TrainingPortal +educates cluster portal open --portal=my-portal --admin + +# Open given TrainingPortal in given Educates cluster +educates cluster portal open --portal=my-portal --kubeconfig ~/.kube/config --context=my-context +` + func (o *ClusterPortalOpenOptions) Run() error { var err error // Ensure have portal name. - if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context) @@ -45,37 +54,20 @@ func (o *ClusterPortalOpenOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } - trainingPortalClient := dynamicClient.Resource(trainingPortalResource) - - trainingPortal, err := trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) - - if k8serrors.IsNotFound(err) { - return errors.New("no workshops deployed") + config := portal.TrainingPortalOpenConfig{ + Portal: o.Portal, + Admin: o.Admin, } - targetUrl, found, _ := unstructured.NestedString(trainingPortal.Object, "status", "educates", "url") - - if !found { - return errors.New("workshops not available") - } + manager := portal.NewPortalManager(dynamicClient) - rootUrl := targetUrl + targetUrl, err := manager.GetTrainingPortalBrowserUrl(&config) - if o.Admin { - targetUrl = targetUrl + "/admin" - } else { - password, _, _ := unstructured.NestedString(trainingPortal.Object, "spec", "portal", "password") - - if password != "" { - values := url.Values{} - values.Add("redirect_url", "/") - values.Add("password", password) - - targetUrl = fmt.Sprintf("%s/workshops/access/?%s", targetUrl, values.Encode()) - } + if err != nil { + return err } - fmt.Printf("Training portal %q.\n", trainingPortal.GetName()) + fmt.Printf("Training portal %q.\n", o.Portal) fmt.Print("Checking training portal is ready.\n") @@ -89,7 +81,7 @@ func (o *ClusterPortalOpenOptions) Run() error { time.Sleep(time.Second) - resp, err := http.Get(rootUrl) + resp, err := http.Get(targetUrl) if err != nil || resp.StatusCode == 503 { continue @@ -105,22 +97,7 @@ func (o *ClusterPortalOpenOptions) Run() error { fmt.Printf("Opening training portal %s.\n", targetUrl) - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", targetUrl).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", targetUrl).Start() - case "darwin": - err = exec.Command("open", targetUrl).Start() - default: - err = fmt.Errorf("unsupported platform") - } - - if err != nil { - return errors.Wrap(err, "unable to open web browser") - } - - return nil + return utils.OpenBrowser(targetUrl) } func (p *ProjectInfo) NewClusterPortalOpenCmd() *cobra.Command { @@ -131,6 +108,7 @@ func (p *ProjectInfo) NewClusterPortalOpenCmd() *cobra.Command { Use: "open", Short: "Browse portal in Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterPortalOpenExample, } c.Flags().StringVar( @@ -155,7 +133,7 @@ func (p *ProjectInfo) NewClusterPortalOpenCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) diff --git a/client-programs/pkg/cmd/cluster_portal_password_cmd.go b/client-programs/pkg/cmd/cluster_portal_password_cmd.go index 964627957..10f51f1b5 100644 --- a/client-programs/pkg/cmd/cluster_portal_password_cmd.go +++ b/client-programs/pkg/cmd/cluster_portal_password_cmd.go @@ -1,17 +1,13 @@ package cmd import ( - "context" "fmt" - "os" - "text/tabwriter" + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/portal" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) type ClusterPortalPasswordOptions struct { @@ -20,13 +16,27 @@ type ClusterPortalPasswordOptions struct { Portal string } +const clusterPortalPasswordExample = ` +# View accesspassword for TrainingPortal in Educates cluster with default name +educates cluster portal password + +# View access password for TrainingPortal in Educates cluster with specific name +educates cluster portal password --portal=my-portal + +# View admin password for TrainingPortal in Educates cluster with default name +educates cluster portal password --admin + +# View access password for given TrainingPortal in given Educates cluster +educates cluster portal password --portal=my-portal --kubeconfig ~/.kube/config --context=my-context --admin +` + func (o *ClusterPortalPasswordOptions) Run() error { var err error // Ensure have portal name. if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context) @@ -41,40 +51,21 @@ func (o *ClusterPortalPasswordOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } - trainingPortalClient := dynamicClient.Resource(trainingPortalResource) - - trainingPortal, err := trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) - - if k8serrors.IsNotFound(err) { - return errors.New("no workshops deployed") + config := portal.TrainingPortalPasswordConfig{ + Portal: o.Portal, + Admin: o.Admin, } - if o.Admin { - username, found, err := unstructured.NestedString(trainingPortal.Object, "status", "educates", "credentials", "admin", "username") + manager := portal.NewPortalManager(dynamicClient) - if err != nil || !found { - return errors.New("unable to access credentials") - } + password, err := manager.GetTrainingPortalPassword(&config) - password, found, err := unstructured.NestedString(trainingPortal.Object, "status", "educates", "credentials", "admin", "password") - - if err != nil || !found { - return errors.New("unable to access credentials") - } - - w := new(tabwriter.Writer) - w.Init(os.Stdout, 8, 8, 3, ' ', 0) - - defer w.Flush() - - fmt.Fprintf(w, "%s\t%s\n", "USERNAME", "PASSWORD") - fmt.Fprintf(w, "%s\t%s\n", username, password) - } else { - password, _, _ := unstructured.NestedString(trainingPortal.Object, "spec", "portal", "password") - - fmt.Println(password) + if err != nil { + return err } + fmt.Println(password) + return nil } @@ -86,6 +77,7 @@ func (p *ProjectInfo) NewClusterPortalPasswordCmd() *cobra.Command { Use: "password", Short: "View portal credentials in Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterPortalPasswordExample, } c.Flags().StringVar( @@ -110,7 +102,7 @@ func (p *ProjectInfo) NewClusterPortalPasswordCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) diff --git a/client-programs/pkg/cmd/cluster_session_extend_cmd.go b/client-programs/pkg/cmd/cluster_session_extend_cmd.go index 921d6da84..5f60b729f 100644 --- a/client-programs/pkg/cmd/cluster_session_extend_cmd.go +++ b/client-programs/pkg/cmd/cluster_session_extend_cmd.go @@ -3,10 +3,11 @@ package cmd import ( "fmt" - "github.com/pkg/errors" - "github.com/spf13/cobra" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - "github.com/educates/educates-training-platform/client-programs/pkg/educatesrestapi" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/sessions" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/spf13/cobra" ) type ClusterSessionExtendOptions struct { @@ -15,6 +16,17 @@ type ClusterSessionExtendOptions struct { Name string } +const clusterSessionExtendExample = ` +# Extend duration of session "my-session" in Kubernetes +educates cluster session extend my-session SESSION_NAME + +# Extend duration of session "my-session" in Kubernetes using a specific portal +educates cluster session extend my-session SESSION_NAME --portal=my-portal + +# Extend duration of session "my-session" in Kubernetes using a specific portal and context +educates cluster session extend my-session SESSION_NAME --portal=my-portal --kubeconfig ~/.kube/config --context=my-context +` + func (o *ClusterSessionExtendOptions) Run() error { var err error @@ -24,27 +36,17 @@ func (o *ClusterSessionExtendOptions) Run() error { return err } - catalogApiRequester := educatesrestapi.NewWorkshopsCatalogRequester( - clusterConfig, - o.Portal, - ) - logout, err := catalogApiRequester.Login() - defer logout() - if err != nil { - return errors.Wrap(err, "failed to login to training portal") - } - - details, err := catalogApiRequester.ExtendWorkshopSession(o.Name) + manager := sessions.NewSessionManager() + result, err := manager.ExtendSession(sessions.ExtendSessionConfig{ + ClusterConfig: clusterConfig, + Portal: o.Portal, + Name: o.Name, + }) if err != nil { return err } - fmt.Println("Started:", details.Started) - fmt.Println("Expires:", details.Expires) - fmt.Println("Expiring:", details.Expiring) - fmt.Println("Countdown:", details.Countdown) - fmt.Println("Extendable:", details.Extendable) - fmt.Println("Status:", details.Status) + fmt.Println(result) return nil } @@ -53,10 +55,16 @@ func (p *ProjectInfo) NewClusterSessionExtendCmd() *cobra.Command { var o ClusterSessionExtendOptions var c = &cobra.Command{ - Args: cobra.ExactArgs(1), + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "session name is required", "NAME") + } + return nil + }, Use: "extend NAME", Short: "Extend duration of session in Kubernetes", RunE: func(_ *cobra.Command, args []string) error { o.Name = args[0]; return o.Run() }, + Example: clusterSessionExtendExample, } c.Flags().StringVar( @@ -75,7 +83,7 @@ func (p *ProjectInfo) NewClusterSessionExtendCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name of the training portal", ) diff --git a/client-programs/pkg/cmd/cluster_session_list_cmd.go b/client-programs/pkg/cmd/cluster_session_list_cmd.go index d34831446..99ce16911 100644 --- a/client-programs/pkg/cmd/cluster_session_list_cmd.go +++ b/client-programs/pkg/cmd/cluster_session_list_cmd.go @@ -1,18 +1,13 @@ package cmd import ( - "context" "fmt" - "os" - "text/tabwriter" + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/sessions" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" ) type ClusterSessionListOptions struct { @@ -21,7 +16,16 @@ type ClusterSessionListOptions struct { Environment string } -var workshopSessionResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopsessions"} +const clusterSessionListExample = ` +# List active Educates sessions in default Educates portal and cluster +educates cluster session list + +# List active Educates sessions using a specific portal +educates cluster session list --portal=my-portal + +# List active Educates sessions in Kubernetes using a specific portal and context +educates cluster session list --portal=my-portal --kubeconfig ~/.kube/config --context=my-context +` func (o *ClusterSessionListOptions) Run() error { var err error @@ -38,59 +42,18 @@ func (o *ClusterSessionListOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } - workshopSessionClient := dynamicClient.Resource(workshopSessionResource) - - workshopSessions, err := workshopSessionClient.List(context.TODO(), metav1.ListOptions{}) - - if k8serrors.IsNotFound(err) { - fmt.Println("No sessions found.") - return nil - } - - var sessions []unstructured.Unstructured - - for _, item := range workshopSessions.Items { - labels := item.GetLabels() - - portal, ok := labels["training.educates.dev/portal.name"] - - if ok && portal == o.Portal { - if o.Environment != "" { - environment, ok := labels["training.educates.dev/environment.name"] - - if ok && environment == o.Environment { - sessions = append(sessions, item) - } - } else { - sessions = append(sessions, item) - } + manager := sessions.NewSessionManager() - } - } - - if len(sessions) == 0 { - fmt.Println("No sessions found.") - return nil + list, err := manager.ListSessions(sessions.ListSessionsConfig{ + Client: dynamicClient, + Portal: o.Portal, + Environment: o.Environment, + }) + if err != nil { + return err } - w := new(tabwriter.Writer) - w.Init(os.Stdout, 8, 8, 3, ' ', 0) - - defer w.Flush() - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "NAME", "PORTAL", "ENVIRONMENT", "STATUS") - - for _, item := range sessions { - name := item.GetName() - labels := item.GetLabels() - - portal := labels["training.educates.dev/portal.name"] - environment := labels["training.educates.dev/environment.name"] - - status, _, _ := unstructured.NestedString(item.Object, "status", "educates", "phase") - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, portal, environment, status) - } + fmt.Println(list) return nil } @@ -103,6 +66,7 @@ func (p *ProjectInfo) NewClusterSessionListCmd() *cobra.Command { Use: "list", Short: "List active sessions in Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterSessionListExample, } c.Flags().StringVar( @@ -121,7 +85,7 @@ func (p *ProjectInfo) NewClusterSessionListCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name of the training portal", ) c.Flags().StringVarP( diff --git a/client-programs/pkg/cmd/cluster_session_status_cmd.go b/client-programs/pkg/cmd/cluster_session_status_cmd.go index 29d00cd5b..eeccd9365 100644 --- a/client-programs/pkg/cmd/cluster_session_status_cmd.go +++ b/client-programs/pkg/cmd/cluster_session_status_cmd.go @@ -3,10 +3,11 @@ package cmd import ( "fmt" - "github.com/pkg/errors" - "github.com/spf13/cobra" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - "github.com/educates/educates-training-platform/client-programs/pkg/educatesrestapi" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/sessions" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/spf13/cobra" ) type ClusterSessionStatusOptions struct { @@ -15,32 +16,33 @@ type ClusterSessionStatusOptions struct { Name string } +const clusterSessionStatusExample = ` +# Get status of Educates session "my-session" in default Educates portal +educates cluster session status my-session + +# Get status of Educates session "my-session" using a specific portal +educates cluster session status my-session --portal=my-portal + +# Get status of Educates session "my-session" using a specific portal and context +educates cluster session status my-session --portal=my-portal --kubeconfig ~/.kube/config --context=my-context +` + func (o *ClusterSessionStatusOptions) Run() error { var err error clusterConfig := cluster.NewClusterConfig(o.Kubeconfig, o.Context) - catalogApiRequester := educatesrestapi.NewWorkshopsCatalogRequester( - clusterConfig, - o.Portal, - ) - logout, err := catalogApiRequester.Login() - defer logout() - if err != nil { - return errors.Wrap(err, "failed to login to training portal") - } - - details, err := catalogApiRequester.GetWorkshopSession(o.Name) + manager := sessions.NewSessionManager() + result, err := manager.SessionStatus(sessions.SessionStatusConfig{ + ClusterConfig: clusterConfig, + Portal: o.Portal, + Name: o.Name, + }) if err != nil { return err } - fmt.Println("Started:", details.Started) - fmt.Println("Expires:", details.Expires) - fmt.Println("Expiring:", details.Expiring) - fmt.Println("Countdown:", details.Countdown) - fmt.Println("Extendable:", details.Extendable) - fmt.Println("Status:", details.Status) + fmt.Println(result) return nil } @@ -49,10 +51,16 @@ func (p *ProjectInfo) NewClusterSessionStatusCmd() *cobra.Command { var o ClusterSessionStatusOptions var c = &cobra.Command{ - Args: cobra.ExactArgs(1), + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "session name is required", "NAME") + } + return nil + }, Use: "status NAME", Short: "Output status of session in Kubernetes", RunE: func(_ *cobra.Command, args []string) error { o.Name = args[0]; return o.Run() }, + Example: clusterSessionStatusExample, } c.Flags().StringVar( @@ -71,7 +79,7 @@ func (p *ProjectInfo) NewClusterSessionStatusCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name of the training portal", ) diff --git a/client-programs/pkg/cmd/cluster_session_terminate_cmd.go b/client-programs/pkg/cmd/cluster_session_terminate_cmd.go index cb07e6fe1..c3950e0e0 100644 --- a/client-programs/pkg/cmd/cluster_session_terminate_cmd.go +++ b/client-programs/pkg/cmd/cluster_session_terminate_cmd.go @@ -3,10 +3,11 @@ package cmd import ( "fmt" - "github.com/pkg/errors" - "github.com/spf13/cobra" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - "github.com/educates/educates-training-platform/client-programs/pkg/educatesrestapi" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/sessions" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/spf13/cobra" ) type ClusterSessionTerminateOptions struct { @@ -15,32 +16,33 @@ type ClusterSessionTerminateOptions struct { Name string } +const clusterSessionTerminateExample = ` +# Terminate running Educatessession "my-session" in default Educates portal +educates cluster session terminate my-session + +# Terminate running Educates session "my-session" using a specific portal +educates cluster session terminate my-session --portal=my-portal + +# Terminate running Educates session "my-session" using a specific portal and context +educates cluster session terminate my-session --portal=my-portal --kubeconfig ~/.kube/config --context=my-context +` + func (o *ClusterSessionTerminateOptions) Run() error { var err error clusterConfig := cluster.NewClusterConfig(o.Kubeconfig, o.Context) - catalogApiRequester := educatesrestapi.NewWorkshopsCatalogRequester( - clusterConfig, - o.Portal, - ) - logout, err := catalogApiRequester.Login() - defer logout() - if err != nil { - return errors.Wrap(err, "failed to login to training portal") - } - - details, err := catalogApiRequester.TerminateWorkshopSession(o.Name) + manager := sessions.NewSessionManager() + result, err := manager.TerminateSession(sessions.TerminateSessionConfig{ + ClusterConfig: clusterConfig, + Portal: o.Portal, + Name: o.Name, + }) if err != nil { return err } - fmt.Println("Started:", details.Started) - fmt.Println("Expires:", details.Expires) - fmt.Println("Expiring:", details.Expiring) - fmt.Println("Countdown:", details.Countdown) - fmt.Println("Extendable:", details.Extendable) - fmt.Println("Status:", details.Status) + fmt.Println(result) return nil } @@ -49,11 +51,17 @@ func (p *ProjectInfo) NewClusterSessionTerminateCmd() *cobra.Command { var o ClusterSessionTerminateOptions var c = &cobra.Command{ - Args: cobra.ExactArgs(1), + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "session name is required", "NAME") + } + return nil + }, Use: "delete NAME", Aliases: []string{"terminate"}, Short: "Terminate running session in Kubernetes", RunE: func(_ *cobra.Command, args []string) error { o.Name = args[0]; return o.Run() }, + Example: clusterSessionTerminateExample, } c.Flags().StringVar( @@ -72,7 +80,7 @@ func (p *ProjectInfo) NewClusterSessionTerminateCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name of the training portal", ) diff --git a/client-programs/pkg/cmd/cluster_workshop_delete_cmd.go b/client-programs/pkg/cmd/cluster_workshop_delete_cmd.go index 8bbe26e2c..9daf5f8ce 100644 --- a/client-programs/pkg/cmd/cluster_workshop_delete_cmd.go +++ b/client-programs/pkg/cmd/cluster_workshop_delete_cmd.go @@ -1,18 +1,30 @@ package cmd import ( - "context" - yttcmd "carvel.dev/ytt/pkg/cmd/template" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" "github.com/pkg/errors" "github.com/spf13/cobra" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/dynamic" ) +const ( + clusterWorkshopDeleteExample = ` + # Delete Educates workshop from cluster in current workshop directory and using default workshop file + educates cluster workshop delete + + # Delete Educates workshop from cluster from specific portal + educates cluster workshop delete --portal my-portal + + # Delete Educates workshop from cluster defined with custom path and workshop file + educates cluster workshop delete --path ./workshop --workshop-file custom-workshop.yaml + + # Delete Educates workshop from alternate cluster + educates cluster workshop delete --kubeconfig ~/.kube/config --context=my-context +` +) type ClusterWorkshopDeleteOptions struct { KubeconfigOptions Name string @@ -32,7 +44,7 @@ func (o *ClusterWorkshopDeleteOptions) Run() error { // Ensure have portal name. if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } if name == "" { @@ -52,7 +64,16 @@ func (o *ClusterWorkshopDeleteOptions) Run() error { var workshop *unstructured.Unstructured - if workshop, err = loadWorkshopDefinition(o.Name, path, o.Portal, o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + definitionConfig := workshops.WorkshopDefinitionConfig{ + Name: o.Name, + Path: path, + Portal: o.Portal, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + + if workshop, err = workshops.LoadWorkshopDefinition(&definitionConfig); err != nil { return err } @@ -71,9 +92,15 @@ func (o *ClusterWorkshopDeleteOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } - // Delete the deployed workshop from the Kubernetes cluster. + manager := workshops.NewWorkshopManager(dynamicClient) - err = deleteWorkshopResource(dynamicClient, name, o.Alias, o.Portal) + // Delete the deployed workshop from the Kubernetes cluster. + deleteConfig := workshops.DeleteWorkshopResourceConfig{ + Name: name, + Alias: o.Alias, + Portal: o.Portal, + } + err = manager.DeleteWorkshopResource(&deleteConfig) if err != nil { return err @@ -90,6 +117,7 @@ func (p *ProjectInfo) NewClusterWorkshopDeleteCmd() *cobra.Command { Use: "delete", Short: "Delete workshop from Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterWorkshopDeleteExample, } c.Flags().StringVarP( @@ -129,7 +157,7 @@ func (p *ProjectInfo) NewClusterWorkshopDeleteCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) @@ -187,47 +215,3 @@ func (p *ProjectInfo) NewClusterWorkshopDeleteCmd() *cobra.Command { return c } - -func deleteWorkshopResource(client dynamic.Interface, name string, alias string, portal string) error { - trainingPortalClient := client.Resource(trainingPortalResource) - - trainingPortal, err := trainingPortalClient.Get(context.TODO(), portal, metav1.GetOptions{}) - - if k8serrors.IsNotFound(err) { - return nil - } - - workshops, _, err := unstructured.NestedSlice(trainingPortal.Object, "spec", "workshops") - - if err != nil { - return errors.Wrap(err, "unable to retrieve workshops from training portal") - } - - var found = false - - var updatedWorkshops []interface{} - - for _, item := range workshops { - object := item.(map[string]interface{}) - - if object["name"] != name || object["alias"] != alias { - updatedWorkshops = append(updatedWorkshops, object) - } else { - found = true - } - } - - if !found { - return nil - } - - unstructured.SetNestedSlice(trainingPortal.Object, updatedWorkshops, "spec", "workshops") - - _, err = trainingPortalClient.Update(context.TODO(), trainingPortal, metav1.UpdateOptions{FieldManager: "educates-cli"}) - - if err != nil { - return errors.Wrapf(err, "unable to update training portal %q in cluster", portal) - } - - return nil -} diff --git a/client-programs/pkg/cmd/cluster_workshop_deploy_cmd.go b/client-programs/pkg/cmd/cluster_workshop_deploy_cmd.go index f14e5f2b4..e728ee812 100644 --- a/client-programs/pkg/cmd/cluster_workshop_deploy_cmd.go +++ b/client-programs/pkg/cmd/cluster_workshop_deploy_cmd.go @@ -1,28 +1,28 @@ package cmd import ( - "context" - "encoding/json" "fmt" - "hash/maphash" - "io" - "math/rand" - "net/http" - "net/url" - "os/exec" - "runtime" - "strings" - "time" yttcmd "carvel.dev/ytt/pkg/cmd/template" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" "github.com/pkg/errors" "github.com/spf13/cobra" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/dynamic" +) + +const ( + clusterWorkshopDeployExample = ` + # Deploy Educates workshop to cluster in current workshop directory and using default workshop file + educates cluster workshop deploy + + # Deploy Educates workshop to cluster with custom workshop name and alias and custom workshop settings + educates cluster workshop deploy --name my-workshop --alias my-workshop -initial 10 -reserved 5 -expires 1h -overtime 10m -deadline 2h -orphaned 10m -overdue 10s + + # Deploy Educates workshop to cluster with custom workshop file + educates cluster workshop deploy --path ./workshop --workshop-file custom-workshop.yaml +` ) type ClusterWorkshopDeployOptions struct { @@ -57,7 +57,7 @@ func (o *ClusterWorkshopDeployOptions) Run() error { // Ensure have portal name. if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } // If path not provided assume the current working directory. When loading @@ -74,7 +74,16 @@ func (o *ClusterWorkshopDeployOptions) Run() error { var workshop *unstructured.Unstructured - if workshop, err = loadWorkshopDefinition(o.Name, path, o.Portal, o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + loadConfig := workshops.WorkshopDefinitionConfig{ + Name: o.Name, + Path: path, + Portal: o.Portal, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + + if workshop, err = workshops.LoadWorkshopDefinition(&loadConfig); err != nil { return err } @@ -90,9 +99,14 @@ func (o *ClusterWorkshopDeployOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } + manager := workshops.NewWorkshopManager(dynamicClient) + // Update the workshop resource in the Kubernetes cluster. + updateConfig := workshops.UpdateWorkshopResourceConfig{ + Workshop: workshop, + } - err = updateWorkshopResource(dynamicClient, workshop) + err = manager.UpdateWorkshopResource(&updateConfig) if err != nil { return err @@ -102,7 +116,33 @@ func (o *ClusterWorkshopDeployOptions) Run() error { // Update the training portal, creating it if necessary. - err = deployWorkshopResource(dynamicClient, workshop, o.Alias, o.Portal, o.Capacity, o.Reserved, o.Initial, o.Expires, o.Overtime, o.Deadline, o.Orphaned, o.Overdue, o.Refresh, o.Repository, o.Environ, o.Labels, o.OpenBrowser) + deployConfig := workshops.DeployWorkshopConfig{ + Workshop: workshop, + Alias: o.Alias, + Portal: o.Portal, + Capacity: o.Capacity, + Reserved: o.Reserved, + Initial: o.Initial, + Expires: o.Expires, + Overtime: o.Overtime, + Deadline: o.Deadline, + Orphaned: o.Orphaned, + Overdue: o.Overdue, + Refresh: o.Refresh, + Registry: o.Repository, + Environ: o.Environ, + Labels: o.Labels, + OpenBrowser: o.OpenBrowser, + } + err = manager.DeployWorkshopResource(&deployConfig) + + // TODO: Move open browser logic to separate function and extract logic here + // if o.OpenBrowser { + // err = manager.OpenBrowser(&deployConfig) + // if err != nil { + // return err + // } + // } if err != nil { return err @@ -119,6 +159,7 @@ func (p *ProjectInfo) NewClusterWorkshopDeployCmd() *cobra.Command { Use: "deploy", Short: "Deploy workshop to Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterWorkshopDeployExample, } c.Flags().StringVarP( @@ -158,7 +199,7 @@ func (p *ProjectInfo) NewClusterWorkshopDeployCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) c.Flags().UintVar( @@ -298,363 +339,3 @@ func (p *ProjectInfo) NewClusterWorkshopDeployCmd() *cobra.Command { return c } - -var trainingPortalResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "trainingportals"} - -func deployWorkshopResource(client dynamic.Interface, workshop *unstructured.Unstructured, alias string, portal string, capacity uint, reserved uint, initial uint, expires string, overtime string, deadline string, orphaned string, overdue string, refresh string, registry string, environ []string, labels []string, openBrowser bool) error { - trainingPortalClient := client.Resource(trainingPortalResource) - - trainingPortal, err := trainingPortalClient.Get(context.TODO(), portal, metav1.GetOptions{}) - - var trainingPortalExists = true - - if k8serrors.IsNotFound(err) { - trainingPortalExists = false - - trainingPortal = &unstructured.Unstructured{} - - trainingPortal.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": "training.educates.dev/v1beta1", - "kind": "TrainingPortal", - "metadata": map[string]interface{}{ - "name": portal, - }, - "spec": map[string]interface{}{ - "portal": map[string]interface{}{ - "password": randomPassword(12), - "registration": struct { - Type string `json:"type"` - }{ - Type: "anonymous", - }, - "updates": struct { - Workshop bool `json:"workshop"` - }{ - Workshop: true, - }, - "sessions": struct { - Maximum int64 `json:"maximum"` - }{ - Maximum: 5, - }, - "workshop": map[string]interface{}{ - "defaults": struct { - Reserved int `json:"reserved"` - }{ - Reserved: 0, - }, - }, - }, - "workshops": []interface{}{}, - }, - }) - } - - workshops, _, err := unstructured.NestedSlice(trainingPortal.Object, "spec", "workshops") - - if err != nil { - return errors.Wrap(err, "unable to retrieve workshops from training portal") - } - - var updatedWorkshops []interface{} - - if expires == "" { - duration, propertyExists, err := unstructured.NestedString(workshop.Object, "spec", "duration") - - if err != nil || !propertyExists { - expires = "60m" - } else { - expires = duration - } - } - - type EnvironDetails struct { - Name string `json:"name"` - Value string `json:"value"` - } - - var environVariables []EnvironDetails - - for _, value := range environ { - parts := strings.SplitN(value, "=", 2) - environVariables = append(environVariables, EnvironDetails{ - Name: parts[0], - Value: parts[1], - }) - } - - type LabelDetails struct { - Name string `json:"name"` - Value string `json:"value"` - } - - var labelOverrides []LabelDetails - - for _, value := range labels { - parts := strings.SplitN(value, "=", 2) - labelOverrides = append(labelOverrides, LabelDetails{ - Name: parts[0], - Value: parts[1], - }) - } - - var foundWorkshop = false - - for _, item := range workshops { - object := item.(map[string]interface{}) - - updatedWorkshops = append(updatedWorkshops, object) - - if object["name"] == workshop.GetName() && object["alias"] == alias { - foundWorkshop = true - - object["reserved"] = int64(reserved) - object["initial"] = int64(initial) - - if capacity != 0 { - object["capacity"] = int64(capacity) - } else { - delete(object, "capacity") - } - - if expires != "" { - object["expires"] = expires - } else { - delete(object, "expires") - } - - if overtime != "" { - object["overtime"] = overtime - } else { - delete(object, "overtime") - } - - if deadline != "" { - object["deadline"] = deadline - } else { - delete(object, "deadline") - } - - if orphaned != "" { - object["orphaned"] = orphaned - } else { - delete(object, "orphaned") - } - - if overdue != "" { - object["overdue"] = overdue - } else { - delete(object, "overdue") - } - - if refresh != "" { - object["refresh"] = refresh - } else { - delete(object, "refresh") - } - - var tmpEnvironVariables []interface{} - - for _, item := range environVariables { - tmpEnvironVariables = append(tmpEnvironVariables, map[string]interface{}{ - "name": item.Name, - "value": item.Value, - }) - } - - object["env"] = tmpEnvironVariables - - var tmpLabelOverrides []interface{} - - for _, item := range labelOverrides { - tmpLabelOverrides = append(tmpLabelOverrides, map[string]interface{}{ - "name": item.Name, - "value": item.Value, - }) - } - - object["labels"] = tmpLabelOverrides - } - } - - type RegistryDetails struct { - Host string `json:"host"` - Namespace string `json:"namespace,omitempty"` - } - - type WorkshopDetails struct { - Name string `json:"name"` - Alias string `json:"alias"` - Capacity int64 `json:"capacity,omitempty"` - Initial int64 `json:"initial"` - Reserved int64 `json:"reserved"` - Expires string `json:"expires,omitempty"` - Overtime string `json:"overtime,omitempty"` - Deadline string `json:"deadline,omitempty"` - Orphaned string `json:"orphaned,omitempty"` - Overdue string `json:"overdue,omitempty"` - Refresh string `json:"refresh,omitempty"` - Registry *RegistryDetails `json:"registry,omitempty"` - Environ []EnvironDetails `json:"env"` - Labels []LabelDetails `json:"labels"` - } - - if !foundWorkshop { - workshopDetails := WorkshopDetails{ - Name: workshop.GetName(), - Alias: alias, - Initial: int64(initial), - Reserved: int64(reserved), - Expires: expires, - Overtime: overtime, - Deadline: deadline, - Orphaned: orphaned, - Overdue: overdue, - Refresh: refresh, - Environ: environVariables, - Labels: labelOverrides, - } - - if capacity != 0 { - workshopDetails.Capacity = int64(capacity) - } - - if registry != "" { - parts := strings.SplitN(registry, "/", 2) - - host := parts[0] - var namespace string - - if len(parts) > 1 { - namespace = parts[1] - } - - registryDetails := RegistryDetails{ - Host: host, - Namespace: namespace, - } - - workshopDetails.Registry = ®istryDetails - } - - var workshopDetailsMap map[string]interface{} - - data, _ := json.Marshal(workshopDetails) - json.Unmarshal(data, &workshopDetailsMap) - - updatedWorkshops = append(updatedWorkshops, workshopDetailsMap) - } - - unstructured.SetNestedSlice(trainingPortal.Object, updatedWorkshops, "spec", "workshops") - - if trainingPortalExists { - fmt.Printf("Updating existing training portal %q.\n", trainingPortal.GetName()) - _, err = trainingPortalClient.Update(context.TODO(), trainingPortal, metav1.UpdateOptions{FieldManager: "educates-cli"}) - } else { - fmt.Printf("Creating new training portal %q.\n", trainingPortal.GetName()) - _, err = trainingPortalClient.Create(context.TODO(), trainingPortal, metav1.CreateOptions{FieldManager: "educates-cli"}) - } - - if err != nil { - return errors.Wrapf(err, "unable to update training portal %q in cluster", portal) - } - - fmt.Print("Workshop added to training portal.\n") - - if openBrowser { - // Need to refetch training portal because if was just created the URL - // for access may not have been set yet. - - var targetUrl string - - fmt.Print("Checking training portal is ready.\n") - - spinner := func(iteration int) string { - spinners := `|/-\` - return string(spinners[iteration%len(spinners)]) - } - - for i := 1; i < 60; i++ { - fmt.Printf("\r[%s] Waiting...", spinner(i)) - - time.Sleep(time.Second) - - trainingPortal, err = trainingPortalClient.Get(context.TODO(), portal, metav1.GetOptions{}) - - if err != nil { - return errors.Wrapf(err, "unable to fetch training portal %q in cluster", portal) - } - - var found bool - - targetUrl, found, _ = unstructured.NestedString(trainingPortal.Object, "status", "educates", "url") - - if found { - break - } - } - - rootUrl := targetUrl - - password, _, _ := unstructured.NestedString(trainingPortal.Object, "spec", "portal", "password") - - if password != "" { - values := url.Values{} - values.Add("redirect_url", "/") - values.Add("password", password) - - targetUrl = fmt.Sprintf("%s/workshops/access/?%s", targetUrl, values.Encode()) - } - - for i := 1; i < 300; i++ { - fmt.Printf("\r[%s] Waiting...", spinner(i)) - - time.Sleep(time.Second) - - resp, err := http.Get(rootUrl) - - if err != nil || resp.StatusCode == 503 { - continue - } - - defer resp.Body.Close() - io.ReadAll(resp.Body) - - break - } - - fmt.Print("\r \r") - - fmt.Printf("Opening training portal %s.\n", targetUrl) - - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", targetUrl).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", targetUrl).Start() - case "darwin": - err = exec.Command("open", targetUrl).Start() - default: - err = fmt.Errorf("unsupported platform") - } - - if err != nil { - return errors.Wrap(err, "unable to open web browser") - } - } - - return nil -} - -func randomPassword(length int) string { - rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64()))) - - chars := []rune("!#%+23456789:=?@ABCDEFGHJKLMNPRSTUVWXYZabcdefghijkmnopqrstuvwxyz") - - var b strings.Builder - - for i := 0; i < length; i++ { - b.WriteRune(chars[rand.Intn(len(chars))]) - } - return b.String() -} diff --git a/client-programs/pkg/cmd/cluster_workshop_list_cmd.go b/client-programs/pkg/cmd/cluster_workshop_list_cmd.go index b2228615f..4300f123a 100644 --- a/client-programs/pkg/cmd/cluster_workshop_list_cmd.go +++ b/client-programs/pkg/cmd/cluster_workshop_list_cmd.go @@ -1,17 +1,26 @@ package cmd import ( - "context" "fmt" - "os" - "text/tabwriter" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" "github.com/pkg/errors" "github.com/spf13/cobra" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +const ( + clusterWorkshopListExample = ` + # List Educates workshops deployed to Kubernetes cluster + educates cluster workshop list + + # List Educates workshops deployed to Kubernetes cluster with a specific portal + educates cluster workshop list --portal=my-portal + + # List Educates workshops deployed to alternateKubernetes cluster + educates cluster workshop list --kubeconfig ~/.kube/config --context=my-context +` ) type ClusterWorkshopsListOptions struct { @@ -25,7 +34,7 @@ func (o *ClusterWorkshopsListOptions) Run() error { // Ensure have portal name. if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context) @@ -39,65 +48,17 @@ func (o *ClusterWorkshopsListOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } - trainingPortalClient := dynamicClient.Resource(trainingPortalResource) - - trainingPortal, err := trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) - - if k8serrors.IsNotFound(err) { - fmt.Println("No workshops found.") - return nil - } - - sessionsMaximum, sessionsMaximumExists, _ := unstructured.NestedInt64(trainingPortal.Object, "spec", "portal", "sessions", "maximum") + manager := workshops.NewWorkshopManager(dynamicClient) - workshops, _, err := unstructured.NestedSlice(trainingPortal.Object, "spec", "workshops") + list, err := manager.ListWorkshopResources(&workshops.ListWorkshopResourcesConfig{ + Portal: o.Portal, + }) if err != nil { - return errors.Wrap(err, "unable to retrieve workshops from training portal") - } - - if len(workshops) == 0 { - fmt.Println("No workshops found.") - return nil + return err } - w := new(tabwriter.Writer) - w.Init(os.Stdout, 8, 8, 3, ' ', 0) - - defer w.Flush() - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "NAME", "ALIAS", "CAPACITY", "SOURCE") - - workshopsClient := dynamicClient.Resource(workshopResource) - - for _, item := range workshops { - object := item.(map[string]interface{}) - name := object["name"].(string) - - var capacityField string - - capacity, capacityExists := object["capacity"] - - if capacityExists { - capacityField = fmt.Sprintf("%d", capacity) - } else if sessionsMaximumExists { - capacityField = fmt.Sprintf("%d", sessionsMaximum) - } - - workshop, err := workshopsClient.Get(context.TODO(), name, metav1.GetOptions{}) - - source := "" - - if err == nil { - annotations := workshop.GetAnnotations() - - if val, ok := annotations["training.educates.dev/source"]; ok { - source = val - } - } - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", object["name"], object["alias"], capacityField, source) - } + fmt.Println(list) return nil } @@ -110,6 +71,7 @@ func (p *ProjectInfo) NewClusterWorkshopListCmd() *cobra.Command { Use: "list", Short: "List workshops deployed to Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterWorkshopListExample, } c.Flags().StringVar( @@ -128,7 +90,7 @@ func (p *ProjectInfo) NewClusterWorkshopListCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) diff --git a/client-programs/pkg/cmd/cluster_workshop_request_cmd.go b/client-programs/pkg/cmd/cluster_workshop_request_cmd.go index 38efb9d1a..9e1127147 100644 --- a/client-programs/pkg/cmd/cluster_workshop_request_cmd.go +++ b/client-programs/pkg/cmd/cluster_workshop_request_cmd.go @@ -4,21 +4,36 @@ import ( "context" "fmt" "os" - "os/exec" - "runtime" "strings" yttcmd "carvel.dev/ytt/pkg/cmd/template" + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" + educatesrestapi "github.com/educates/educates-training-platform/client-programs/pkg/educates/restapi" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/joho/godotenv" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - "github.com/educates/educates-training-platform/client-programs/pkg/educatesrestapi" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +const ( + clusterWorkshopRequestExample = ` + # Request Educates workshop in cluster in current workshop directory and using default workshop file + educates cluster workshop request + + # Request Educates workshop in cluster with custom workshop file + educates cluster workshop request --path ./workshop --workshop-file custom-workshop.yaml + + # Request Educates workshop but don't open the browser + educates cluster workshop request --no-browser +` +) + type ClusterWorkshopRequestOptions struct { KubeconfigOptions Name string @@ -89,7 +104,7 @@ func (o *ClusterWorkshopRequestOptions) Run() error { // Ensure have portal name. if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } if name == "" { @@ -109,7 +124,16 @@ func (o *ClusterWorkshopRequestOptions) Run() error { var workshop *unstructured.Unstructured - if workshop, err = loadWorkshopDefinition(o.Name, path, o.Portal, o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + definitionConfig := workshops.WorkshopDefinitionConfig{ + Name: o.Name, + Path: path, + Portal: o.Portal, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + + if workshop, err = workshops.LoadWorkshopDefinition(&definitionConfig); err != nil { return err } @@ -146,6 +170,7 @@ func (p *ProjectInfo) NewClusterWorkshopRequestCmd() *cobra.Command { Use: "request", Short: "Request workshop in Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterWorkshopRequestExample, } c.Flags().StringVarP( @@ -178,7 +203,7 @@ func (p *ProjectInfo) NewClusterWorkshopRequestCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) c.Flags().StringArrayVarP( @@ -299,7 +324,7 @@ func ensurePortalHasWorkshop(clusterConfig *cluster.ClusterConfig, name string, return errors.Wrapf(err, "unable to create Kubernetes client") } - trainingPortalClient := client.Resource(trainingPortalResource) + trainingPortalClient := client.Resource(educatesTypes.TrainingPortalResource) trainingPortal, err := trainingPortalClient.Get(context.TODO(), portal, metav1.GetOptions{}) @@ -378,20 +403,5 @@ func requestWorkshop(clusterConfig *cluster.ClusterConfig, workshopName string, fmt.Printf("Opening workshop URL %s.\n", workshopUrl) - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", workshopUrl).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", workshopUrl).Start() - case "darwin": - err = exec.Command("open", workshopUrl).Start() - default: - err = fmt.Errorf("unsupported platform") - } - - if err != nil { - return errors.Wrap(err, "unable to open web browser on workshop") - } - - return nil + return utils.OpenBrowser(workshopUrl) } diff --git a/client-programs/pkg/cmd/cluster_workshop_serve_cmd.go b/client-programs/pkg/cmd/cluster_workshop_serve_cmd.go index 5bfdf020c..71d26c04d 100644 --- a/client-programs/pkg/cmd/cluster_workshop_serve_cmd.go +++ b/client-programs/pkg/cmd/cluster_workshop_serve_cmd.go @@ -2,9 +2,7 @@ package cmd import ( "fmt" - "io/ioutil" "os" - "path" "path/filepath" yttcmd "carvel.dev/ytt/pkg/cmd/template" @@ -13,10 +11,25 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" "github.com/educates/educates-training-platform/client-programs/pkg/renderer" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) +const ( + clusterWorkshopServeExample = ` + # Serve Educates workshop from local system in current workshop directory and using default workshop file + educates cluster workshop serve + + # Serve Educates workshop from local system with custom workshop file + educates cluster workshop serve --path ./workshop --workshop-file custom-workshop.yaml + + # Serve Educates workshop from local system with custom workshop file and activate live reload mode + educates cluster workshop serve --path ./workshop --workshop-file custom-workshop.yaml --patch-workshop +` +) + +// TODO: Move this function somewhere and see how to update the code that does this same functionality in many workshop related commands func calculateWorkshopRoot(path string) (string, error) { var err error @@ -41,22 +54,6 @@ func calculateWorkshopRoot(path string) (string, error) { return path, nil } -// func calculateWorkshopName(name string, path string, portal string, workshopFile string, workshopVersion string, dataValuesFlags yttcmd.DataValuesFlags) (string, error) { -// var err error - -// if name == "" { -// var workshop *unstructured.Unstructured - -// if workshop, err = loadWorkshopDefinition(name, path, portal, workshopFile, workshopVersion, dataValuesFlags); err != nil { -// return "", err -// } - -// name = workshop.GetName() -// } - -// return name, nil -// } - type ClusterWorkshopServeOptions struct { KubeconfigOptions Name string @@ -77,51 +74,6 @@ type ClusterWorkshopServeOptions struct { DataValuesFlags yttcmd.DataValuesFlags } -func generateAccessToken(refresh bool) (string, error) { - configFileDir := utils.GetEducatesHomeDir() - accessTokenFile := path.Join(configFileDir, "live-reload-token.dat") - - err := os.MkdirAll(configFileDir, os.ModePerm) - - if err != nil { - return "", errors.Wrapf(err, "unable to create config directory") - } - - var accessToken string - - if refresh { - accessToken = randomPassword(32) - - err := ioutil.WriteFile(accessTokenFile, []byte(accessToken), 0644) - - if err != nil { - return "", err - } - } else { - if _, err := os.Stat(accessTokenFile); err == nil { - accessTokenBytes, err := ioutil.ReadFile(accessTokenFile) - - if err != nil { - return "", err - } - - accessToken = string(accessTokenBytes) - } else if os.IsNotExist(err) { - accessToken = randomPassword(32) - - err = ioutil.WriteFile(accessTokenFile, []byte(accessToken), 0644) - - if err != nil { - return "", err - } - } else { - return "", err - } - } - - return accessToken, nil -} - func (o *ClusterWorkshopServeOptions) Run() error { var err error @@ -137,20 +89,26 @@ func (o *ClusterWorkshopServeOptions) Run() error { } // Ensure have portal name. - if portal == "" { - portal = "educates-cli" + portal = constants.DefaultPortalName } // Calculate workshop root and name. - if path, err = calculateWorkshopRoot(path); err != nil { return err } var workshop *unstructured.Unstructured - if workshop, err = loadWorkshopDefinition(name, path, portal, o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + definitionConfig := workshops.WorkshopDefinitionConfig{ + Name: name, + Path: path, + Portal: portal, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + if workshop, err = workshops.LoadWorkshopDefinition(&definitionConfig); err != nil { return err } @@ -159,9 +117,8 @@ func (o *ClusterWorkshopServeOptions) Run() error { } // If going to patch hosted workshop, ensure we have an access token. - if o.PatchWorkshop && token == "" { - token, err = generateAccessToken(o.RefreshToken) + token, err = renderer.GenerateAccessToken(o.RefreshToken) if err != nil { return errors.Wrap(err, "error generating access token") @@ -169,7 +126,6 @@ func (o *ClusterWorkshopServeOptions) Run() error { } // If patching hosted workshop create an apply the updated configuration. - if o.PatchWorkshop { patchedWorkshop := workshop.DeepCopyObject().(*unstructured.Unstructured) @@ -201,9 +157,11 @@ func (o *ClusterWorkshopServeOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } + manager := workshops.NewWorkshopManager(dynamicClient) // Update the workshop resource in the Kubernetes cluster. - - err = updateWorkshopResource(dynamicClient, patchedWorkshop) + err = manager.UpdateWorkshopResource(&workshops.UpdateWorkshopResourceConfig{ + Workshop: patchedWorkshop, + }) if err != nil { return err @@ -214,23 +172,38 @@ func (o *ClusterWorkshopServeOptions) Run() error { var cleanupFunc = func() { // Do our best to revert workshop configuration and ignore errors. - clusterConfig := cluster.NewClusterConfig(o.Kubeconfig, o.Context) dynamicClient, err := clusterConfig.GetDynamicClient() if err == nil { // Update the workshop resource in the Kubernetes cluster. - - updateWorkshopResource(dynamicClient, workshop) - - fmt.Printf("Restored workshop %q.\n", workshop.GetName()) + manager := workshops.NewWorkshopManager(dynamicClient) + err = manager.UpdateWorkshopResource(&workshops.UpdateWorkshopResourceConfig{ + Workshop: workshop, + }) + if err != nil { + fmt.Printf("Error restoring workshop %q: %v\n", workshop.GetName(), err) + } else { + fmt.Printf("Restored workshop %q.\n", workshop.GetName()) + } } } // Run the proxy server and Hugo server. - - return renderer.RunHugoServer(path, o.Kubeconfig, o.Context, name, portal, o.LocalHost, o.LocalPort, o.HugoPort, token, o.Files, cleanupFunc) + return renderer.RunHugoServer(&renderer.RunHugoServerConfig{ + WorkshopRoot: path, + Kubeconfig: o.Kubeconfig, + Context: o.Context, + Workshop: name, + Portal: portal, + LocalHost: o.LocalHost, + LocalPort: o.LocalPort, + HugoPort: o.HugoPort, + Token: token, + Files: o.Files, + CleanupFunc: cleanupFunc, + }) } func (p *ProjectInfo) NewClusterWorkshopServeCmd() *cobra.Command { @@ -241,6 +214,7 @@ func (p *ProjectInfo) NewClusterWorkshopServeCmd() *cobra.Command { Use: "serve", Short: "Serve workshop from local system", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterWorkshopServeExample, } c.Flags().StringVarP( @@ -273,7 +247,7 @@ func (p *ProjectInfo) NewClusterWorkshopServeCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name of the training portal to lookup the workshop", ) c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/cluster_workshop_update_cmd.go b/client-programs/pkg/cmd/cluster_workshop_update_cmd.go index c3075ded8..15795fdbd 100644 --- a/client-programs/pkg/cmd/cluster_workshop_update_cmd.go +++ b/client-programs/pkg/cmd/cluster_workshop_update_cmd.go @@ -1,28 +1,25 @@ package cmd import ( - "context" - "crypto/sha1" "fmt" - "io" - "net/http" - "net/url" - "os" - "path/filepath" yttcmd "carvel.dev/ytt/pkg/cmd/template" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - "github.com/educates/educates-training-platform/client-programs/pkg/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" "github.com/pkg/errors" "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/dynamic" - "k8s.io/kubectl/pkg/scheme" +) + +const ( + clusterWorkshopUpdateExample = ` + # Update Educates workshop in Kubernetes cluster in current workshop directory and using default workshop file + educates cluster workshop update + + # Update Educates workshop in Kubernetes cluster with custom workshop file + educates cluster workshop update --path ./workshop --workshop-file custom-workshop.yaml +` ) type ClusterWorkshopUpdateOptions struct { @@ -43,7 +40,7 @@ func (o *ClusterWorkshopUpdateOptions) Run() error { // Ensure have portal name. if o.Portal == "" { - o.Portal = "educates-cli" + o.Portal = constants.DefaultPortalName } // If path not provided assume the current working directory. When loading @@ -60,7 +57,16 @@ func (o *ClusterWorkshopUpdateOptions) Run() error { var workshop *unstructured.Unstructured - if workshop, err = loadWorkshopDefinition(o.Name, path, o.Portal, o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + definitionConfig := workshops.WorkshopDefinitionConfig{ + Name: o.Name, + Path: o.Path, + Portal: o.Portal, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + + if workshop, err = workshops.LoadWorkshopDefinition(&definitionConfig); err != nil { return err } @@ -76,9 +82,14 @@ func (o *ClusterWorkshopUpdateOptions) Run() error { return errors.Wrapf(err, "unable to create Kubernetes client") } + manager := workshops.NewWorkshopManager(dynamicClient) + // Update the workshop resource in the Kubernetes cluster. + updateConfig := workshops.UpdateWorkshopResourceConfig{ + Workshop: workshop, + } - err = updateWorkshopResource(dynamicClient, workshop) + err = manager.UpdateWorkshopResource(&updateConfig) if err != nil { return err @@ -97,6 +108,7 @@ func (p *ProjectInfo) NewClusterWorkshopUpdateCmd() *cobra.Command { Use: "update", Short: "Update workshop in Kubernetes", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: clusterWorkshopUpdateExample, } c.Flags().StringVarP( @@ -129,7 +141,7 @@ func (p *ProjectInfo) NewClusterWorkshopUpdateCmd() *cobra.Command { &o.Portal, "portal", "p", - "educates-cli", + constants.DefaultPortalName, "name to be used for training portal and workshop name prefixes", ) @@ -187,172 +199,3 @@ func (p *ProjectInfo) NewClusterWorkshopUpdateCmd() *cobra.Command { return c } - -func loadWorkshopDefinition(name string, path string, portal string, workshopFile string, workshopVersion string, dataValueFlags yttcmd.DataValuesFlags) (*unstructured.Unstructured, error) { - // Parse the workshop location so we can determine if it is a local file - // or accessible using a HTTP/HTTPS URL. - - var urlInfo *url.URL - var err error - - if urlInfo, err = url.Parse(path); err != nil { - return nil, errors.Wrap(err, "unable to parse workshop location") - } - - // Check if file system path first (not HTTP/HTTPS) and if so normalize - // the path. If it the path references a directory, then extend the path - // so we look for the workshop file within that directory. - - if urlInfo.Scheme != "http" && urlInfo.Scheme != "https" { - path = filepath.Clean(path) - - if path, err = filepath.Abs(path); err != nil { - return nil, errors.Wrap(err, "couldn't convert workshop location to absolute path") - } - - if !filepath.IsAbs(workshopFile) { - fileInfo, err := os.Stat(path) - - if err != nil { - return nil, errors.Wrap(err, "couldn't test if workshop location is a directory") - } - - if fileInfo.IsDir() { - path = filepath.Join(path, workshopFile) - } - } else { - path = workshopFile - } - } - - // Read in the workshop definition as raw data ready for parsing. - - var workshopData []byte - - if urlInfo.Scheme != "http" && urlInfo.Scheme != "https" { - if workshopData, err = os.ReadFile(path); err != nil { - return nil, errors.Wrap(err, "couldn't read workshop definition data file") - } - } else { - var client http.Client - - resp, err := client.Get(path) - - if err != nil { - return nil, errors.Wrap(err, "couldn't download workshop definition from host") - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, errors.New("failed to download workshop definition from host") - } - - workshopData, err = io.ReadAll(resp.Body) - - if err != nil { - return nil, errors.Wrap(err, "failed to read workshop definition from host") - } - } - - // Process the workshop YAML data in case it contains ytt templating. - - if workshopData, err = workshops.ProcessWorkshopDefinition(workshopData, dataValueFlags); err != nil { - return nil, errors.Wrap(err, "unable to process workshop definition as template") - } - - // Parse the workshop definition. - - decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder() - - workshop := &unstructured.Unstructured{} - - err = runtime.DecodeInto(decoder, workshopData, workshop) - - if err != nil { - return nil, errors.Wrap(err, "couldn't parse workshop definition") - } - - // Verify the type of resource definition. - - if workshop.GetAPIVersion() != "training.educates.dev/v1beta1" || workshop.GetKind() != "Workshop" { - return nil, errors.New("invalid type for workshop definition") - } - - // Add annotations recording details about original workshop location. - - annotations := workshop.GetAnnotations() - - if annotations == nil { - annotations = map[string]string{} - } - - annotations["training.educates.dev/workshop"] = workshop.GetName() - - if urlInfo.Scheme != "http" && urlInfo.Scheme != "https" { - annotations["training.educates.dev/source"] = fmt.Sprintf("file://%s", path) - } else { - annotations["training.educates.dev/source"] = path - } - - workshop.SetAnnotations(annotations) - - // Update the name for the workshop such that it incorporates a hash of - // the workshop location. - - if name == "" { - name = generateWorkshopName(path, workshop, portal) - } - - workshop.SetName(name) - - // Insert workshop version property if not specified. - - _, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") - - if !found && workshopVersion != "latest" { - unstructured.SetNestedField(workshop.Object, workshopVersion, "spec", "version") - } - - // Remove the publish section as will not be accurate after publising. - - unstructured.RemoveNestedField(workshop.Object, "spec", "publish") - - return workshop, nil -} - -func generateWorkshopName(path string, workshop *unstructured.Unstructured, portal string) string { - name := workshop.GetName() - - h := sha1.New() - - io.WriteString(h, path) - - hv := fmt.Sprintf("%x", h.Sum(nil)) - - name = fmt.Sprintf("%s--%s-%s", portal, name, hv[len(hv)-7:]) - - return name -} - -var workshopResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshops"} - -func updateWorkshopResource(client dynamic.Interface, workshop *unstructured.Unstructured) error { - workshopsClient := client.Resource(workshopResource) - - // _, err := workshopsClient.Apply(context.TODO(), workshop.GetName(), workshop, metav1.ApplyOptions{FieldManager: "educates-cli", Force: true}) - - workshopBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, workshop) - - if err != nil { - return errors.Wrapf(err, "unable to update workshop definition in cluster %q", workshop.GetName()) - } - - _, err = workshopsClient.Patch(context.TODO(), workshop.GetName(), types.ApplyPatchType, workshopBytes, metav1.ApplyOptions{FieldManager: "educates-cli", Force: true}.ToPatchOptions()) - - if err != nil { - return errors.Wrapf(err, "unable to update workshop definition in cluster %q", workshop.GetName()) - } - - return nil -} diff --git a/client-programs/pkg/cmd/docker_extension_backend_cmd.go b/client-programs/pkg/cmd/docker_extension_backend_cmd.go index bc1e28d6e..19d42d188 100644 --- a/client-programs/pkg/cmd/docker_extension_backend_cmd.go +++ b/client-programs/pkg/cmd/docker_extension_backend_cmd.go @@ -1,234 +1,19 @@ package cmd import ( - "context" - "encoding/json" - "fmt" - "net" - "net/http" - "os" - "os/signal" - "regexp" - "strconv" - "strings" - "syscall" - - "github.com/pkg/errors" + "github.com/educates/educates-training-platform/client-programs/pkg/docker" "github.com/spf13/cobra" ) +const dockerExtensionBackendExample = ` +# Start the backend server on a Unix socket +docker extension backend --socket /run/guest-services/backend.sock +` + type DockerExtensionBackendOptions struct { Socket string } -type DockerWorkshopsBackend struct { - Manager DockerWorkshopsManager - ImageRepository string - ImageVersion string -} - -func NewDockerWorkshopsBackend(version string, imageRepository string) DockerWorkshopsBackend { - return DockerWorkshopsBackend{ - Manager: NewDockerWorkshopsManager(), - ImageRepository: imageRepository, - ImageVersion: version, - } -} - -func (b *DockerWorkshopsBackend) ListWorkhops(w http.ResponseWriter, r *http.Request) { - workshops, err := b.Manager.ListWorkhops() - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - jsonData, err := json.Marshal(workshops) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - - w.WriteHeader(http.StatusOK) - w.Write(jsonData) -} - -func (b *DockerWorkshopsBackend) DeployWorkshop(w http.ResponseWriter, r *http.Request) { - queryParams := r.URL.Query() - - url := queryParams.Get("url") - - if url == "" { - http.Error(w, "workshop definition url required", http.StatusBadRequest) - return - } - - portString := queryParams.Get("port") - - if portString == "" { - portString = "10081" - } - - port, err := strconv.Atoi(portString) - - if err != nil || port <= 0 { - http.Error(w, "invalid workshop port supplied", http.StatusBadRequest) - return - } - - o := DockerWorkshopDeployOptions{ - Path: url, - Host: "127.0.0.1", - Port: uint(port), - LocalRepository: "localhost:5001", - DisableOpenBrowser: false, - ImageRepository: b.ImageRepository, - ImageVersion: b.ImageVersion, - Cluster: "", - KubeConfig: "", - Assets: "", - } - - name, err := b.Manager.DeployWorkshop(&o, os.Stdout, os.Stderr) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - sessionUrl := fmt.Sprintf("http://workshop.%s.nip.io:%d", strings.ReplaceAll(o.Host, ".", "-"), o.Port) - - workshop := DockerWorkshopDetails{ - Name: name, - Url: sessionUrl, - Source: url, - Status: "Started", - } - - jsonData, err := json.Marshal(workshop) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - - w.WriteHeader(http.StatusOK) - w.Write(jsonData) -} - -func (b *DockerWorkshopsBackend) DeleteWorkshop(w http.ResponseWriter, r *http.Request) { - queryParams := r.URL.Query() - - name := queryParams.Get("name") - - if name == "" { - http.Error(w, "workshop session name required", http.StatusBadRequest) - return - } - - err := b.Manager.DeleteWorkshop(name, os.Stdout, os.Stderr) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - workshop := DockerWorkshopDetails{ - Name: name, - Status: "Stopped", - } - - jsonData, err := json.Marshal(workshop) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - - w.WriteHeader(http.StatusOK) - w.Write(jsonData) -} - -func (o *DockerExtensionBackendOptions) Run(p *ProjectInfo) error { - if o.Socket == "" { - return errors.New("invalid socket for HTTP server") - } - - router := http.NewServeMux() - - versionHandler := func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, p.Version) - } - - router.HandleFunc("/version", versionHandler) - - backend := NewDockerWorkshopsBackend(p.Version, p.ImageRepository) - - router.HandleFunc("/workshop/list", backend.ListWorkhops) - router.HandleFunc("/workshop/deploy", backend.DeployWorkshop) - router.HandleFunc("/workshop/delete", backend.DeleteWorkshop) - - server := http.Server{ - Handler: router, - } - - // The socket string can either be of the form host:nnn, or it can be a file - // system path (absolute or relative). In the first case we start up a - // normal HTTP server accepting connections over an INET socket connection. - // In the second case connections will be accepted over a UNIX socket. - - inetRegexPattern := `^([a-zA-Z0-9.-]+):(\d+)$` - - match, err := regexp.MatchString(inetRegexPattern, o.Socket) - - if err != nil { - return errors.Wrap(err, "failed to perform regex match on socket") - } - - var listener net.Listener - - if match { - listener, err = net.Listen("tcp", o.Socket) - - if err != nil { - return errors.Wrap(err, "unable to create INET HTTP server socket") - } - } else { - listener, err = net.Listen("unix", o.Socket) - - if err != nil { - return errors.Wrap(err, "unable to create UNIX HTTP server socket") - } - - defer os.Remove(o.Socket) - } - - defer listener.Close() - - go func() { - server.Serve(listener) - }() - - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - <-sigChan - - err = server.Shutdown(context.TODO()) - - if err != nil { - return errors.Wrap(err, "failed to shutdown HTTP server") - } - - return nil -} - func (p *ProjectInfo) NewDockerExtensionBackendCmd() *cobra.Command { var o DockerExtensionBackendOptions @@ -236,7 +21,13 @@ func (p *ProjectInfo) NewDockerExtensionBackendCmd() *cobra.Command { Args: cobra.NoArgs, Use: "backend", Short: "Docker desktop extension backend", - RunE: func(_ *cobra.Command, _ []string) error { return o.Run(p) }, + RunE: func(_ *cobra.Command, _ []string) error { + dockerExtensionBackend := docker.NewDockerExtensionBackend(p.Version, p.ImageRepository) + return dockerExtensionBackend.Run(&docker.DockerExtensionBackendConfig{ + Socket: o.Socket, + }) + }, + Example: dockerExtensionBackendExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/docker_workshop_delete_cmd.go b/client-programs/pkg/cmd/docker_workshop_delete_cmd.go index e7da2b75b..48fff07ca 100644 --- a/client-programs/pkg/cmd/docker_workshop_delete_cmd.go +++ b/client-programs/pkg/cmd/docker_workshop_delete_cmd.go @@ -1,21 +1,24 @@ package cmd import ( - "context" - "fmt" - "io" - "os" - "os/exec" - "path" - yttcmd "carvel.dev/ytt/pkg/cmd/template" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/docker" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" - "github.com/pkg/errors" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +const dockerWorkshopDeleteExample = ` + # Delete Educates workshop from Docker in current workshop directory and using default workshop file + educates docker workshop delete + + # Delete Educates workshop from Docker from specific portal + educates docker workshop delete --portal my-portal + + # Delete Educates workshop from Docker defined with custom path and workshop file + educates docker workshop delete --path ./workshop --workshop-file custom-workshop.yaml +` + type DockerWorkshopDeleteOptions struct { Name string Path string @@ -24,58 +27,7 @@ type DockerWorkshopDeleteOptions struct { DataValuesFlags yttcmd.DataValuesFlags } -func (m *DockerWorkshopsManager) DeleteWorkshop(name string, stdout io.Writer, stderr io.Writer) error { - m.SetWorkshopStatus(name, "", "", "Stopping") - - defer m.ClearWorkshopStatus(name) - - dockerCommand := exec.Command( - "docker", - "compose", - "--project-name", - name, - "rm", - "--stop", - "--force", - "--volumes", - ) - - dockerCommand.Stdout = stdout - dockerCommand.Stderr = stderr - - err := dockerCommand.Run() - - if err != nil { - return errors.Wrap(err, "unable to stop workshop") - } - - ctx := context.Background() - - cli, err := docker.NewDockerClient() - - if err != nil { - return errors.Wrap(err, "unable to create docker client") - } - - err = cli.VolumeRemove(ctx, fmt.Sprintf("%s_workshop", name), false) - - if err != nil { - return errors.Wrap(err, "unable to delete workshop volume") - } - - configFileDir := utils.GetEducatesHomeDir() - workshopConfigDir := path.Join(configFileDir, "workshops", name) - composeConfigDir := path.Join(configFileDir, "compose", name) - - os.RemoveAll(workshopConfigDir) - os.RemoveAll(composeConfigDir) - - return nil -} - func (o *DockerWorkshopDeleteOptions) Run(cmd *cobra.Command) error { - var err error - var name = o.Name if name == "" { @@ -85,24 +37,28 @@ func (o *DockerWorkshopDeleteOptions) Run(cmd *cobra.Command) error { // the workshop will then expect the workshop definition to reside in the // resources/workshop.yaml file under the directory, the same as if a // directory path was provided explicitly. - if path == "" { path = "." } // Load the workshop definition. The path can be a HTTP/HTTPS URL for a // local file system path for a directory or file. - - var workshop *unstructured.Unstructured - - if workshop, err = loadWorkshopDefinition(o.Name, path, "educates-cli", o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + workshop, err := workshops.LoadWorkshopDefinition(&workshops.WorkshopDefinitionConfig{ + Name: o.Name, + Path: path, + Portal: constants.DefaultPortalName, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + }) + if err != nil { return err } name = workshop.GetName() } - dockerWorkshopsManager := NewDockerWorkshopsManager() + dockerWorkshopsManager := docker.NewDockerWorkshopsManager() return dockerWorkshopsManager.DeleteWorkshop(name, cmd.OutOrStdout(), cmd.OutOrStderr()) } @@ -115,6 +71,7 @@ func (p *ProjectInfo) NewDockerWorkshopDeleteCmd() *cobra.Command { Use: "delete", Short: "Delete workshop from Docker", RunE: func(cmd *cobra.Command, _ []string) error { return o.Run(cmd) }, + Example: dockerWorkshopDeleteExample, } c.Flags().StringVarP( @@ -124,6 +81,8 @@ func (p *ProjectInfo) NewDockerWorkshopDeleteCmd() *cobra.Command { "", "name to be used for the workshop definition, generated if not set", ) + + // TODO: Move "." to a constant c.Flags().StringVarP( &o.Path, "file", @@ -132,6 +91,7 @@ func (p *ProjectInfo) NewDockerWorkshopDeleteCmd() *cobra.Command { "path to local workshop directory, definition file, or URL for workshop definition file", ) + // TODO: Move "resources/workshop.yaml" to a constant c.Flags().StringVar( &o.WorkshopFile, "workshop-file", @@ -139,6 +99,7 @@ func (p *ProjectInfo) NewDockerWorkshopDeleteCmd() *cobra.Command { "location of the workshop definition file", ) + // TODO: Move "latest" to a constant c.Flags().StringVar( &o.WorkshopVersion, "workshop-version", diff --git a/client-programs/pkg/cmd/docker_workshop_deploy_cmd.go b/client-programs/pkg/cmd/docker_workshop_deploy_cmd.go index a5bfac293..41ee0e235 100644 --- a/client-programs/pkg/cmd/docker_workshop_deploy_cmd.go +++ b/client-programs/pkg/cmd/docker_workshop_deploy_cmd.go @@ -1,34 +1,41 @@ package cmd import ( - "bytes" - "context" "fmt" "io" "net/http" - "os" - "os/exec" - "path" - "path/filepath" - "runtime" "strings" - "text/template" "time" yttcmd "carvel.dev/ytt/pkg/cmd/template" - composeloader "github.com/compose-spec/compose-go/loader" - composetypes "github.com/compose-spec/compose-go/types" "github.com/educates/educates-training-platform/client-programs/pkg/docker" "github.com/educates/educates-training-platform/client-programs/pkg/utils" - "github.com/pkg/errors" "github.com/spf13/cobra" - "golang.org/x/exp/slices" - "gopkg.in/yaml.v2" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/kind/pkg/cluster" - "sigs.k8s.io/kind/pkg/cmd" ) +const dockerWorkshopDeployExample = ` + # Deploy Educates workshop to Docker in current workshop directory and using default workshop file + educates docker workshop deploy + + # Deploy Educates workshop to Docker from specific path and using custom workshop file + educates docker workshop deploy --path ./workshop --workshop-file custom-workshop.yaml + + # Deploy Educates workshop to Docker with custom host and port + educates docker workshop deploy --host 192.168.1.100 --port 10081 + + # Deploy Educates workshop to Docker with custom local repository + educates docker workshop deploy --local-repository localhost:5001 + + # Deploy Educates workshop adding to the session kubeconfig to specified Kind cluster + educates docker workshop deploy --cluster my-cluster + + # Deploy Educates workshop adding to the specified kubeconfig to the session + educates docker workshop deploy --kubeconfig /path/to/kubeconfig + + # Deploy Educates workshop in current folder without opening the browser + educates docker workshop deploy --disable-open-browser +` + type DockerWorkshopDeployOptions struct { Path string Host string @@ -46,375 +53,29 @@ type DockerWorkshopDeployOptions struct { DataValuesFlags yttcmd.DataValuesFlags } -const containerScript = `exec bash -s << "EOF" -mkdir -p /opt/eduk8s/config -cat > /opt/eduk8s/config/workshop.yaml << "EOS" -{{ .WorkshopConfig -}} -EOS -{{ if .Assets -}} -cat > /opt/eduk8s/config/vendir-assets-01.yaml << "EOS" -apiVersion: vendir.k14s.io/v1alpha1 -kind: Config -directories: -- path: /opt/assets/files - contents: - - directory: - path: /opt/eduk8s/mnt/assets - path: . -EOS -{{ else -}} -{{ range $k, $v := .VendirFilesConfig -}} -{{ $off := inc $k -}} -cat > /opt/eduk8s/config/vendir-assets-{{ printf "%02d" $off }}.yaml << "EOS" -{{ $v -}} -EOS -{{ end -}} -{{ end -}} -{{ if .VendirPackagesConfig -}} -cat > /opt/eduk8s/config/vendir-packages.yaml << "EOS" -{{ .VendirPackagesConfig -}} -EOS -{{ end -}} -{{ if .KubeConfig -}} -mkdir -p /opt/kubeconfig -cat > /opt/kubeconfig/config << "EOS" -{{ .KubeConfig -}} -EOS -{{ end -}} -exec start-container -EOF -` - -func (m *DockerWorkshopsManager) DeployWorkshop(o *DockerWorkshopDeployOptions, stdout io.Writer, stderr io.Writer) (string, error) { - var err error - - // If path not provided assume the current working directory. When loading - // the workshop will then expect the workshop definition to reside in the - // resources/workshop.yaml file under the directory, the same as if a - // directory path was provided explicitly. - - if o.Path == "" { - o.Path = "." - } - - // Load the workshop definition. The path can be a HTTP/HTTPS URL for a - // local file system path for a directory or file. - - var workshop *unstructured.Unstructured - - if workshop, err = loadWorkshopDefinition("", o.Path, "educates-cli", o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { - return "", err - } - - name := workshop.GetName() - - m.SetWorkshopStatus(name, "", o.Path, "Starting") - - defer m.ClearWorkshopStatus(name) - - originalName := workshop.GetAnnotations()["training.educates.dev/workshop"] - - configFileDir := utils.GetEducatesHomeDir() - composeConfigDir := path.Join(configFileDir, "compose", name) - - err = os.MkdirAll(composeConfigDir, os.ModePerm) - - if err != nil { - return name, errors.Wrapf(err, "unable to create workshops compose directory") - } - - ctx := context.Background() - - cli, err := docker.NewDockerClient() - - if err != nil { - return name, errors.Wrap(err, "unable to create docker client") - } - - _, err = cli.ContainerInspect(ctx, name) - - if err == nil { - return name, errors.New("this workshop is already running") - } - - registryNetwork := false - - if o.LocalRepository == "localhost:5001" { - o.LocalRepository = "registry.docker.local:5000" - } - - var registryIP string - - registryInfo, err := cli.ContainerInspect(ctx, "educates-registry") - - if err == nil { - educatesNetwork, exists := registryInfo.NetworkSettings.Networks["educates"] - - if !exists { - return name, errors.New("registry is not attached to educates network") - } - - registryNetwork = true - registryIP = educatesNetwork.IPAddress - } else { - o.LocalRepository = "" - } - - var kubeConfigData string - - if o.KubeConfig != "" { - kubeConfigBytes, err := os.ReadFile(o.KubeConfig) - - if err != nil { - return name, errors.Wrap(err, "unable to read kubeconfig file") - } - - kubeConfigData = string(kubeConfigBytes) - } - - if o.Cluster != "" { - kubeConfigData, err = generateClusterKubeconfig(o.Cluster) - - if err != nil { - return name, err - } - } - - var workshopConfigData string - var vendirFilesConfigData []string - var vendirPackagesConfigData string - var workshopImageName string - - var workshopPortsConfig []composetypes.ServicePortConfig - var workshopVolumesConfig []composetypes.ServiceVolumeConfig - - var workshopEnvironment []string - var workshopLabels map[string]string - var workshopExtraHosts map[string]string - - var workshopComposeProject *composetypes.Project - - if workshopConfigData, err = generateWorkshopConfig(workshop); err != nil { - return name, err - } - - if vendirFilesConfigData, err = generateVendirFilesConfig(workshop, originalName, o.LocalRepository, o.WorkshopVersion); err != nil { - return name, err - } - - if vendirPackagesConfigData, err = generateVendirPackagesConfig(workshop, originalName, o.LocalRepository, o.WorkshopVersion); err != nil { - return name, err - } - - if workshopImageName, err = generateWorkshopImageName(workshop, o.LocalRepository, o.ImageRepository, o.ImageVersion, o.WorkshopImage, o.WorkshopVersion); err != nil { - return name, err - } - - if workshopPortsConfig, err = composetypes.ParsePortConfig(fmt.Sprintf("%s:%d:10081", o.Host, o.Port)); err != nil { - return name, errors.Wrap(err, "unable to generate workshop ports config") - } - - if workshopVolumesConfig, err = generateWorkshopVolumeMounts(workshop, o.Assets); err != nil { - return name, err - } - - if workshopEnvironment, err = generateWorkshopEnvironment(workshop, o.LocalRepository, o.Host, o.Port); err != nil { - return name, err - } - - if workshopLabels, err = generateWorkshopLabels(workshop, o.Host, o.Port); err != nil { - return name, err - } - - if registryIP != "" { - if workshopExtraHosts, err = generateWorkshopExtraHosts(workshop, registryIP); err != nil { - return name, err - } - } - - if workshopComposeProject, err = extractWorkshopComposeConfig(workshop); err != nil { - return name, err - } - - type TemplateInputs struct { - WorkshopConfig string - VendirFilesConfig []string - VendirPackagesConfig string - KubeConfig string - Assets string - } - - inputs := TemplateInputs{ - WorkshopConfig: workshopConfigData, - VendirFilesConfig: vendirFilesConfigData, - VendirPackagesConfig: vendirPackagesConfigData, - KubeConfig: kubeConfigData, - Assets: o.Assets, - } - - funcMap := template.FuncMap{ - "inc": func(i int) int { - return i + 1 - }, - } - - containerScriptTemplate, err := template.New("entrypoint").Funcs(funcMap).Parse(containerScript) - - if err != nil { - return name, errors.Wrap(err, "not able to parse container script template") - } - - var containerScriptData bytes.Buffer - - err = containerScriptTemplate.Execute(&containerScriptData, inputs) - - if err != nil { - return name, errors.Wrap(err, "not able to generate container script") - } - - networks := map[string]*composetypes.ServiceNetworkConfig{ - "default": {}, - } - - if registryNetwork { - networks["educates"] = &composetypes.ServiceNetworkConfig{} - } - - workshopServiceConfig := composetypes.ServiceConfig{ - Name: "workshop", - Image: workshopImageName, - Command: composetypes.ShellCommand([]string{"bash", "-c", containerScriptData.String()}), - User: "1001:0", - Ports: workshopPortsConfig, - Volumes: workshopVolumesConfig, - Environment: composetypes.NewMappingWithEquals(workshopEnvironment), - Labels: composetypes.Labels(workshopLabels), - ExtraHosts: composetypes.HostsList(workshopExtraHosts), - DependsOn: composetypes.DependsOnConfig{}, - Networks: networks, - } - - if o.Cluster != "" { - workshopServiceConfig.Networks["kind"] = &composetypes.ServiceNetworkConfig{} - } - - dockerEnabled, found, _ := unstructured.NestedBool(workshop.Object, "spec", "session", "applications", "docker", "enabled") - - if found && dockerEnabled { - extraServices, _, _ := unstructured.NestedMap(workshop.Object, "spec", "session", "applications", "docker", "compose") - - socketEnabledDefault := true - - if len(extraServices) != 0 { - socketEnabledDefault = false - } - - socketEnabled, found, _ := unstructured.NestedBool(workshop.Object, "spec", "session", "applications", "docker", "socket", "enabled") - - if !found { - socketEnabled = socketEnabledDefault - } - - if socketEnabled { - workshopServiceConfig.GroupAdd = []string{"docker"} - } - } - - workshopServices := []composetypes.ServiceConfig{workshopServiceConfig} - - composeConfig := composetypes.Project{ - Name: originalName, - Services: workshopServices, - Networks: composetypes.Networks{ - "educates": {External: composetypes.External{External: true}}, - }, - Volumes: composetypes.Volumes{ - "workshop": composetypes.VolumeConfig{}, - }, - } - - if workshopComposeProject != nil { - for _, extraService := range workshopComposeProject.Services { - extraService.Ports = []composetypes.ServicePortConfig{} - - composeConfig.Services = append(composeConfig.Services, extraService) - - workshopServiceConfig.DependsOn[extraService.Name] = composetypes.ServiceDependency{ - Condition: composetypes.ServiceConditionStarted, - } - } - - for volumeName, extraVolume := range workshopComposeProject.Volumes { - if volumeName != "workshop" { - composeConfig.Volumes[volumeName] = extraVolume - } - } - } - - if o.Cluster != "" { - composeConfig.Networks["kind"] = composetypes.NetworkConfig{External: composetypes.External{External: true}} - } - - composeConfigBytes, err := yaml.Marshal(&composeConfig) - - if err != nil { - return name, errors.Wrap(err, "failed to generate compose config") - } - - composeConfigFilePath := path.Join(composeConfigDir, "docker-compose.yaml") - - composeConfigFile, err := os.OpenFile(composeConfigFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - - if err != nil { - return name, errors.Wrapf(err, "unable to create workshop config file %s", composeConfigFilePath) - } - - if _, err = composeConfigFile.Write(composeConfigBytes); err != nil { - return name, errors.Wrapf(err, "unable to write workshop config file %s", composeConfigFilePath) - } - - if err := composeConfigFile.Close(); err != nil { - return name, errors.Wrapf(err, "unable to close workshop config file %s", composeConfigFilePath) - } - - dockerCommand := exec.Command( - "docker", - "compose", - "--project-directory", - composeConfigDir, - "--file", - composeConfigFilePath, - "--project-name", - name, - "up", - "--detach", - "--renew-anon-volumes", - ) - - dockerCommand.Stdout = stdout - dockerCommand.Stderr = stderr - - err = dockerCommand.Run() - - if err != nil { - return name, errors.Wrap(err, "unable to start workshop") - } - - return name, nil -} - func (o *DockerWorkshopDeployOptions) Run(cmd *cobra.Command) error { - dockerWorkshopsManager := NewDockerWorkshopsManager() - - _, err := dockerWorkshopsManager.DeployWorkshop(o, cmd.OutOrStdout(), cmd.OutOrStderr()) + dockerWorkshopsManager := docker.NewDockerWorkshopsManager() + + config := docker.DockerWorkshopDeployConfig{ + Path: o.Path, + Host: o.Host, + Port: o.Port, + LocalRepository: o.LocalRepository, + DisableOpenBrowser: o.DisableOpenBrowser, + ImageRepository: o.ImageRepository, + ImageVersion: o.ImageVersion, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValuesFlags: o.DataValuesFlags, + } + _, err := dockerWorkshopsManager.DeployWorkshop(&config, cmd.OutOrStdout(), cmd.OutOrStderr()) if err != nil { return err } - // XXX Need a better way of handling very long startup times for container + // TODO: XXX Need a better way of handling very long startup times for container // due to workshop content or package downloads. - url := fmt.Sprintf("http://workshop.%s.nip.io:%d", strings.ReplaceAll(o.Host, ".", "-"), o.Port) if !o.DisableOpenBrowser { @@ -433,20 +94,7 @@ func (o *DockerWorkshopDeployOptions) Run(cmd *cobra.Command) error { break } - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - case "darwin": - err = exec.Command("open", url).Start() - default: - err = fmt.Errorf("unsupported platform") - } - - if err != nil { - return errors.Wrap(err, "unable to open web browser") - } + return utils.OpenBrowser(url) } return nil @@ -460,6 +108,7 @@ func (p *ProjectInfo) NewDockerWorkshopDeployCmd() *cobra.Command { Use: "deploy", Short: "Deploy workshop to Docker", RunE: func(cmd *cobra.Command, _ []string) error { return o.Run(cmd) }, + Example: dockerWorkshopDeployExample, } c.Flags().StringVarP( @@ -585,369 +234,3 @@ func (p *ProjectInfo) NewDockerWorkshopDeployCmd() *cobra.Command { return c } - -func generateWorkshopConfig(workshop *unstructured.Unstructured) (string, error) { - workshopTitle, _, _ := unstructured.NestedFieldNoCopy(workshop.Object, "spec", "title") - workshopDescription, _, _ := unstructured.NestedFieldNoCopy(workshop.Object, "spec", "description") - applicationsConfig, _, _ := unstructured.NestedFieldNoCopy(workshop.Object, "spec", "session", "applications") - ingressesConfig, _, _ := unstructured.NestedSlice(workshop.Object, "spec", "session", "ingresses") - dashboardsConfig, _, _ := unstructured.NestedSlice(workshop.Object, "spec", "session", "dashboards") - - workshopConfig := map[string]interface{}{ - "spec": map[string]interface{}{ - "title": workshopTitle, - "description": workshopDescription, - "session": map[string]interface{}{ - "applications": applicationsConfig, - "ingresses": ingressesConfig, - "dashboards": dashboardsConfig, - }, - }, - } - - workshopConfigData, err := yaml.Marshal(&workshopConfig) - - if err != nil { - return "", errors.Wrap(err, "failed to generate workshop config") - } - - return string(workshopConfigData), nil -} - -func generateVendirFilesConfig(workshop *unstructured.Unstructured, name string, localRepository string, version string) ([]string, error) { - var vendirConfigs []string - - workshopVersion, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") - - if !found { - workshopVersion = version - } - - filesItems, found, _ := unstructured.NestedSlice(workshop.Object, "spec", "workshop", "files") - - if found && len(filesItems) != 0 { - for _, filesItem := range filesItems { - directoriesConfig := []map[string]interface{}{} - - tmpPath, found := filesItem.(map[string]interface{})["path"] - - var filesItemPath string - - if found { - filesItemPath = tmpPath.(string) - } else { - filesItemPath = "." - } - - filesItemPath = filepath.Clean(path.Join("/opt/assets/files", filesItemPath)) - - filesItem.(map[string]interface{})["path"] = "." - - directoriesConfig = append(directoriesConfig, map[string]interface{}{ - "path": filesItemPath, - "contents": []interface{}{filesItem}, - }) - - vendirConfig := map[string]interface{}{ - "apiVersion": "vendir.k14s.io/v1alpha1", - "kind": "Config", - "directories": directoriesConfig, - } - - vendirConfigBytes, err := yaml.Marshal(&vendirConfig) - - if err != nil { - return []string{}, errors.Wrap(err, "failed to generate vendir config") - } - - vendirConfigString := string(vendirConfigBytes) - - vendirConfigString = strings.ReplaceAll(vendirConfigString, "$(image_repository)", localRepository) - vendirConfigString = strings.ReplaceAll(vendirConfigString, "$(workshop_name)", name) - vendirConfigString = strings.ReplaceAll(vendirConfigString, "$(workshop_version)", workshopVersion) - vendirConfigString = strings.ReplaceAll(vendirConfigString, "$(platform_arch)", runtime.GOARCH) - - vendirConfigs = append(vendirConfigs, vendirConfigString) - } - } - - return vendirConfigs, nil -} - -func generateVendirPackagesConfig(workshop *unstructured.Unstructured, name string, localRepository string, version string) (string, error) { - var vendirConfigString string - - workshopVersion, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") - - if !found { - workshopVersion = version - } - - packagesItems, found, _ := unstructured.NestedSlice(workshop.Object, "spec", "workshop", "packages") - - if found && len(packagesItems) != 0 { - directoriesConfig := []map[string]interface{}{} - - for _, packagesItem := range packagesItems { - tmpPackagesItem := packagesItem.(map[string]interface{}) - - tmpName, found := tmpPackagesItem["name"] - - if !found { - continue - } - - packagesItemPath := filepath.Clean(path.Join("/opt/packages", tmpName.(string))) - - tmpPackagesFilesItem := tmpPackagesItem["files"] - - packagesFilesItem := tmpPackagesFilesItem.([]interface{}) - - for _, tmpEntry := range packagesFilesItem { - entry := tmpEntry.(map[string]interface{}) - - _, found = entry["path"] - - if !found { - entry["path"] = "." - } - } - - directoriesConfig = append(directoriesConfig, map[string]interface{}{ - "path": packagesItemPath, - "contents": packagesFilesItem, - }) - - } - - vendirConfig := map[string]interface{}{ - "apiVersion": "vendir.k14s.io/v1alpha1", - "kind": "Config", - "directories": directoriesConfig, - } - - vendirConfigBytes, err := yaml.Marshal(&vendirConfig) - - if err != nil { - return "", errors.Wrap(err, "failed to generate vendir config") - } - - vendirConfigString = string(vendirConfigBytes) - - vendirConfigString = strings.ReplaceAll(vendirConfigString, "$(image_repository)", localRepository) - vendirConfigString = strings.ReplaceAll(vendirConfigString, "$(workshop_name)", name) - vendirConfigString = strings.ReplaceAll(vendirConfigString, "$(workshop_version)", workshopVersion) - } - - return vendirConfigString, nil -} - -func generateWorkshopImageName(workshop *unstructured.Unstructured, localRepository string, imageRepository string, baseImageVersion string, workshopImage string, workshopVersion string) (string, error) { - _, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") - - if found { - workshopVersion, _, _ = unstructured.NestedString(workshop.Object, "spec", "version") - } - - image, found, err := unstructured.NestedString(workshop.Object, "spec", "workshop", "image") - - if err != nil { - return "", errors.Wrapf(err, "unable to parse workshop definition") - } - - if !found || image == "" { - image = "base-environment:*" - } - - defaultImageVersion := strings.TrimSpace(baseImageVersion) - - if workshopImage != "" { - image = workshopImage - } else { - if defaultImageVersion == "latest" { - image = strings.ReplaceAll(image, "base-environment:*", fmt.Sprintf("localhost:5001/educates-base-environment:%s", defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk8-environment:*", fmt.Sprintf("localhost:5001/educates-jdk8-environment:%s", defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk11-environment:*", fmt.Sprintf("localhost:5001/educates-jdk11-environment:%s", defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk17-environment:*", fmt.Sprintf("localhost:5001/educates-jdk17-environment:%s", defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk21-environment:*", fmt.Sprintf("localhost:5001/educates-jdk21-environment:%s", defaultImageVersion)) - image = strings.ReplaceAll(image, "conda-environment:*", fmt.Sprintf("localhost:5001/educates-conda-environment:%s", defaultImageVersion)) - } else { - image = strings.ReplaceAll(image, "base-environment:*", fmt.Sprintf("%s/educates-base-environment:%s", imageRepository, defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk8-environment:*", fmt.Sprintf("%s/educates-jdk8-environment:%s", imageRepository, defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk11-environment:*", fmt.Sprintf("%s/educates-jdk11-environment:%s", imageRepository, defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk17-environment:*", fmt.Sprintf("%s/educates-jdk17-environment:%s", imageRepository, defaultImageVersion)) - image = strings.ReplaceAll(image, "jdk21-environment:*", fmt.Sprintf("%s/educates-jdk21-environment:%s", imageRepository, defaultImageVersion)) - image = strings.ReplaceAll(image, "conda-environment:*", fmt.Sprintf("%s/educates-conda-environment:%s", imageRepository, defaultImageVersion)) - } - } - - image = strings.ReplaceAll(image, "$(image_repository)", localRepository) - image = strings.ReplaceAll(image, "$(workshop_version)", workshopVersion) - - return image, nil -} - -func generateWorkshopVolumeMounts(workshop *unstructured.Unstructured, assets string) ([]composetypes.ServiceVolumeConfig, error) { - filesMounts := []composetypes.ServiceVolumeConfig{ - { - Type: "volume", - Source: "workshop", - Target: "/home/eduk8s", - }, - } - - if assets != "" { - assets = filepath.Clean(assets) - assets, err := filepath.Abs(assets) - - if err != nil { - return []composetypes.ServiceVolumeConfig{}, errors.Wrap(err, "can't resolve local workshop assets path") - } - - filesMounts = append(filesMounts, composetypes.ServiceVolumeConfig{ - Type: "bind", - Source: assets, - Target: "/opt/eduk8s/mnt/assets", - ReadOnly: true, - }) - } - - dockerEnabled, found, _ := unstructured.NestedBool(workshop.Object, "spec", "session", "applications", "docker", "enabled") - - if found && dockerEnabled { - extraServices, _, _ := unstructured.NestedMap(workshop.Object, "spec", "session", "applications", "docker", "compose") - - socketEnabledDefault := true - - if len(extraServices) != 0 { - socketEnabledDefault = false - } - - socketEnabled, found, _ := unstructured.NestedBool(workshop.Object, "spec", "session", "applications", "docker", "socket", "enabled") - - if !found { - socketEnabled = socketEnabledDefault - } - - if socketEnabled { - if runtime.GOOS == "linux" { - filesMounts = append(filesMounts, composetypes.ServiceVolumeConfig{ - Type: "bind", - Source: "/var/run/docker.sock", - Target: "/var/run/docker/docker.sock", - ReadOnly: true, - }) - } else { - filesMounts = append(filesMounts, composetypes.ServiceVolumeConfig{ - Type: "bind", - Source: "/var/run/docker.sock.raw", - Target: "/var/run/docker/docker.sock", - ReadOnly: true, - }) - } - } - } - - return filesMounts, nil -} - -func generateWorkshopEnvironment(workshop *unstructured.Unstructured, localRepository string, host string, port uint) ([]string, error) { - domain := fmt.Sprintf("%s.nip.io", strings.ReplaceAll(host, ".", "-")) - - return []string{ - fmt.Sprintf("WORKSHOP_NAME=%s", workshop.GetName()), - "SESSION_NAME=workshop", - fmt.Sprintf("SESSION_URL=http://workshop.%s:%d", domain, port), - "INGRESS_PROTOCOL=http", - fmt.Sprintf("INGRESS_DOMAIN=%s", domain), - fmt.Sprintf("INGRESS_PORT_SUFFIX=:%d", port), - fmt.Sprintf("IMAGE_REPOSITORY=%s", localRepository), - }, nil -} - -func generateWorkshopLabels(workshop *unstructured.Unstructured, host string, port uint) (map[string]string, error) { - labels := workshop.GetAnnotations() - - domain := fmt.Sprintf("%s.nip.io", strings.ReplaceAll(host, ".", "-")) - - labels["training.educates.dev/url"] = fmt.Sprintf("http://workshop.%s:%d", domain, port) - labels["training.educates.dev/session"] = workshop.GetName() - - return labels, nil -} - -func generateWorkshopExtraHosts(workshop *unstructured.Unstructured, registryIP string) (map[string]string, error) { - hosts := map[string]string{} - - if registryIP != "" { - hosts["registry.docker.local"] = registryIP - } - - return hosts, nil -} - -func extractWorkshopComposeConfig(workshop *unstructured.Unstructured) (*composetypes.Project, error) { - composeConfigObj, found, _ := unstructured.NestedMap(workshop.Object, "spec", "session", "applications", "docker", "compose") - - if found { - composeConfigObjBytes, err := yaml.Marshal(&composeConfigObj) - - if err != nil { - return nil, errors.Wrap(err, "unable to parse workshop docker compose config") - } - - configFiles := composetypes.ConfigFile{ - Content: composeConfigObjBytes, - } - - composeConfigDetails := composetypes.ConfigDetails{ - ConfigFiles: []composetypes.ConfigFile{configFiles}, - } - - return composeloader.Load(composeConfigDetails, func(options *composeloader.Options) { - options.SkipConsistencyCheck = true - options.SkipNormalization = true - options.ResolvePaths = false - }) - } - - return nil, nil -} - -func generateClusterKubeconfig(name string) (string, error) { - provider := cluster.NewProvider( - cluster.ProviderWithLogger(cmd.NewLogger()), - ) - - clusters, err := provider.List() - - if err != nil { - return "", errors.Wrap(err, "unable to get list of clusters") - } - - if !slices.Contains(clusters, name) { - return "", errors.Errorf("cluster %s doesn't exist", name) - } - - file, err := os.CreateTemp("", "kubeconfig-") - - if err != nil { - return "", errors.Wrap(err, "unable to generate kubeconfig file") - } - - defer os.Remove(file.Name()) - - err = provider.ExportKubeConfig(name, file.Name(), true) - - if err != nil { - return "", errors.Wrap(err, "unable to generate kubeconfig file") - } - - kubeConfigData, err := os.ReadFile(file.Name()) - - if err != nil { - return "", errors.Wrap(err, "unable to generate kubeconfig file") - } - - return string(kubeConfigData), nil -} diff --git a/client-programs/pkg/cmd/docker_workshop_list_cmd.go b/client-programs/pkg/cmd/docker_workshop_list_cmd.go index 13b47204a..7850977ba 100644 --- a/client-programs/pkg/cmd/docker_workshop_list_cmd.go +++ b/client-programs/pkg/cmd/docker_workshop_list_cmd.go @@ -1,160 +1,43 @@ package cmd import ( - "context" "fmt" - "os" - "sync" - "text/tabwriter" - "github.com/docker/docker/api/types/container" "github.com/educates/educates-training-platform/client-programs/pkg/docker" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" "github.com/spf13/cobra" ) +const dockerWorkshopListExample = ` + # List Educates workshops deployed to Docker + educates docker workshop list +` + func (p *ProjectInfo) NewDockerWorkshopListCmd() *cobra.Command { var c = &cobra.Command{ Args: cobra.NoArgs, Use: "list", Short: "List workshops deployed to Docker", RunE: func(_ *cobra.Command, _ []string) error { - dockerWorkshopsManager := NewDockerWorkshopsManager() + dockerWorkshopsManager := docker.NewDockerWorkshopsManager() - workshops, err := dockerWorkshopsManager.ListWorkhops() + workshops, err := dockerWorkshopsManager.ListWorkshops() if err != nil { return errors.Wrap(err, "cannot display list of workshops") } - w := new(tabwriter.Writer) - w.Init(os.Stdout, 8, 8, 3, ' ', 0) - - defer w.Flush() - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "NAME", "URL", "SOURCE", "STATUS") - + var data [][]string for _, workshop := range workshops { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", workshop.Name, workshop.Url, workshop.Source, workshop.Status) + data = append(data, []string{workshop.Name, workshop.Url, workshop.Source, workshop.Status}) } + fmt.Println(utils.PrintTable([]string{"NAME", "URL", "SOURCE", "STATUS"}, data)) return nil }, + Example: dockerWorkshopListExample, } return c } - -type DockerWorkshopsManager struct { - Statuses map[string]DockerWorkshopDetails - StatusesMutex sync.Mutex -} - -func NewDockerWorkshopsManager() DockerWorkshopsManager { - return DockerWorkshopsManager{ - Statuses: map[string]DockerWorkshopDetails{}, - StatusesMutex: sync.Mutex{}, - } -} - -type DockerWorkshopDetails struct { - Name string `json:"name"` - Url string `json:"url,omitempty"` - Source string `json:"source,omitempty"` - Status string `json:"status"` -} - -func (m *DockerWorkshopsManager) WorkshopStatus(name string) (DockerWorkshopDetails, bool) { - workshops, err := m.ListWorkhops() - - if err != nil { - return DockerWorkshopDetails{}, false - } - - for _, workshop := range workshops { - if workshop.Name == name { - return workshop, true - } - } - - return DockerWorkshopDetails{}, false -} - -func (m *DockerWorkshopsManager) SetWorkshopStatus(name string, url string, source string, status string) { - m.StatusesMutex.Lock() - - m.Statuses[name] = DockerWorkshopDetails{ - Name: name, - Url: url, - Source: source, - Status: status, - } - - m.StatusesMutex.Unlock() -} - -func (m *DockerWorkshopsManager) ClearWorkshopStatus(name string) { - m.StatusesMutex.Lock() - - delete(m.Statuses, name) - - m.StatusesMutex.Unlock() -} - -func (m *DockerWorkshopsManager) ListWorkhops() ([]DockerWorkshopDetails, error) { - setOfWorkshops := map[string]DockerWorkshopDetails{} - workshopsList := []DockerWorkshopDetails{} - - ctx := context.Background() - - cli, err := docker.NewDockerClient() - - if err != nil { - return nil, errors.Wrap(err, "unable to create docker client") - } - - containers, err := cli.ContainerList(ctx, container.ListOptions{}) - - if err != nil { - return nil, errors.Wrap(err, "unable to list containers") - } - - m.StatusesMutex.Lock() - - for _, details := range m.Statuses { - if details.Status == "Starting" { - setOfWorkshops[details.Name] = details - } - } - - defer m.StatusesMutex.Unlock() - - for _, container := range containers { - url, found := container.Labels["training.educates.dev/url"] - source := container.Labels["training.educates.dev/source"] - instance := container.Labels["training.educates.dev/session"] - - details, statusFound := m.Statuses[instance] - - status := "Running" - - if statusFound { - status = details.Status - } - - if found && url != "" && len(container.Names) != 0 { - setOfWorkshops[instance] = DockerWorkshopDetails{ - Name: instance, - Url: url, - Source: source, - Status: status, - } - } - } - - for _, details := range setOfWorkshops { - workshopsList = append(workshopsList, details) - } - - return workshopsList, nil -} diff --git a/client-programs/pkg/cmd/docker_workshop_logs.go b/client-programs/pkg/cmd/docker_workshop_logs.go index 841d17932..8330d20e0 100644 --- a/client-programs/pkg/cmd/docker_workshop_logs.go +++ b/client-programs/pkg/cmd/docker_workshop_logs.go @@ -4,11 +4,26 @@ import ( "os/exec" yttcmd "carvel.dev/ytt/pkg/cmd/template" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" "github.com/pkg/errors" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +const dockerWorkshopLogsExample = ` + # Display logs for Educates workshop in current workshop directory + educates docker workshop logs + + # Display logs for Educates workshop with provided name + educates docker workshop logs --name my-workshop + + # Display logs for Educates workshop from specific path and using custom workshop file + educates docker workshop logs --path ./workshop --workshop-file custom-workshop.yaml + + # Display logs for Educates workshop in current folder and follow the logs + educates docker workshop logs --follow +` + type DockerWorkshopLogsOptions struct { Name string Path string @@ -30,17 +45,22 @@ func (o *DockerWorkshopLogsOptions) Run(cmd *cobra.Command) error { // the workshop will then expect the workshop definition to reside in the // resources/workshop.yaml file under the directory, the same as if a // directory path was provided explicitly. - if path == "" { path = "." } // Load the workshop definition. The path can be a HTTP/HTTPS URL for a // local file system path for a directory or file. - - var workshop *unstructured.Unstructured - - if workshop, err = loadWorkshopDefinition(o.Name, path, "educates-cli", o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + definitionConfig := workshops.WorkshopDefinitionConfig{ + Name: o.Name, + Path: path, + Portal: constants.DefaultPortalName, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + workshop, err := workshops.LoadWorkshopDefinition(&definitionConfig) + if err != nil { return err } @@ -80,6 +100,7 @@ func (p *ProjectInfo) NewDockerWorkshopLogsCmd() *cobra.Command { Use: "logs", Short: "Display logs for workshop", RunE: func(cmd *cobra.Command, _ []string) error { return o.Run(cmd) }, + Example: dockerWorkshopLogsExample, } c.Flags().StringVarP( diff --git a/client-programs/pkg/cmd/docker_workshop_open_cmd.go b/client-programs/pkg/cmd/docker_workshop_open_cmd.go index 80ff36c85..683de9277 100644 --- a/client-programs/pkg/cmd/docker_workshop_open_cmd.go +++ b/client-programs/pkg/cmd/docker_workshop_open_cmd.go @@ -2,20 +2,30 @@ package cmd import ( "context" - "fmt" "io" "net/http" - "os/exec" - "runtime" "time" yttcmd "carvel.dev/ytt/pkg/cmd/template" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/docker" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +const dockerWorkshopOpenExample = ` + # Open Educates workshop in browser in current workshop directory + educates docker workshop open + + # Open Educates workshop in browser with provided name + educates docker workshop open --name my-workshop + + # Open Educates workshop in browser from specific path and using custom workshop file + educates docker workshop open --path ./workshop --workshop-file custom-workshop.yaml +` + type DockerWorkshopOpenOptions struct { Name string Path string @@ -25,8 +35,6 @@ type DockerWorkshopOpenOptions struct { } func (o *DockerWorkshopOpenOptions) Run() error { - var err error - var name = o.Name if name == "" { @@ -36,17 +44,22 @@ func (o *DockerWorkshopOpenOptions) Run() error { // the workshop will then expect the workshop definition to reside in the // resources/workshop.yaml file under the directory, the same as if a // directory path was provided explicitly. - if path == "" { path = "." } // Load the workshop definition. The path can be a HTTP/HTTPS URL for a // local file system path for a directory or file. - - var workshop *unstructured.Unstructured - - if workshop, err = loadWorkshopDefinition(o.Name, path, "educates-cli", o.WorkshopFile, o.WorkshopVersion, o.DataValuesFlags); err != nil { + definitionConfig := workshops.WorkshopDefinitionConfig{ + Name: o.Name, + Path: path, + Portal: constants.DefaultPortalName, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + workshop, err := workshops.LoadWorkshopDefinition(&definitionConfig) + if err != nil { return err } @@ -69,15 +82,14 @@ func (o *DockerWorkshopOpenOptions) Run() error { return errors.New("unable to find workshop") } - url, found := container.Config.Labels["training.educates.dev/url"] + url, found := container.Config.Labels[constants.EducatesWorkshopLabelAnnotationURL] if !found || url == "" { return errors.New("can't determine URL for workshop") } - // XXX Need a better way of handling very long startup times for container + // TODO: XXX Need a better way of handling very long startup times for container // due to workshop content or package downloads. - for i := 1; i < 120; i++ { time.Sleep(time.Second) @@ -93,22 +105,7 @@ func (o *DockerWorkshopOpenOptions) Run() error { break } - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - case "darwin": - err = exec.Command("open", url).Start() - default: - err = fmt.Errorf("unsupported platform") - } - - if err != nil { - return errors.Wrap(err, "unable to open web browser") - } - - return nil + return utils.OpenBrowser(url) } func (p *ProjectInfo) NewDockerWorkshopOpenCmd() *cobra.Command { @@ -119,6 +116,7 @@ func (p *ProjectInfo) NewDockerWorkshopOpenCmd() *cobra.Command { Use: "open", Short: "Open workshop in browser", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: dockerWorkshopOpenExample, } c.Flags().StringVarP( diff --git a/client-programs/pkg/cmd/local_cluster_create_cmd.go b/client-programs/pkg/cmd/local_cluster_create_cmd.go index 876e4392f..0d89d626c 100644 --- a/client-programs/pkg/cmd/local_cluster_create_cmd.go +++ b/client-programs/pkg/cmd/local_cluster_create_cmd.go @@ -15,6 +15,7 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/cluster" "github.com/educates/educates-training-platform/client-programs/pkg/config" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/docker" "github.com/educates/educates-training-platform/client-programs/pkg/installer" "github.com/educates/educates-training-platform/client-programs/pkg/registry" @@ -22,7 +23,24 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) -var ( +// SupportedKubernetesVersions returns a sorted list of supported Kubernetes versions +func SupportedKubernetesVersions() []string { + versions := make([]string, 0, len(constants.KubernetesVersionToKindImage)) + for v := range constants.KubernetesVersionToKindImage { + versions = append(versions, v) + } + // Sort in descending order (newest first) + for i := 0; i < len(versions)-1; i++ { + for j := i + 1; j < len(versions); j++ { + if versions[i] < versions[j] { + versions[i], versions[j] = versions[j], versions[i] + } + } + } + return versions +} + +const ( localClusterCreateExample = ` # Create local educates cluster (no configuration, uses nip.io wildcard domain and Kind as provider config defaults) educates local cluster create @@ -55,6 +73,7 @@ type LocalClusterCreateOptions struct { Config string Kubeconfig string ClusterImage string + KubernetesVersion string Domain string PackageRepository string Version string @@ -98,7 +117,7 @@ func (o *LocalClusterCreateOptions) Run() error { return err } - client, err := clusterConfig.Config.GetClient() + k8sClient, err := clusterConfig.Config.GetClient() if err != nil { return err @@ -107,29 +126,31 @@ func (o *LocalClusterCreateOptions) Run() error { // This creates the educates-secrets namespace if it doesn't exist and creates the // wildcard and CA secrets in there if !o.ClusterOnly { - if err = secrets.SyncLocalCachedSecretsToCluster(client); err != nil { + if err = secrets.SyncLocalCachedSecretsToCluster(k8sClient); err != nil { return err } } - if err = registry.DeployRegistryAndLinkToCluster(o.RegistryBindIP, client); err != nil { + reg := registry.NewRegistry(o.RegistryBindIP, k8sClient) + if err = reg.DeployAndLinkToCluster(); err != nil { return errors.Wrap(err, "failed to deploy registry") } // This is needed for imgpkg pull from locally published workshops - if err = registry.UpdateRegistryK8SService(client); err != nil { + if err = reg.UpdateK8SService(); err != nil { return errors.Wrap(err, "failed to create service for registry") } // This is for hugo livereload (educates serve-workshop) - if err = cluster.CreateLoopbackService(client, fullConfig.ClusterIngress.Domain); err != nil { + if err = cluster.CreateLoopbackService(k8sClient, fullConfig.ClusterIngress.Domain); err != nil { return err } // Create and add registry mirrors defined in config to Kind nodes - for _, mirror := range fullConfig.LocalKindCluster.RegistryMirrors { - if err = registry.DeployMirrorAndLinkToCluster(&mirror); err != nil { - return errors.Wrap(err, "failed to deploy registry mirror "+mirror.Mirror) + for _, mirrorCfg := range fullConfig.LocalKindCluster.RegistryMirrors { + mirror := registry.NewMirror(&mirrorCfg) + if err = mirror.DeployAndLinkToCluster(); err != nil { + return errors.Wrap(err, "failed to deploy registry mirror "+mirrorCfg.Mirror) } } @@ -141,7 +162,7 @@ func (o *LocalClusterCreateOptions) Run() error { } } - fmt.Println("Educates cluster has been created succesfully") + fmt.Println("🎓 Educates cluster has been created succesfully") return nil } @@ -160,6 +181,17 @@ func (p *ProjectInfo) NewLocalClusterCreateCmd() *cobra.Command { } o.RegistryBindIP = ip + // Validate kubernetes-version if provided + if o.KubernetesVersion != "" { + if _, ok := constants.KubernetesVersionToKindImage[o.KubernetesVersion]; !ok { + return fmt.Errorf("unsupported kubernetes version %q, supported versions are: %v", o.KubernetesVersion, SupportedKubernetesVersions()) + } + // If kind-cluster-image is not explicitly set, use the mapped image + if o.ClusterImage == "" { + o.ClusterImage = constants.KubernetesVersionToKindImage[o.KubernetesVersion] + } + } + return o.Run() }, Example: localClusterCreateExample, @@ -183,6 +215,12 @@ func (p *ProjectInfo) NewLocalClusterCreateCmd() *cobra.Command { "", "docker image to use when booting the kind cluster", ) + c.Flags().StringVar( + &o.KubernetesVersion, + "kubernetes-version", + constants.DefaultKubernetesVersion, + fmt.Sprintf("kubernetes version for the kind cluster (supported: %v)", SupportedKubernetesVersions()), + ) c.Flags().StringVar( &o.Domain, "domain", diff --git a/client-programs/pkg/cmd/local_cluster_delete_cmd.go b/client-programs/pkg/cmd/local_cluster_delete_cmd.go index ab9a7303b..01dbcbdc6 100644 --- a/client-programs/pkg/cmd/local_cluster_delete_cmd.go +++ b/client-programs/pkg/cmd/local_cluster_delete_cmd.go @@ -8,6 +8,14 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/resolver" ) +const localClusterDeleteExample = ` + # Delete the local Kubernetes cluster + educates local cluster delete + + # Delete the local Kubernetes cluster and all components (registry, mirrors and resolver) + educates local cluster delete --all +` + type LocalClusterDeleteOptions struct { Kubeconfig string AllComponents bool @@ -17,7 +25,8 @@ func (o *LocalClusterDeleteOptions) Run() error { c := cluster.NewKindClusterConfig("") if o.AllComponents { - registry.DeleteRegistry() + reg := registry.NewRegistry("", nil) + reg.Delete() resolver.DeleteResolver() // Delete all mirrors registry.DeleteRegistryMirrors() @@ -30,10 +39,11 @@ func (p *ProjectInfo) NewLocalClusterDeleteCmd() *cobra.Command { var o LocalClusterDeleteOptions var c = &cobra.Command{ - Args: cobra.NoArgs, - Use: "delete", - Short: "Deletes the local Kubernetes cluster", - RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Args: cobra.NoArgs, + Use: "delete", + Short: "Deletes the local Kubernetes cluster", + RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: localClusterDeleteExample, } c.Flags().BoolVar( diff --git a/client-programs/pkg/cmd/local_cluster_start_cmd.go b/client-programs/pkg/cmd/local_cluster_start_cmd.go index c7f7a2ac6..a9c990df3 100644 --- a/client-programs/pkg/cmd/local_cluster_start_cmd.go +++ b/client-programs/pkg/cmd/local_cluster_start_cmd.go @@ -6,6 +6,11 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/cluster" ) +const localClusterStartExample = ` + # Start the local Kubernetes cluster + educates local cluster start +` + func (p *ProjectInfo) NewLocalClusterStartCmd() *cobra.Command { var c = &cobra.Command{ Args: cobra.NoArgs, @@ -16,6 +21,7 @@ func (p *ProjectInfo) NewLocalClusterStartCmd() *cobra.Command { return c.StartCluster() }, + Example: localClusterStartExample, } return c diff --git a/client-programs/pkg/cmd/local_cluster_status_cmd.go b/client-programs/pkg/cmd/local_cluster_status_cmd.go index 912edec6a..c62b86feb 100644 --- a/client-programs/pkg/cmd/local_cluster_status_cmd.go +++ b/client-programs/pkg/cmd/local_cluster_status_cmd.go @@ -6,6 +6,11 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/cluster" ) +const localClusterStatusExample = ` + # Get status of the local Kubernetes cluster + educates local cluster status +` + func (p *ProjectInfo) NewLocalClusterStatusCmd() *cobra.Command { var c = &cobra.Command{ Args: cobra.NoArgs, @@ -16,6 +21,7 @@ func (p *ProjectInfo) NewLocalClusterStatusCmd() *cobra.Command { return c.ClusterStatus() }, + Example: localClusterStatusExample, } return c diff --git a/client-programs/pkg/cmd/local_cluster_stop_cmd.go b/client-programs/pkg/cmd/local_cluster_stop_cmd.go index c7685dc0b..f7b419aaa 100644 --- a/client-programs/pkg/cmd/local_cluster_stop_cmd.go +++ b/client-programs/pkg/cmd/local_cluster_stop_cmd.go @@ -6,6 +6,11 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/cluster" ) +const localClusterStopExample = ` + # Stop the local Kubernetes cluster + educates local cluster stop +` + func (p *ProjectInfo) NewLocalClusterStopCmd() *cobra.Command { var c = &cobra.Command{ Args: cobra.NoArgs, @@ -16,6 +21,7 @@ func (p *ProjectInfo) NewLocalClusterStopCmd() *cobra.Command { return c.StopCluster() }, + Example: localClusterStopExample, } return c diff --git a/client-programs/pkg/cmd/local_config_edit_cmd.go b/client-programs/pkg/cmd/local_config_edit_cmd.go index 2b76ce1b6..0758f7d9f 100644 --- a/client-programs/pkg/cmd/local_config_edit_cmd.go +++ b/client-programs/pkg/cmd/local_config_edit_cmd.go @@ -1,93 +1,33 @@ package cmd import ( - "fmt" - "os" - "os/exec" - "path" - - "github.com/pkg/errors" - "github.com/spf13/cobra" "github.com/educates/educates-training-platform/client-programs/pkg/config" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/spf13/cobra" ) +const localConfigEditExample = ` + # Edit the local configuration + educates local config edit +` + +type LocalConfigEditOptions struct{} + +func (o *LocalConfigEditOptions) Run() error { + c := config.LocalConfigEditConfig{} + return c.Edit() +} + func (p *ProjectInfo) NewLocalConfigEditCmd() *cobra.Command { + var o LocalConfigEditOptions + var c = &cobra.Command{ Args: cobra.NoArgs, Use: "edit", Short: "Edit local configuration", RunE: func(_ *cobra.Command, _ []string) error { - err := os.MkdirAll(utils.GetEducatesHomeDir(), os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create configuration directory %q", utils.GetEducatesHomeDir()) - } - - valuesFilePath := path.Join(utils.GetEducatesHomeDir(), "values.yaml") - tmpValuesFilePath := fmt.Sprintf("%s.%d", valuesFilePath, os.Getpid()) - - tmpValuesFile, err := os.OpenFile(tmpValuesFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create local configuration file %q", tmpValuesFilePath) - } - - valuesFileData, err := os.ReadFile(valuesFilePath) - - if err == nil && len(valuesFileData) != 0 { - tmpValuesFile.Write(valuesFileData) - } - - tmpValuesFile.Close() - - defer os.Remove(tmpValuesFilePath) - - editor := "vi" - - if s := os.Getenv("EDITOR"); s != "" { - editor = s - } - - editorPath, err := exec.LookPath(editor) - - if err != nil { - return errors.Wrapf(err, "unable to determine path for editor %q", editor) - - } - - cmd := exec.Command(editorPath, tmpValuesFilePath) - - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - err = cmd.Start() - - if err != nil { - return errors.Wrapf(err, "cannot execute editor on configuration") - } - - err = cmd.Wait() - - if err != nil { - return errors.Wrapf(err, "editing of values configuration failed") - } - - _, err = config.NewInstallationConfigFromFile(tmpValuesFilePath) - - if err != nil { - return errors.Wrapf(err, "error in values configuration file") - } - - err = os.Rename(tmpValuesFilePath, valuesFilePath) - - if err != nil { - return errors.Wrapf(err, "unable to update default configuration") - } - - return nil + return o.Run() }, + Example: localConfigEditExample, } return c diff --git a/client-programs/pkg/cmd/local_config_reset_cmd.go b/client-programs/pkg/cmd/local_config_reset_cmd.go index 450b5ef8e..c7ab95b05 100644 --- a/client-programs/pkg/cmd/local_config_reset_cmd.go +++ b/client-programs/pkg/cmd/local_config_reset_cmd.go @@ -4,22 +4,37 @@ import ( "os" "path" - "github.com/spf13/cobra" "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/spf13/cobra" ) +const localConfigResetExample = ` + # Reset the local configuration + educates local config reset +` + +type LocalConfigResetOptions struct {} + +func (o *LocalConfigResetOptions) Run() error { + // TODO: Move "values.yaml" to a constant + valuesFile := path.Join(utils.GetEducatesHomeDir(), "values.yaml") + + os.Remove(valuesFile) + + return nil +} + func (p *ProjectInfo) NewLocalConfigResetCmd() *cobra.Command { + var o LocalConfigResetOptions + var c = &cobra.Command{ Args: cobra.NoArgs, Use: "reset", Short: "Reset local configuration", RunE: func(_ *cobra.Command, _ []string) error { - valuesFile := path.Join(utils.GetEducatesHomeDir(), "values.yaml") - - os.Remove(valuesFile) - - return nil + return o.Run() }, + Example: localConfigResetExample, } return c diff --git a/client-programs/pkg/cmd/local_config_view_cmd.go b/client-programs/pkg/cmd/local_config_view_cmd.go index cbdb22240..c78dbeca8 100644 --- a/client-programs/pkg/cmd/local_config_view_cmd.go +++ b/client-programs/pkg/cmd/local_config_view_cmd.go @@ -3,13 +3,13 @@ package cmd import ( "fmt" + "github.com/educates/educates-training-platform/client-programs/pkg/config" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/config" "gopkg.in/yaml.v2" ) -var ( +const ( localConfigViewExample = ` # View local educates cluster configuration by default. Uses nip.io wildcard domain and Kind as provider config defaults educates local config view --config NULL @@ -54,7 +54,7 @@ func (p *ProjectInfo) NewLocalConfigViewCmd() *cobra.Command { Args: cobra.NoArgs, Use: "view", Short: "View local configuration", - Long: "View local configuration. Uses nip.io wildcard domain and Kind as provider config defaults", + Long: "View local configuration. Uses nip.io wildcard domain and Kind as provider config defaults if not specified", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, Example: localConfigViewExample, } diff --git a/client-programs/pkg/cmd/local_mirror_cmd_group.go b/client-programs/pkg/cmd/local_mirror_cmd_group.go index ad3588914..031dc30e7 100644 --- a/client-programs/pkg/cmd/local_mirror_cmd_group.go +++ b/client-programs/pkg/cmd/local_mirror_cmd_group.go @@ -8,19 +8,20 @@ import ( func (p *ProjectInfo) NewLocalMirrorCmdGroup() *cobra.Command { var c = &cobra.Command{ Use: "mirror", + Aliases: []string{"mirrors"}, Short: "Manage local image registry mirrors", } // Use a command group as it allows us to dictate the order in which they // are displayed in the help message, as otherwise they are displayed in // sort order. - commandGroups := templates.CommandGroups{ { Message: "Available Commands:", Commands: []*cobra.Command{ p.NewLocalMirrorDeployCmd(), p.NewLocalMirrorDeleteCmd(), + p.NewLocalMirrorListCmd(), }, }, } diff --git a/client-programs/pkg/cmd/local_mirror_delete_cmd.go b/client-programs/pkg/cmd/local_mirror_delete_cmd.go index 8ecb53fea..24d39a764 100644 --- a/client-programs/pkg/cmd/local_mirror_delete_cmd.go +++ b/client-programs/pkg/cmd/local_mirror_delete_cmd.go @@ -5,9 +5,10 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/config" "github.com/educates/educates-training-platform/client-programs/pkg/registry" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) -var ( +const ( localMirrorDeleteExample = ` # Delete a local image registry mirror educates local mirror delete mymirror @@ -23,14 +24,20 @@ func (o *LocalMirrorDeleteOptions) Run() error { Mirror: o.MirrorName, } - return registry.DeleteMirrorAndUnlinkFromCluster(mirrorConfig) + mirror := registry.NewMirror(mirrorConfig) + return mirror.DeleteAndUnlinkFromCluster() } func (p *ProjectInfo) NewLocalMirrorDeleteCmd() *cobra.Command { var o LocalMirrorDeleteOptions var c = &cobra.Command{ - Args: cobra.ExactArgs(1), + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "name is required", "NAME") + } + return nil + }, Use: "delete NAME", Short: "Deletes the local image registry mirror", RunE: func(_ *cobra.Command, args []string) error { o.MirrorName = args[0]; return o.Run() }, diff --git a/client-programs/pkg/cmd/local_mirror_deploy_cmd.go b/client-programs/pkg/cmd/local_mirror_deploy_cmd.go index baf03678c..c26c41e27 100644 --- a/client-programs/pkg/cmd/local_mirror_deploy_cmd.go +++ b/client-programs/pkg/cmd/local_mirror_deploy_cmd.go @@ -8,17 +8,17 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/registry" ) -var ( +const ( localMirrorDeployExample = ` # Mirror DockerHub anonymously (may be subject to rate limits): educates local mirror deploy docker.io # Mirror DockerHub with credentials (recommended to avoid throttling): educates local mirror deploy docker.io --username --password - + # Mirror a private registry: educates local mirror deploy myprivateregistry.com --username --password - + # Mirror a registry with a different remote URL: educates local mirror deploy mymirror --url registry.example.com ` @@ -39,7 +39,8 @@ func (o *LocalMirrorDeployOptions) Run() error { Password: o.Password, } - err := registry.DeployMirrorAndLinkToCluster(mirrorConfig) + mirror := registry.NewMirror(mirrorConfig) + err := mirror.DeployAndLinkToCluster() if err != nil { return errors.Wrap(err, "failed to deploy registry mirror") diff --git a/client-programs/pkg/cmd/local_mirror_list_cmd.go b/client-programs/pkg/cmd/local_mirror_list_cmd.go new file mode 100644 index 000000000..64d442b55 --- /dev/null +++ b/client-programs/pkg/cmd/local_mirror_list_cmd.go @@ -0,0 +1,47 @@ +package cmd + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/educates/educates-training-platform/client-programs/pkg/registry" +) + +const ( + localMirroListExample = ` + # List all local image registry mirrors + educates local mirror list +` +) + +type LocalMirrorListOptions struct {} + +func (o *LocalMirrorListOptions) Run() error { + list, err := registry.ListRegistryMirrors() + + if err != nil { + return errors.Wrap(err, "failed to deploy registry mirror") + } + + fmt.Println(list) + + return nil +} + +func (p *ProjectInfo) NewLocalMirrorListCmd() *cobra.Command { + var o LocalMirrorListOptions + + var c = &cobra.Command{ + Args: cobra.NoArgs, + Use: "list", + Short: "List all local image registry mirrors", + RunE: func(_ *cobra.Command, _ []string) error { + return o.Run() + }, + Example: localMirroListExample, + } + + return c +} diff --git a/client-programs/pkg/cmd/local_registry_delete_cmd.go b/client-programs/pkg/cmd/local_registry_delete_cmd.go index 36f7e04c2..983923b83 100644 --- a/client-programs/pkg/cmd/local_registry_delete_cmd.go +++ b/client-programs/pkg/cmd/local_registry_delete_cmd.go @@ -6,12 +6,21 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/registry" ) +const localRegistryDeleteExample = ` + # Delete the local image registry + educates local registry delete +` + func (p *ProjectInfo) NewLocalRegistryDeleteCmd() *cobra.Command { var c = &cobra.Command{ Args: cobra.NoArgs, Use: "delete", Short: "Deletes the local image registry", - RunE: func(_ *cobra.Command, _ []string) error { return registry.DeleteRegistry() }, + RunE: func(_ *cobra.Command, _ []string) error { + reg := registry.NewRegistry("", nil) + return reg.Delete() + }, + Example: localRegistryDeleteExample, } return c diff --git a/client-programs/pkg/cmd/local_registry_deploy_cmd.go b/client-programs/pkg/cmd/local_registry_deploy_cmd.go index e6c7d7346..b8de58046 100644 --- a/client-programs/pkg/cmd/local_registry_deploy_cmd.go +++ b/client-programs/pkg/cmd/local_registry_deploy_cmd.go @@ -11,39 +11,53 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) +const localRegistryDeployExample = ` + # Deploy the local image registry + educates local registry deploy + + # Deploy the local image registry with a custom bind IP + educates local registry deploy --bind-ip 192.168.1.100 + + # Deploy the local image registry with a custom kubeconfig + educates local registry deploy --kubeconfig /path/to/kubeconfig --context my-context +` + type LocalRegistryDeployOptions struct { KubeconfigOptions BindIP string } func (o *LocalRegistryDeployOptions) Run() error { - err := registry.DeployRegistry(o.BindIP) - - if err != nil { - return errors.Wrap(err, "failed to deploy registry") - } - // This will fail if you do not have a Kubernetes cluster, but we can still // deploy just the image registry alone without Kubernetes. If a Kubernetes // cluster is created later, then the registry service will be added then. - clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context) + var client *registry.Registry + if err != nil { fmt.Println("Warning: Kubernetes cluster not available") - return nil + client = registry.NewRegistry(o.BindIP, nil) + } else { + k8sClient, err := clusterConfig.GetClient() + if err != nil { + fmt.Println("Warning: Kubernetes cluster not updated with registry service.") + client = registry.NewRegistry(o.BindIP, nil) + } else { + client = registry.NewRegistry(o.BindIP, k8sClient) + } } - client, err := clusterConfig.GetClient() - + err = client.Deploy() if err != nil { - fmt.Println("Warning: Kubernetes cluster not updated with registry service.") - - return nil + return errors.Wrap(err, "failed to deploy registry") } - if err = registry.UpdateRegistryK8SService(client); err != nil { - return errors.Wrap(err, "failed to create service for registry") + if client != nil { + if err = client.UpdateK8SService(); err != nil { + // Don't fail if we can't update the K8s service, just warn + fmt.Println("Warning: Kubernetes cluster not updated with registry service.") + } } return nil @@ -65,6 +79,7 @@ func (p *ProjectInfo) NewLocalRegistryDeployCmd() *cobra.Command { return o.Run() }, + Example: localRegistryDeployExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/local_registry_prune_cmd.go b/client-programs/pkg/cmd/local_registry_prune_cmd.go index 3becc460c..14af516ad 100644 --- a/client-programs/pkg/cmd/local_registry_prune_cmd.go +++ b/client-programs/pkg/cmd/local_registry_prune_cmd.go @@ -7,11 +7,17 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/registry" ) +const localRegistryPruneExample = ` + # Prune the local image registry + educates local registry prune +` + type LocalRegistryPruneOptions struct { } func (o *LocalRegistryPruneOptions) Run() error { - err := registry.PruneRegistry() + reg := registry.NewRegistry("", nil) + err := reg.Prune() if err != nil { return errors.Wrap(err, "failed to prune registry") @@ -28,6 +34,7 @@ func (p *ProjectInfo) NewLocalRegistryPruneCmd() *cobra.Command { Use: "prune", Short: "Prunes the local image registry (deletes any untagged image)", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: localRegistryPruneExample, } return c diff --git a/client-programs/pkg/cmd/local_resolver_delete_cmd.go b/client-programs/pkg/cmd/local_resolver_delete_cmd.go index ac00c1d37..51a04c96d 100644 --- a/client-programs/pkg/cmd/local_resolver_delete_cmd.go +++ b/client-programs/pkg/cmd/local_resolver_delete_cmd.go @@ -6,12 +6,18 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/resolver" ) +const localResolverDeleteExample = ` + # Delete the local DNS resolver + educates local resolver delete +` + func (p *ProjectInfo) NewLocalResolverDeleteCmd() *cobra.Command { var c = &cobra.Command{ Args: cobra.NoArgs, Use: "delete", Short: "Deletes the local DNS resolver", RunE: func(_ *cobra.Command, _ []string) error { return resolver.DeleteResolver() }, + Example: localResolverDeleteExample, } return c diff --git a/client-programs/pkg/cmd/local_resolver_deploy_cmd.go b/client-programs/pkg/cmd/local_resolver_deploy_cmd.go index aaf643e7b..b18fd8053 100644 --- a/client-programs/pkg/cmd/local_resolver_deploy_cmd.go +++ b/client-programs/pkg/cmd/local_resolver_deploy_cmd.go @@ -7,6 +7,17 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/resolver" ) +const localResolverDeployExample = ` + # Deploy the local DNS resolver + educates local resolver deploy + + # Deploy the local DNS resolver with a custom config + educates local resolver deploy --config /path/to/config.yaml + + # Deploy the local DNS resolver with a custom domain + educates local resolver deploy --domain test.educates.io +` + type LocalResolverDeployOptions struct { Config string Domain string @@ -41,6 +52,7 @@ func (p *ProjectInfo) NewLocalResolverDeployCmd() *cobra.Command { Use: "deploy", Short: "Deploys a local DNS resolver", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: localResolverDeployExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/local_resolver_update_cmd.go b/client-programs/pkg/cmd/local_resolver_update_cmd.go index 93c0648bc..82a4cf7a0 100644 --- a/client-programs/pkg/cmd/local_resolver_update_cmd.go +++ b/client-programs/pkg/cmd/local_resolver_update_cmd.go @@ -7,6 +7,17 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/resolver" ) +const localResolverUpdateExample = ` + # Update the local DNS resolver + educates local resolver update + + # Update the local DNS resolver with a custom config + educates local resolver update --config /path/to/config.yaml + + # Update the local DNS resolver with a custom domain + educates local resolver update --domain test.educates.io +` + type LocalResolverUpdateOptions struct { Config string Domain string @@ -37,6 +48,7 @@ func (p *ProjectInfo) NewLocalResolverUpdateCmd() *cobra.Command { Use: "update", Short: "Updates the local DNS resolver", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: localResolverUpdateExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/local_secrets_add_ca_cmd.go b/client-programs/pkg/cmd/local_secrets_add_ca_cmd.go new file mode 100644 index 000000000..b6220e0be --- /dev/null +++ b/client-programs/pkg/cmd/local_secrets_add_ca_cmd.go @@ -0,0 +1,142 @@ +package cmd + +import ( + "encoding/json" + "os" + "path" + "regexp" + + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" +) + +const localSecretsAddCaExample = ` + # Create a CA secret + educates local secrets add ca my-ca + + # Create a CA secret with a custom domain + educates local secrets add ca my-ca --domain my-domain.com + + # Create a CA secret with a custom certificate file + educates local secrets add ca my-ca --cert /path/to/ca.crt +` + +type LocalSecretsAddCaOptions struct { + CertFile string + IngressDomain string +} + +func (o *LocalSecretsAddCaOptions) Run(name string) error { + var err error + var matched bool + + if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { + return errors.Wrapf(err, "regex match on secret name failed") + } + + if !matched { + return errors.New("invalid secret name") + } + + var certificateFileData []byte + + if o.CertFile != "" { + certificateFileData, err = os.ReadFile(o.CertFile) + + if err != nil { + return errors.Wrapf(err, "failed to read certificate file %s", o.CertFile) + } + } + + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: map[string]string{}, + }, + // Type: "kubernetes.io/tls", + Data: map[string][]byte{ + "ca.crt": certificateFileData, + }, + } + + if o.IngressDomain != "" { + secret.ObjectMeta.Annotations[constants.EducatesTrainingLabelAnnotationDomain] = o.IngressDomain + } + + secretData, err := json.MarshalIndent(&secret, "", " ") + + if err != nil { + return errors.Wrap(err, "failed to generate secret data") + } + + secretData, err = yaml.JSONToYAML(secretData) + + if err != nil { + return errors.Wrap(err, "failed to generate YAML data") + } + + secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") + + err = os.MkdirAll(secretsCacheDir, os.ModePerm) + + if err != nil { + return errors.Wrapf(err, "unable to create secrets cache directory") + } + + secretFilePath := path.Join(secretsCacheDir, name+".yaml") + + secretFile, err := os.OpenFile(secretFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) + + if err != nil { + return errors.Wrapf(err, "unable to create secret file %s", secretFilePath) + } + + if _, err := secretFile.Write(secretData); err != nil { + return errors.Wrapf(err, "unable to write secret file %s", secretFilePath) + } + + if err := secretFile.Close(); err != nil { + return errors.Wrapf(err, "unable to close secret file %s", secretFilePath) + } + + return nil +} + +func (p *ProjectInfo) NewLocalSecretsAddCaCmd() *cobra.Command { + var o LocalSecretsAddCaOptions + + var c = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "name is required", "NAME") + } + return nil + }, + Use: "ca NAME", + Short: "Create a CA secret", + RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, + Example: localSecretsAddCaExample, + } + + c.Flags().StringVar( + &o.CertFile, + "cert", + "", + "path to PEM encoded CA certificate", + ) + c.Flags().StringVar( + &o.IngressDomain, + "domain", + "", + "wildcard ingress domain matching certificate", + ) + + c.MarkFlagsRequiredTogether("cert") + + return c +} diff --git a/client-programs/pkg/cmd/local_secrets_add_cmd.go b/client-programs/pkg/cmd/local_secrets_add_cmd.go deleted file mode 100644 index eadd56184..000000000 --- a/client-programs/pkg/cmd/local_secrets_add_cmd.go +++ /dev/null @@ -1,441 +0,0 @@ -package cmd - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "os" - "path" - "regexp" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/yaml" -) - -func (p *ProjectInfo) NewLocalSecretsAddCmdGroup() *cobra.Command { - var c = &cobra.Command{ - Args: cobra.NoArgs, - Use: "add", - Short: "Add secret to the cache", - } - - // Use a command group as it allows us to dictate the order in which they - // are displayed in the help message, as otherwise they are displayed in - // sort order. - - commandGroups := templates.CommandGroups{ - { - Message: "Available Commands:", - Commands: []*cobra.Command{ - p.NewLocalSecretsAddCaCmd(), - p.NewLocalSecretsAddDockerRegistryCmd(), - // NewLocalSecretsAddGenericCmd(), - p.NewLocalSecretsAddTlsCmd(), - }, - }, - } - - commandGroups.Add(c) - - templates.ActsAsRootCommand(c, []string{"--help"}, commandGroups...) - - return c -} - -type LocalSecretsAddTlsOptions struct { - CertFile string - KeyFile string - IngressDomain string -} - -func (o *LocalSecretsAddTlsOptions) Run(name string) error { - var err error - var matched bool - - if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { - return errors.Wrapf(err, "regex match on secret name failed") - } - - if !matched { - return errors.New("invalid secret name") - } - - var certificateFileData []byte - var certificateKeyFileData []byte - - if o.CertFile != "" { - certificateFileData, err = os.ReadFile(o.CertFile) - - if err != nil { - return errors.Wrapf(err, "failed to read certificate file %s", o.CertFile) - } - } - - if o.KeyFile != "" { - certificateKeyFileData, err = os.ReadFile(o.KeyFile) - - if err != nil { - return errors.Wrapf(err, "failed to read certificate key file %s", o.KeyFile) - } - } - - secret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{}, - }, - Type: "kubernetes.io/tls", - Data: map[string][]byte{ - "tls.crt": certificateFileData, - "tls.key": certificateKeyFileData, - }, - } - - if o.IngressDomain != "" { - secret.ObjectMeta.Annotations["training.educates.dev/domain"] = o.IngressDomain - } - - secretData, err := json.MarshalIndent(&secret, "", " ") - - if err != nil { - return errors.Wrap(err, "failed to generate secret data") - } - - secretData, err = yaml.JSONToYAML(secretData) - - if err != nil { - return errors.Wrap(err, "failed to generate YAML data") - } - - secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") - - err = os.MkdirAll(secretsCacheDir, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create secrets cache directory") - } - - secretFilePath := path.Join(secretsCacheDir, name+".yaml") - - secretFile, err := os.OpenFile(secretFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create secret file %s", secretFilePath) - } - - if _, err := secretFile.Write(secretData); err != nil { - return errors.Wrapf(err, "unable to write secret file %s", secretFilePath) - } - - if err := secretFile.Close(); err != nil { - return errors.Wrapf(err, "unable to close secret file %s", secretFilePath) - } - - return nil -} - -func (p *ProjectInfo) NewLocalSecretsAddTlsCmd() *cobra.Command { - var o LocalSecretsAddTlsOptions - - var c = &cobra.Command{ - Args: cobra.ExactArgs(1), - Use: "tls NAME", - Short: "Create a TLS secret", - RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, - } - - c.Flags().StringVar( - &o.CertFile, - "cert", - "", - "path to PEM encoded public key certificate", - ) - c.Flags().StringVar( - &o.KeyFile, - "key", - "", - "path to private key associated with given certificate", - ) - c.Flags().StringVar( - &o.IngressDomain, - "domain", - "", - "wildcard ingress domain matching certificate", - ) - - c.MarkFlagsRequiredTogether("cert", "key") - - return c -} - -type LocalSecretsAddCaOptions struct { - CertFile string - IngressDomain string -} - -func (o *LocalSecretsAddCaOptions) Run(name string) error { - var err error - var matched bool - - if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { - return errors.Wrapf(err, "regex match on secret name failed") - } - - if !matched { - return errors.New("invalid secret name") - } - - var certificateFileData []byte - - if o.CertFile != "" { - certificateFileData, err = os.ReadFile(o.CertFile) - - if err != nil { - return errors.Wrapf(err, "failed to read certificate file %s", o.CertFile) - } - } - - secret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: map[string]string{}, - }, - // Type: "kubernetes.io/tls", - Data: map[string][]byte{ - "ca.crt": certificateFileData, - }, - } - - if o.IngressDomain != "" { - secret.ObjectMeta.Annotations["training.educates.dev/domain"] = o.IngressDomain - } - - secretData, err := json.MarshalIndent(&secret, "", " ") - - if err != nil { - return errors.Wrap(err, "failed to generate secret data") - } - - secretData, err = yaml.JSONToYAML(secretData) - - if err != nil { - return errors.Wrap(err, "failed to generate YAML data") - } - - secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") - - err = os.MkdirAll(secretsCacheDir, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create secrets cache directory") - } - - secretFilePath := path.Join(secretsCacheDir, name+".yaml") - - secretFile, err := os.OpenFile(secretFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create secret file %s", secretFilePath) - } - - if _, err := secretFile.Write(secretData); err != nil { - return errors.Wrapf(err, "unable to write secret file %s", secretFilePath) - } - - if err := secretFile.Close(); err != nil { - return errors.Wrapf(err, "unable to close secret file %s", secretFilePath) - } - - return nil -} - -func (p *ProjectInfo) NewLocalSecretsAddCaCmd() *cobra.Command { - var o LocalSecretsAddCaOptions - - var c = &cobra.Command{ - Args: cobra.ExactArgs(1), - Use: "ca NAME", - Short: "Create a CA secret", - RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, - } - - c.Flags().StringVar( - &o.CertFile, - "cert", - "", - "path to PEM encoded CA certificate", - ) - c.Flags().StringVar( - &o.IngressDomain, - "domain", - "", - "wildcard ingress domain matching certificate", - ) - - c.MarkFlagsRequiredTogether("cert") - - return c -} - -type LocalSecretsAddDockerRegistryOptions struct { - Server string - Username string - Password string - Email string -} - -func (o *LocalSecretsAddDockerRegistryOptions) Run(name string) error { - var err error - var matched bool - - if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { - return errors.Wrapf(err, "regex match on secret name failed") - } - - if !matched { - return errors.New("invalid secret name") - } - - authString := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", o.Username, o.Password))) - - dockerConfig := map[string]interface{}{ - "auths": map[string]interface{}{ - o.Server: map[string]string{ - "username": o.Username, - "password": o.Password, - "email": o.Email, - "auth": authString, - }, - }, - } - - dockerConfigData, _ := json.Marshal(dockerConfig) - - secret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Type: "kubernetes.io/dockerconfigjson", - Data: map[string][]byte{ - ".dockerconfigjson": dockerConfigData, - }, - } - - secretData, err := json.MarshalIndent(&secret, "", " ") - - if err != nil { - return errors.Wrap(err, "failed to generate secret data") - } - - secretData, err = yaml.JSONToYAML(secretData) - - if err != nil { - return errors.Wrap(err, "failed to generate YAML data") - } - - secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") - - err = os.MkdirAll(secretsCacheDir, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create secrets cache directory") - } - - secretFilePath := path.Join(secretsCacheDir, name+".yaml") - - secretFile, err := os.OpenFile(secretFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create secret file %s", secretFile) - } - - if _, err = secretFile.Write(secretData); err != nil { - return errors.Wrapf(err, "unable to write secret file %s", secretFile) - } - - if err := secretFile.Close(); err != nil { - return errors.Wrapf(err, "unable to close secret file %s", secretFile) - } - - return nil -} - -func (p *ProjectInfo) NewLocalSecretsAddDockerRegistryCmd() *cobra.Command { - var o LocalSecretsAddDockerRegistryOptions - - var c = &cobra.Command{ - Args: cobra.ExactArgs(1), - Use: "docker-registry NAME", - Short: "Create a secret for use with a Docker registry", - RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, - } - - c.Flags().StringVar( - &o.Server, - "docker-server", - "https://index.docker.io/v1/", - "server location for docker registry", - ) - c.Flags().StringVar( - &o.Username, - "docker-username", - "", - "username for docker registry authentication", - ) - c.Flags().StringVar( - &o.Password, - "docker-password", - "", - "password for docker registry authentication", - ) - c.Flags().StringVar( - &o.Email, - "docker-email", - "", - "email for docker registry", - ) - - c.MarkFlagsRequiredTogether("docker-username", "docker-password", "docker-email") - - return c -} - -type LocalSecretsAddGenericOptions struct { - FileSources []string - LiteralSources []string -} - -func (o *LocalSecretsAddGenericOptions) Run(name string) error { - return nil -} - -func (p *ProjectInfo) NewLocalSecretsAddGenericCmd() *cobra.Command { - var o LocalSecretsAddGenericOptions - - var c = &cobra.Command{ - Args: cobra.ExactArgs(1), - Use: "generic NAME", - Short: "Create a secret from a local file, directory, or literal value", - RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, - } - - c.Flags().StringArrayVar( - &o.FileSources, - "from-file", - []string{}, - "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is avalid secret key.", - ) - c.Flags().StringArrayVar( - &o.LiteralSources, - "from-literal", - []string{}, - "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)", - ) - - return c -} diff --git a/client-programs/pkg/cmd/local_secrets_add_cmd_group.go b/client-programs/pkg/cmd/local_secrets_add_cmd_group.go new file mode 100644 index 000000000..1dd845c28 --- /dev/null +++ b/client-programs/pkg/cmd/local_secrets_add_cmd_group.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/templates" +) + +func (p *ProjectInfo) NewLocalSecretsAddCmdGroup() *cobra.Command { + var c = &cobra.Command{ + Args: cobra.NoArgs, + Use: "add", + Short: "Add secret to the cache", + } + + // Use a command group as it allows us to dictate the order in which they + // are displayed in the help message, as otherwise they are displayed in + // sort order. + commandGroups := templates.CommandGroups{ + { + Message: "Available Commands:", + Commands: []*cobra.Command{ + p.NewLocalSecretsAddCaCmd(), + p.NewLocalSecretsAddDockerRegistryCmd(), + // NewLocalSecretsAddGenericCmd(), + p.NewLocalSecretsAddTlsCmd(), + }, + }, + } + + commandGroups.Add(c) + + templates.ActsAsRootCommand(c, []string{"--help"}, commandGroups...) + + return c +} diff --git a/client-programs/pkg/cmd/local_secrets_add_generic_cmd.go b/client-programs/pkg/cmd/local_secrets_add_generic_cmd.go new file mode 100644 index 000000000..5f6e1ee3e --- /dev/null +++ b/client-programs/pkg/cmd/local_secrets_add_generic_cmd.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/spf13/cobra" +) + +const localSecretsAddGenericExample = ` + # Create a secret from a local file + educates local secrets add generic my-secret --from-file /path/to/file + + # Create a secret from a local directory + educates local secrets add generic my-secret --from-literal key=value +` + +type LocalSecretsAddGenericOptions struct { + FileSources []string + LiteralSources []string +} + +func (o *LocalSecretsAddGenericOptions) Run(name string) error { + return nil +} + +func (p *ProjectInfo) NewLocalSecretsAddGenericCmd() *cobra.Command { + var o LocalSecretsAddGenericOptions + + var c = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "name is required", "NAME") + } + return nil + }, + + Use: "generic NAME", + Short: "Create a secret from a local file, directory, or literal value", + RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, + Example: localSecretsAddGenericExample, + } + + c.Flags().StringArrayVar( + &o.FileSources, + "from-file", + []string{}, + "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is avalid secret key.", + ) + c.Flags().StringArrayVar( + &o.LiteralSources, + "from-literal", + []string{}, + "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)", + ) + + return c +} diff --git a/client-programs/pkg/cmd/local_secrets_add_registry_cmd.go b/client-programs/pkg/cmd/local_secrets_add_registry_cmd.go new file mode 100644 index 000000000..f6589b281 --- /dev/null +++ b/client-programs/pkg/cmd/local_secrets_add_registry_cmd.go @@ -0,0 +1,154 @@ +package cmd + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path" + "regexp" + + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" +) + +const localSecretsAddDockerRegistryExample = ` + # Create a secret for use with Docker hub + educates local secrets add docker-registry my-registry --docker-username my-username --docker-password my-password --docker-email my-email + + # Create a secret for use with GitHub Container Registry + educates local secrets add docker-registry my-registry --docker-server https://ghcr.io --docker-username my-username --docker-password my-password --docker-email my-email +` + +type LocalSecretsAddDockerRegistryOptions struct { + Server string + Username string + Password string + Email string +} + +func (o *LocalSecretsAddDockerRegistryOptions) Run(name string) error { + var err error + var matched bool + + if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { + return errors.Wrapf(err, "regex match on secret name failed") + } + + if !matched { + return errors.New("invalid secret name") + } + + authString := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", o.Username, o.Password))) + + dockerConfig := map[string]interface{}{ + "auths": map[string]interface{}{ + o.Server: map[string]string{ + "username": o.Username, + "password": o.Password, + "email": o.Email, + "auth": authString, + }, + }, + } + + dockerConfigData, _ := json.Marshal(dockerConfig) + + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Type: "kubernetes.io/dockerconfigjson", + Data: map[string][]byte{ + ".dockerconfigjson": dockerConfigData, + }, + } + + secretData, err := json.MarshalIndent(&secret, "", " ") + + if err != nil { + return errors.Wrap(err, "failed to generate secret data") + } + + secretData, err = yaml.JSONToYAML(secretData) + + if err != nil { + return errors.Wrap(err, "failed to generate YAML data") + } + + secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") + + err = os.MkdirAll(secretsCacheDir, os.ModePerm) + + if err != nil { + return errors.Wrapf(err, "unable to create secrets cache directory") + } + + secretFilePath := path.Join(secretsCacheDir, name+".yaml") + + secretFile, err := os.OpenFile(secretFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) + + if err != nil { + return errors.Wrapf(err, "unable to create secret file %s", secretFilePath) + } + + if _, err = secretFile.Write(secretData); err != nil { + return errors.Wrapf(err, "unable to write secret file %s", secretFilePath) + } + + if err := secretFile.Close(); err != nil { + return errors.Wrapf(err, "unable to close secret file %s", secretFilePath) + } + + return nil +} + +func (p *ProjectInfo) NewLocalSecretsAddDockerRegistryCmd() *cobra.Command { + var o LocalSecretsAddDockerRegistryOptions + + var c = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "name is required", "NAME") + } + return nil + }, + Use: "docker-registry NAME", + Short: "Create a secret for use with a Docker registry", + RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, + Example: localSecretsAddDockerRegistryExample, + } + + c.Flags().StringVar( + &o.Server, + "docker-server", + "https://index.docker.io/v1/", + "server location for docker registry", + ) + c.Flags().StringVar( + &o.Username, + "docker-username", + "", + "username for docker registry authentication", + ) + c.Flags().StringVar( + &o.Password, + "docker-password", + "", + "password for docker registry authentication", + ) + c.Flags().StringVar( + &o.Email, + "docker-email", + "", + "email for docker registry", + ) + + c.MarkFlagsRequiredTogether("docker-username", "docker-password", "docker-email") + + return c +} diff --git a/client-programs/pkg/cmd/local_secrets_add_tls_cmd.go b/client-programs/pkg/cmd/local_secrets_add_tls_cmd.go new file mode 100644 index 000000000..c8a95891f --- /dev/null +++ b/client-programs/pkg/cmd/local_secrets_add_tls_cmd.go @@ -0,0 +1,156 @@ +package cmd + +import ( + "encoding/json" + "os" + "path" + "regexp" + + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" +) + +const localSecretsAddTlsExample = ` + # Create a TLS secret + educates local secrets add tls my-tls --cert /path/to/cert.pem --key /path/to/key.pem + + # Create a TLS secret with a custom domain + educates local secrets add tls my-tls --cert /path/to/cert.pem --key /path/to/key.pem --domain my-domain.com +` + +type LocalSecretsAddTlsOptions struct { + CertFile string + KeyFile string + IngressDomain string +} + +func (o *LocalSecretsAddTlsOptions) Run(name string) error { + var err error + var matched bool + + if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { + return errors.Wrapf(err, "regex match on secret name failed") + } + + if !matched { + return errors.New("invalid secret name") + } + + var certificateFileData []byte + var certificateKeyFileData []byte + + if o.CertFile != "" { + certificateFileData, err = os.ReadFile(o.CertFile) + + if err != nil { + return errors.Wrapf(err, "failed to read certificate file %s", o.CertFile) + } + } + + if o.KeyFile != "" { + certificateKeyFileData, err = os.ReadFile(o.KeyFile) + + if err != nil { + return errors.Wrapf(err, "failed to read certificate key file %s", o.KeyFile) + } + } + + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: map[string]string{}, + }, + Type: "kubernetes.io/tls", + Data: map[string][]byte{ + "tls.crt": certificateFileData, + "tls.key": certificateKeyFileData, + }, + } + + if o.IngressDomain != "" { + secret.ObjectMeta.Annotations[constants.EducatesTrainingLabelAnnotationDomain] = o.IngressDomain + } + + secretData, err := json.MarshalIndent(&secret, "", " ") + + if err != nil { + return errors.Wrap(err, "failed to generate secret data") + } + + secretData, err = yaml.JSONToYAML(secretData) + + if err != nil { + return errors.Wrap(err, "failed to generate YAML data") + } + + secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") + + err = os.MkdirAll(secretsCacheDir, os.ModePerm) + + if err != nil { + return errors.Wrapf(err, "unable to create secrets cache directory") + } + + secretFilePath := path.Join(secretsCacheDir, name+".yaml") + + secretFile, err := os.OpenFile(secretFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) + + if err != nil { + return errors.Wrapf(err, "unable to create secret file %s", secretFilePath) + } + + if _, err := secretFile.Write(secretData); err != nil { + return errors.Wrapf(err, "unable to write secret file %s", secretFilePath) + } + + if err := secretFile.Close(); err != nil { + return errors.Wrapf(err, "unable to close secret file %s", secretFilePath) + } + + return nil +} + +func (p *ProjectInfo) NewLocalSecretsAddTlsCmd() *cobra.Command { + var o LocalSecretsAddTlsOptions + + var c = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "name is required", "NAME") + } + return nil + }, + Use: "tls NAME", + Short: "Create a TLS secret", + RunE: func(_ *cobra.Command, args []string) error { return o.Run(args[0]) }, + Example: localSecretsAddTlsExample, + } + + c.Flags().StringVar( + &o.CertFile, + "cert", + "", + "path to PEM encoded public key certificate", + ) + c.Flags().StringVar( + &o.KeyFile, + "key", + "", + "path to private key associated with given certificate", + ) + c.Flags().StringVar( + &o.IngressDomain, + "domain", + "", + "wildcard ingress domain matching certificate", + ) + + c.MarkFlagsRequiredTogether("cert", "key") + + return c +} diff --git a/client-programs/pkg/cmd/local_secrets_export_cmd.go b/client-programs/pkg/cmd/local_secrets_export_cmd.go index a6f237265..147cdd409 100644 --- a/client-programs/pkg/cmd/local_secrets_export_cmd.go +++ b/client-programs/pkg/cmd/local_secrets_export_cmd.go @@ -10,27 +10,44 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) +const localSecretsExportExample = ` + # Export all secrets in the cache + educates local secrets export + + # Export multiple secrets from the cache + educates local secrets export my-secret-1 my-secret-2 +` + +type LocalSecretsExportOptions struct {} + +func (o *LocalSecretsExportOptions) Run(args []string) error { + secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") + + err := os.MkdirAll(secretsCacheDir, os.ModePerm) + + if err != nil { + return errors.Wrapf(err, "unable to create secrets cache directory") + } + + err = utils.PrintYamlFilesInDir(secretsCacheDir, args) + if err != nil { + return errors.Wrapf(err, "unable to read secrets cache directory") + } + + return nil +} + func (p *ProjectInfo) NewLocalSecretsExportCmd() *cobra.Command { + var o LocalSecretsExportOptions + var c = &cobra.Command{ Args: cobra.ArbitraryArgs, Use: "export [NAME]", Short: "Export secrets in the cache", RunE: func(_ *cobra.Command, args []string) error { - secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") - - err := os.MkdirAll(secretsCacheDir, os.ModePerm) - - if err != nil { - return errors.Wrapf(err, "unable to create secrets cache directory") - } - - err = utils.PrintYamlFilesInDir(secretsCacheDir, args) - if err != nil { - return errors.Wrapf(err, "unable to read secrets cache directory") - } - - return nil + return o.Run(args) }, + Example: localSecretsExportExample, } return c diff --git a/client-programs/pkg/cmd/local_secrets_import_cmd.go b/client-programs/pkg/cmd/local_secrets_import_cmd.go index e42a0d4b6..583e8ce0d 100644 --- a/client-programs/pkg/cmd/local_secrets_import_cmd.go +++ b/client-programs/pkg/cmd/local_secrets_import_cmd.go @@ -7,9 +7,9 @@ import ( "regexp" "syscall" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -17,6 +17,11 @@ import ( "sigs.k8s.io/yaml" ) +const localSecretsImportExample = ` + # Import secrets from a file + educates local secrets import --file /path/to/secrets.yaml +` + type LocalSecretsImportOptions struct { File string } @@ -113,6 +118,7 @@ func (p *ProjectInfo) NewLocalSecretsImportCmd() *cobra.Command { Use: "import", Short: "Import secrets to the cache", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: localSecretsImportExample, } c.Flags().StringVarP( diff --git a/client-programs/pkg/cmd/local_secrets_list_cmd.go b/client-programs/pkg/cmd/local_secrets_list_cmd.go index f910ebe25..ef6bed897 100644 --- a/client-programs/pkg/cmd/local_secrets_list_cmd.go +++ b/client-programs/pkg/cmd/local_secrets_list_cmd.go @@ -6,40 +6,54 @@ import ( "path" "strings" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) -func (p *ProjectInfo) NewLocalSecretsListCmd() *cobra.Command { - var c = &cobra.Command{ - Args: cobra.NoArgs, - Use: "list", - Short: "List secrets in the cache", - RunE: func(_ *cobra.Command, _ []string) error { - secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") +const localSecretsListExample = ` + # List all secrets in the cache + educates local secrets list +` + +type LocalSecretsListOptions struct {} + +func (o *LocalSecretsListOptions) Run() error { + secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") + + err := os.MkdirAll(secretsCacheDir, os.ModePerm) - err := os.MkdirAll(secretsCacheDir, os.ModePerm) + if err != nil { + return errors.Wrapf(err, "unable to create secrets cache directory") + } + + files, err := os.ReadDir(secretsCacheDir) - if err != nil { - return errors.Wrapf(err, "unable to create secrets cache directory") - } + if err != nil { + return errors.Wrapf(err, "unable to read secrets cache directory") + } - files, err := os.ReadDir(secretsCacheDir) + for _, f := range files { + if strings.HasSuffix(f.Name(), ".yaml") { + name := strings.TrimSuffix(f.Name(), ".yaml") + fmt.Println(name) + } + } - if err != nil { - return errors.Wrapf(err, "unable to read secrets cache directory") - } + return nil +} - for _, f := range files { - if strings.HasSuffix(f.Name(), ".yaml") { - name := strings.TrimSuffix(f.Name(), ".yaml") - fmt.Println(name) - } - } +func (p *ProjectInfo) NewLocalSecretsListCmd() *cobra.Command { + var o LocalSecretsListOptions - return nil + var c = &cobra.Command{ + Args: cobra.NoArgs, + Use: "list", + Short: "List secrets in the cache", + RunE: func(_ *cobra.Command, _ []string) error { + return o.Run() }, + Example: localSecretsListExample, } return c diff --git a/client-programs/pkg/cmd/local_secrets_remove_cmd.go b/client-programs/pkg/cmd/local_secrets_remove_cmd.go index b0953eed8..7e928f26e 100644 --- a/client-programs/pkg/cmd/local_secrets_remove_cmd.go +++ b/client-programs/pkg/cmd/local_secrets_remove_cmd.go @@ -5,38 +5,55 @@ import ( "path" "regexp" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) -func (p *ProjectInfo) NewLocalSecretsRemoveCmd() *cobra.Command { - var c = &cobra.Command{ - Args: cobra.ExactArgs(1), - Use: "remove NAME", - Short: "Remove secret from the cache", - RunE: func(_ *cobra.Command, args []string) error { - name := args[0] +const localSecretsRemoveExample = ` + # Remove a secret from the cache + educates local secrets remove my-secret +` - var err error - var matched bool +type LocalSecretsRemoveOptions struct {} - if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { - return errors.Wrapf(err, "regex match on secret name failed") - } +func (o *LocalSecretsRemoveOptions) Run(name string) error { + var err error + var matched bool - if !matched { - return errors.Errorf("invalid secret name %q", name) - } + if matched, err = regexp.MatchString("^[a-z0-9]([.a-z0-9-]+)?[a-z0-9]$", name); err != nil { + return errors.Wrapf(err, "regex match on secret name failed") + } + + if !matched { + return errors.Errorf("invalid secret name %q", name) + } + + secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") + + secretFilePath := path.Join(secretsCacheDir, name+".yaml") - secretsCacheDir := path.Join(utils.GetEducatesHomeDir(), "secrets") + os.Remove(secretFilePath) - secretFilePath := path.Join(secretsCacheDir, name+".yaml") + return nil +} - os.Remove(secretFilePath) +func (p *ProjectInfo) NewLocalSecretsRemoveCmd() *cobra.Command { + var o LocalSecretsRemoveOptions + var c = &cobra.Command{ + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return utils.CmdError(cmd, "name is required", "NAME") + } return nil }, + Use: "remove NAME", + Short: "Remove secret from the cache", + RunE: func(_ *cobra.Command, args []string) error { + return o.Run(args[0]) + }, + Example: localSecretsRemoveExample, } return c diff --git a/client-programs/pkg/cmd/local_secrets_sync_cmd.go b/client-programs/pkg/cmd/local_secrets_sync_cmd.go index 2151e0fcf..305ce3701 100644 --- a/client-programs/pkg/cmd/local_secrets_sync_cmd.go +++ b/client-programs/pkg/cmd/local_secrets_sync_cmd.go @@ -1,12 +1,20 @@ package cmd import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" "github.com/educates/educates-training-platform/client-programs/pkg/secrets" + "github.com/pkg/errors" + "github.com/spf13/cobra" ) +const localSecretsSyncExample = ` + # Sync secrets to the cluster + educates local secrets sync + + # Sync secrets to the cluster using a custom kubeconfig file and context + educates local secrets sync --kubeconfig /path/to/kubeconfig --context my-context +` + type LocalSecretsSyncOptions struct { KubeconfigOptions } @@ -35,6 +43,7 @@ func (p *ProjectInfo) NewLocalSecretsSyncCmd() *cobra.Command { Use: "sync", Short: "Copy secrets to cluster", RunE: func(_ *cobra.Command, _ []string) error { return o.Run() }, + Example: localSecretsSyncExample, } c.Flags().StringVar( diff --git a/client-programs/pkg/cmd/project_docs_open_cmd.go b/client-programs/pkg/cmd/project_docs_open_cmd.go index 7de7198a4..45702d226 100644 --- a/client-programs/pkg/cmd/project_docs_open_cmd.go +++ b/client-programs/pkg/cmd/project_docs_open_cmd.go @@ -1,10 +1,8 @@ package cmd import ( - "fmt" - "os/exec" - "runtime" - + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/spf13/cobra" ) @@ -17,22 +15,7 @@ func (p *ProjectInfo) NewProjectDocsOpenCmd() *cobra.Command { Use: "open", Short: "Open browser on project documentation", RunE: func(_ *cobra.Command, _ []string) error { - var err error - - const url = "https://docs.educates.dev/" - - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - case "darwin": - err = exec.Command("open", url).Start() - default: - err = fmt.Errorf("unsupported platform") - } - - return err + return utils.OpenBrowser(constants.PROJECT_DOCS_URL) }, } diff --git a/client-programs/pkg/cmd/tunnel_connect_cmd.go b/client-programs/pkg/cmd/tunnel_connect_cmd.go index f5330ad36..1b2ad2814 100644 --- a/client-programs/pkg/cmd/tunnel_connect_cmd.go +++ b/client-programs/pkg/cmd/tunnel_connect_cmd.go @@ -1,13 +1,7 @@ package cmd import ( - "fmt" - "net/http" - "net/url" - "os" - - "github.com/gorilla/websocket" - "github.com/pkg/errors" + "github.com/educates/educates-training-platform/client-programs/pkg/tunnel" "github.com/spf13/cobra" ) @@ -15,46 +9,6 @@ type TunnelConnectOptions struct { Url string } -type session struct { - ws *websocket.Conn - errChan chan error -} - -func (o *TunnelConnectOptions) Run(cmd *cobra.Command) error { - dest, err := url.Parse(o.Url) - - if err != nil { - return errors.Wrap(err, "unable to parse websocket URL") - } - - originURL := *dest - - origin := originURL.String() - - headers := make(http.Header) - headers.Add("Origin", origin) - - dialer := websocket.Dialer{} - - ws, _, err := dialer.Dial(origin, headers) - - if err != nil { - return errors.Wrap(err, "unable to connect to websocket URL") - } - - sess := &session{ - ws: ws, - errChan: make(chan error), - } - - go sess.readInput() - go sess.readRemote() - - os.Stderr.WriteString(fmt.Sprintf("%s\n", <-sess.errChan)) - - return nil -} - func (p *ProjectInfo) NewTunnelConnectCmd() *cobra.Command { var o TunnelConnectOptions @@ -62,7 +16,7 @@ func (p *ProjectInfo) NewTunnelConnectCmd() *cobra.Command { Args: cobra.NoArgs, Use: "connect", Short: "SSH proxy for tunnelling over websockets", - RunE: func(cmd *cobra.Command, _ []string) error { return o.Run(cmd) }, + RunE: func(cmd *cobra.Command, _ []string) error { return tunnel.NewTunnel(o.Url).Start() }, } c.Flags().StringVar( @@ -76,46 +30,3 @@ func (p *ProjectInfo) NewTunnelConnectCmd() *cobra.Command { return c } - -func (s *session) readInput() { - in := os.Stdin - - const BUF_SIZE = 16384 - bufOut := make([]byte, BUF_SIZE) - - for { - var n int - var err error - - if n, err = in.Read(bufOut); err != nil || n == 0 { - break - } - - if err = s.ws.WriteMessage(websocket.BinaryMessage, bufOut[0:n]); err != nil { - break - } - } -} - -func (s *session) readRemote() { - out := os.Stdout - - for { - msgType, buf, err := s.ws.ReadMessage() - - if err != nil { - s.errChan <- err - return - } - - switch msgType { - case websocket.BinaryMessage: - if _, err = out.Write(buf); err != nil { - return - } - default: - s.errChan <- fmt.Errorf("unexpected websocket frame type: %d", msgType) - return - } - } -} diff --git a/client-programs/pkg/cmd/workshop_export_cmd.go b/client-programs/pkg/cmd/workshop_export_cmd.go index 7a14d41a6..709431b8a 100644 --- a/client-programs/pkg/cmd/workshop_export_cmd.go +++ b/client-programs/pkg/cmd/workshop_export_cmd.go @@ -1,38 +1,88 @@ package cmd import ( - "github.com/educates/educates-training-platform/client-programs/pkg/workshops" + "fmt" + "os" + "path/filepath" + + yttcmd "carvel.dev/ytt/pkg/cmd/template" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/local/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" "github.com/spf13/cobra" ) -var ( - workshopExportExample = ` - # Export workshop definition from current directory (workshop definition is expected within the path defined by workshop-file flag) to stdout +const workshopExportExample = ` + # Export workshop resource definition in current directory educates workshop export - # Export workshop definition from specific directory to stdout - educates workshop export lab-k8s-fundamentals - - # Export workshop definition using specific image repository - educates workshop export --image-repository ghcr.io/myorg + # Export workshop resource definition in my-workshop directory + educates workshop export my-workshop - # Export workshop definition using specific version - educates workshop export --workshop-version v1.0.0 + # Export workshop resource definition in my-workshop directory in a different workshop.yaml file + educates workshop export my-workshop --workshop-file ./workshop.yaml - # Export workshop definition for custom workshop file path - educates workshop export --workshop-file custom-workshop.yaml - educates workshop export $HOME/workshops/labs-educates-showcase --workshop-file lab-session-workshop.yaml + # Export workshop resource definition with data values + educates workshop export --image-repository ghcr.io/educates --workshop-version 1.0.0 ` -) + +type FilesExportOptions struct { + Repository string + WorkshopFile string + WorkshopVersion string + DataValuesFlags yttcmd.DataValuesFlags +} + +func (o *FilesExportOptions) Run(args []string) error { + var err error + + var directory string + + if len(args) != 0 { + directory = filepath.Clean(args[0]) + } else { + directory = "." + } + + if directory, err = filepath.Abs(directory); err != nil { + return errors.Wrap(err, "couldn't convert workshop directory to absolute path") + } + + fileInfo, err := os.Stat(directory) + + if err != nil || !fileInfo.IsDir() { + return errors.New("workshop directory does not exist or path is not a directory") + } + config := workshops.WorkshopExportConfig{ + Repository: o.Repository, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValuesFlags: o.DataValuesFlags, + } + + manager := workshops.NewWorkshopManager() + + workshop, err := manager.Export(directory, &config) + if err != nil { + return err + } + fmt.Println(workshop) + return nil +} func (p *ProjectInfo) NewWorkshopExportCmd() *cobra.Command { - var o workshops.FilesExportOptions + var o FilesExportOptions var c = &cobra.Command{ - Args: cobra.MaximumNArgs(1), - Use: "export [PATH]", - Short: "Export workshop resource definition for distribution to stdout", - RunE: func(cmd *cobra.Command, args []string) error { return o.Run(args) }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 1 { + return utils.CmdError(cmd, "too many arguments", "[PATH]") + } + return nil + }, + Use: "export [PATH]", + Short: "Export workshop resource definition", + RunE: func(cmd *cobra.Command, args []string) error { return o.Run(args) }, Example: workshopExportExample, } diff --git a/client-programs/pkg/cmd/workshop_new_cmd.go b/client-programs/pkg/cmd/workshop_new_cmd.go index 119bc69d3..050143ba2 100644 --- a/client-programs/pkg/cmd/workshop_new_cmd.go +++ b/client-programs/pkg/cmd/workshop_new_cmd.go @@ -1,12 +1,19 @@ package cmd import ( + "fmt" + "os" + "path/filepath" + "regexp" + + "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/educates/educates-training-platform/client-programs/pkg/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/local/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" ) -var ( +const ( workshopNewExample = ` # Create a new workshop using default hugo template (a directory will be created with my-workshop as name) educates workshop new my-workshop @@ -53,14 +60,100 @@ var ( ` ) +type WorkshopNewOptions struct { + Template string + Name string + Title string + Description string + Image string + TargetDirectory string + Overwrite bool + WithKubernetesAccess bool + WithGitHubAction bool + WithVirtualCluster bool + WithDockerDaemon bool + WithImageRegistry bool + WithKubernetesConsole bool + WithEditor bool + WithTerminal bool +} + func (p *ProjectInfo) NewWorkshopNewCmd() *cobra.Command { - var o workshops.WorkshopNewOptions + var o WorkshopNewOptions var c = &cobra.Command{ - Args: cobra.ExactArgs(1), - Use: "new PATH", - Short: "Create workshop files from template", - RunE: func(_ *cobra.Command, args []string) error { return o.Run(args) }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return utils.CmdError(cmd, "path is required", "PATH") + } + if len(args) > 1 { + return utils.CmdError(cmd, "too many arguments", "PATH") + } + return nil + }, + Use: "new PATH", + Short: "Create workshop files from template", + RunE: func(_ *cobra.Command, args []string) error { + var err error + + // Validate workshop name + name := o.Name + if name == "" { + name = args[0] + } + if match, _ := regexp.MatchString("^[a-z0-9-]+$", name); !match { + return errors.Errorf("invalid workshop name %q", name) + } + + // Get workshop dir + workshopDir := filepath.Clean(args[0]) + if o.TargetDirectory != "" { + workshopDir = filepath.Join(o.TargetDirectory, args[0]) + } + + if workshopDir, err = filepath.Abs(workshopDir); err != nil { + return errors.Wrapf(err, "could not convert path name %q to absolute path", workshopDir) + } + + // Check if target directory already exist and prompt the user to confirm that they want to overwrite the files in it + if _, err = os.Stat(workshopDir); err == nil { + ok := o.Overwrite + if !o.Overwrite { + ok = utils.YesNoPrompt([]string{ + fmt.Sprintf("WARNING: The directory %q already exists.", workshopDir), + "All files will be created in it, overwriting existing files.", + "Do you still want to use this directory?", + }, true) + } + if !ok { + return nil // Operation cancelled + } + } + + manager := workshops.NewWorkshopManager() + err = manager.NewWorkshop(workshopDir, &workshops.WorkshopNewConfig{ + Template: o.Template, + Name: name, + Title: o.Title, + Description: o.Description, + Image: o.Image, + TargetDirectory: o.TargetDirectory, + Overwrite: o.Overwrite, + WithKubernetesAccess: o.WithKubernetesAccess, + WithGitHubAction: o.WithGitHubAction, + WithVirtualCluster: o.WithVirtualCluster, + WithDockerDaemon: o.WithDockerDaemon, + WithImageRegistry: o.WithImageRegistry, + WithKubernetesConsole: o.WithKubernetesConsole, + WithEditor: o.WithEditor, + WithTerminal: o.WithTerminal, + }) + if err != nil { + return err + } + fmt.Printf("Workshop %q created successfully.\n", name) + return nil + }, Example: workshopNewExample, } diff --git a/client-programs/pkg/cmd/workshop_publish_cmd.go b/client-programs/pkg/cmd/workshop_publish_cmd.go index 21affcdc9..77bfc717f 100644 --- a/client-programs/pkg/cmd/workshop_publish_cmd.go +++ b/client-programs/pkg/cmd/workshop_publish_cmd.go @@ -1,45 +1,90 @@ package cmd import ( + "os" + "path/filepath" "time" - "github.com/educates/educates-training-platform/client-programs/pkg/workshops" + imgpkgcmd "carvel.dev/imgpkg/pkg/imgpkg/cmd" + yttcmd "carvel.dev/ytt/pkg/cmd/template" + "github.com/educates/educates-training-platform/client-programs/pkg/educates/local/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" "github.com/spf13/cobra" ) -var ( - workshopPublishExample = ` - # Publish workshop files to local registry +type FilesPublishOptions struct { + Image string + Repository string + WorkshopFile string + ExportWorkshop string + WorkshopVersion string + RegistryFlags imgpkgcmd.RegistryFlags + DataValuesFlags yttcmd.DataValuesFlags +} + +const workshopPublishExample = ` + # Publish workshop files to repository in current directory educates workshop publish - # Publish workshop files to specific registry - educates workshop publish --image-repository ghcr.io/myorg + # Publish workshop files to repository in my-workshop directory + educates workshop publish my-workshop - # Publish workshop files with specific version - educates workshop publish --workshop-version v1.0.0 + # Publish workshop files to repository with a specific image in my-workshop directory + educates workshop publish my-workshop --image=my-workshop-image-files + + # Publish workshop files to repository with a specific image and repository in my-workshop directory + educates workshop publish my-workshop --image=my-workshop-image-files --image-repository=ghcr.io/educates --workshop-version=1.0.0 +` +func (o *FilesPublishOptions) Run(args []string) error { + var err error - # Publish workshop files with custom workshop definition - educates workshop publish --workshop-file custom-workshop.yaml + var directory string - # Publish workshop files and export modified workshop definition - educates workshop publish --export-workshop exported-workshop.yaml + if len(args) != 0 { + directory = filepath.Clean(args[0]) + } else { + directory = "." + } - # Publish workshop files with registry authentication - educates workshop publish --registry-username user --registry-password pass + if directory, err = filepath.Abs(directory); err != nil { + return errors.Wrap(err, "couldn't convert workshop directory to absolute path") + } - # Publish workshop files with data values - educates workshop publish --data-value workshop.title="My Workshop" --data-value workshop.description="A great workshop" -` -) + fileInfo, err := os.Stat(directory) + + if err != nil || !fileInfo.IsDir() { + return errors.New("workshop directory does not exist or path is not a directory") + } + + config := workshops.WorkshopPublishConfig{ + Image: o.Image, + Repository: o.Repository, + WorkshopFile: o.WorkshopFile, + ExportWorkshop: o.ExportWorkshop, + WorkshopVersion: o.WorkshopVersion, + RegistryFlags: o.RegistryFlags, + DataValuesFlags: o.DataValuesFlags, + } + + m := workshops.NewWorkshopManager() + + return m.Publish(directory, &config) +} func (p *ProjectInfo) NewWorkshopPublishCmd() *cobra.Command { - var o workshops.FilesPublishOptions + var o FilesPublishOptions var c = &cobra.Command{ - Args: cobra.MaximumNArgs(1), - Use: "publish [PATH]", - Short: "Publish workshop files to repository", - RunE: func(cmd *cobra.Command, args []string) error { return o.Run(args) }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 1 { + return utils.CmdError(cmd, "too many arguments", "[PATH]") + } + return nil + }, + Use: "publish [PATH]", + Short: "Publish workshop files to repository", + RunE: func(cmd *cobra.Command, args []string) error { return o.Run(args) }, Example: workshopPublishExample, } diff --git a/client-programs/pkg/config/edit.go b/client-programs/pkg/config/edit.go new file mode 100644 index 000000000..aca00aea7 --- /dev/null +++ b/client-programs/pkg/config/edit.go @@ -0,0 +1,212 @@ +package config + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path" + "strings" + + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" +) + +// Header comment shown at the top of the editor (similar to kubectl edit) +const editHeader = `## Please edit the configuration below. Lines beginning with a '##' will be ignored, +## and an empty file will abort the edit. If an error occurs while saving, this file +## will be reopened with the relevant failures. +## +` + +type LocalConfigEditConfig struct{} + +func (o *LocalConfigEditConfig) Edit() error { + // Create the configuration directory if it doesn't exist + err := os.MkdirAll(utils.GetEducatesHomeDir(), os.ModePerm) + if err != nil { + return errors.Wrapf(err, "unable to create configuration directory %q", utils.GetEducatesHomeDir()) + } + + valuesFilePath := path.Join(utils.GetEducatesHomeDir(), "values.yaml") + + // Read existing configuration file if it exists + var valuesFileData []byte + valuesFileData, err = os.ReadFile(valuesFilePath) + if err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "unable to read existing configuration file %q", valuesFilePath) + } + + // Create a temporary file in the OS temp directory (e.g., /tmp on Unix) + tmpFile, err := os.CreateTemp("", "educates-config-*.yaml") + if err != nil { + return errors.Wrapf(err, "unable to create temporary configuration file") + } + tmpValuesFilePath := tmpFile.Name() + tmpFile.Close() // Close immediately since we'll use os.WriteFile + + // Track whether to preserve temp file on exit (set to true when user cancels after making changes) + preserveTempFile := false + + // Clean up temp file when done (unless we need to preserve it for user recovery) + defer func() { + if preserveTempFile { + return // Don't delete - user's changes are stored there + } + if removeErr := os.Remove(tmpValuesFilePath); removeErr != nil && !os.IsNotExist(removeErr) { + // Log but don't fail on cleanup errors + fmt.Fprintf(os.Stderr, "Warning: unable to remove temporary file %q: %v\n", tmpValuesFilePath, removeErr) + } + }() + + // Determine which editor to use + // Check VISUAL first (common convention), then EDITOR, then default to vi + editor := os.Getenv("VISUAL") + if strings.TrimSpace(editor) == "" { + editor = os.Getenv("EDITOR") + } + if strings.TrimSpace(editor) == "" { + editor = "vi" + } + editor = strings.TrimSpace(editor) + + // Look up the editor executable path + editorPath, err := exec.LookPath(editor) + if err != nil { + return errors.Wrapf(err, "unable to find editor %q in PATH", editor) + } + + // Write the initial configuration with header comment + err = writeEditFile(tmpValuesFilePath, editHeader, valuesFileData) + if err != nil { + return errors.Wrapf(err, "unable to write to temporary configuration file %q", tmpValuesFilePath) + } + + // Track edit iterations to distinguish first edit from subsequent edits + isFirstEdit := true + // Keep track of the last valid user content (stripped of comments) for detecting no-save exits + var lastStrippedContent []byte + + // Edit loop: keep reopening editor on validation errors (like kubectl edit) + for { + // Read file content before editing to detect if user saved or quit without saving + contentBeforeEdit, err := os.ReadFile(tmpValuesFilePath) + if err != nil { + return errors.Wrapf(err, "unable to read temporary configuration file %q", tmpValuesFilePath) + } + + // Launch the editor + cmd := exec.Command(editorPath, tmpValuesFilePath) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Start() + if err != nil { + return errors.Wrapf(err, "unable to start editor %q", editor) + } + + err = cmd.Wait() + if err != nil { + return errors.Wrapf(err, "editor %q exited with an error", editor) + } + + // Read the edited file + editedData, err := os.ReadFile(tmpValuesFilePath) + if err != nil { + return errors.Wrapf(err, "unable to read edited configuration file %q", tmpValuesFilePath) + } + + // Check if the file content changed (detect quit without saving like :q!) + if string(editedData) == string(contentBeforeEdit) { + if isFirstEdit { + // First edit, user quit without making any changes + fmt.Println("Edit cancelled, no changes made.") + return nil + } else { + // Subsequent edit after validation error, user quit without saving + // Preserve the temp file with the user's last changes + err = os.WriteFile(tmpValuesFilePath, lastStrippedContent, 0644) + if err == nil { + preserveTempFile = true + fmt.Printf("A copy of your changes has been stored to %q\n", tmpValuesFilePath) + } + return errors.New("Edit cancelled, no valid changes were saved.") + } + } + + strippedData := stripComments(editedData) + + // Check if the file is empty (abort edit) + if len(strings.TrimSpace(string(strippedData))) == 0 { + if isFirstEdit { + fmt.Println("Edit cancelled, no changes made.") + return nil + } else { + // User cleared all content after a validation error + err = os.WriteFile(tmpValuesFilePath, lastStrippedContent, 0644) + if err == nil { + preserveTempFile = true + fmt.Printf("A copy of your changes has been stored to %q\n", tmpValuesFilePath) + } + return errors.New("Edit cancelled, no valid changes were saved.") + } + } + + // Save the stripped content for potential recovery + lastStrippedContent = strippedData + + // Write stripped data to temp file for validation + err = os.WriteFile(tmpValuesFilePath, strippedData, 0644) + if err != nil { + return errors.Wrapf(err, "unable to write configuration for validation") + } + + // Validate the edited configuration file + _, validationErr := NewInstallationConfigFromFileForConfigEdit(tmpValuesFilePath) + if validationErr != nil { + // Validation failed: rewrite file with error comment and reopen editor + errorHeader := fmt.Sprintf("%s## %s\n##\n", editHeader, validationErr.Error()) + err = writeEditFile(tmpValuesFilePath, errorHeader, strippedData) + if err != nil { + return errors.Wrapf(err, "unable to write error feedback to configuration file") + } + isFirstEdit = false // Mark that we've had at least one validation attempt + continue // Reopen editor + } + + // Validation succeeded: save the configuration + err = os.WriteFile(valuesFilePath, strippedData, 0644) + if err != nil { + return errors.Wrapf(err, "unable to update configuration file %q", valuesFilePath) + } + + fmt.Println("Configuration updated successfully.") + return nil + } +} + +// writeEditFile writes the header comment followed by the configuration data to the file +func writeEditFile(filePath string, header string, data []byte) error { + content := header + string(data) + return os.WriteFile(filePath, []byte(content), 0644) +} + +// stripComments removes lines starting with '##' from the data +func stripComments(data []byte) []byte { + var result strings.Builder + scanner := bufio.NewScanner(strings.NewReader(string(data))) + + for scanner.Scan() { + line := scanner.Text() + // Skip lines that start with '##' (with optional leading whitespace) + trimmedLine := strings.TrimSpace(line) + if strings.HasPrefix(trimmedLine, "##") { + continue + } + result.WriteString(line) + result.WriteString("\n") + } + + return []byte(result.String()) +} diff --git a/client-programs/pkg/config/installationconfig.go b/client-programs/pkg/config/installationconfig.go index 3fe52f2a6..c90f1d881 100644 --- a/client-programs/pkg/config/installationconfig.go +++ b/client-programs/pkg/config/installationconfig.go @@ -4,6 +4,7 @@ import ( "os" "path" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/secrets" "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" @@ -16,12 +17,27 @@ type VolumeMountConfig struct { ReadOnly *bool `yaml:"readOnly,omitempty"` } +// NodeConfig defines configuration for a single kind node +type NodeConfig struct { + Role string `yaml:"role"` // "control-plane" or "worker" + Labels map[string]string `yaml:"labels,omitempty"` // Custom labels for the node + Taints []TaintConfig `yaml:"taints,omitempty"` // Taints for the node +} + +// TaintConfig defines a Kubernetes taint +type TaintConfig struct { + Key string `yaml:"key"` // Taint key + Value string `yaml:"value,omitempty"` // Taint value (optional) + Effect string `yaml:"effect"` // NoSchedule, PreferNoSchedule, or NoExecute +} + type LocalKindClusterConfig struct { ListenAddress string `yaml:"listenAddress,omitempty"` ApiServer KindApiServerConfig `yaml:"apiServer,omitempty"` Networking KindNetworkingConfig `yaml:"networking,omitempty"` VolumeMounts []VolumeMountConfig `yaml:"volumeMounts,omitempty"` RegistryMirrors []RegistryMirrorConfig `yaml:"registryMirrors,omitempty"` + Nodes []NodeConfig `yaml:"nodes,omitempty"` // Nodes configuration for multi-node clusters } type RegistryMirrorConfig struct { @@ -396,6 +412,24 @@ func NewInstallationConfigFromFile(configFile string) (*InstallationConfig, erro return config, nil } +// This function is used to parse the installation config file for config edit. +// It will not print the configFile location to the console. +func NewInstallationConfigFromFileForConfigEdit(configFile string) (*InstallationConfig, error) { + config := &InstallationConfig{} + + data, err := os.ReadFile(configFile) + + if err != nil { + return nil, err + } + + if err := yaml.UnmarshalStrict(data, &config); err != nil { + return nil, err + } + + return config, nil +} + func ConfigForLocalClusters(configFile string, domain string, local bool) (fullConfig *InstallationConfig, err error) { if configFile == NULL_CONFIG_FILE { fullConfig = NewDefaultInstallationConfig() @@ -431,12 +465,12 @@ func ConfigForLocalClusters(configFile string, domain string, local bool) (fullC if local { // This augments the installation config with the secrets that are cached locally if secretName := secrets.LocalCachedSecretForIngressDomain(fullConfig.ClusterIngress.Domain); secretName != "" { - fullConfig.ClusterIngress.TLSCertificateRef.Namespace = "educates-secrets" + fullConfig.ClusterIngress.TLSCertificateRef.Namespace = constants.EducatesSecretsNamespace fullConfig.ClusterIngress.TLSCertificateRef.Name = secretName } if secretName := secrets.LocalCachedSecretForCertificateAuthority(fullConfig.ClusterIngress.Domain); secretName != "" { - fullConfig.ClusterIngress.CACertificateRef.Namespace = "educates-secrets" + fullConfig.ClusterIngress.CACertificateRef.Namespace = constants.EducatesSecretsNamespace fullConfig.ClusterIngress.CACertificateRef.Name = secretName } } @@ -445,6 +479,13 @@ func ConfigForLocalClusters(configFile string, domain string, local bool) (fullC return nil, err } + // Validate nodes configuration for kind clusters + if local && fullConfig.ClusterInfrastructure.Provider == "kind" { + if err := ValidateNodesConfig(&fullConfig.LocalKindCluster.Nodes); err != nil { + return nil, errors.Wrap(err, "invalid nodes configuration") + } + } + return fullConfig, nil } @@ -496,3 +537,67 @@ func ValidateProvider(provider string) error { return errors.New("Invalid ClusterInsfrastructure Provider. Valid values are (eks, gke, kind, custom, vcluster, generic, minikube, openshift)") } } + +// ValidateNodesConfig validates the nodes configuration for a kind cluster +func ValidateNodesConfig(nodes *[]NodeConfig) error { + if len(*nodes) == 0 { + // Empty is valid - will use default single control-plane + return nil + } + + controlPlaneCount := 0 + workerCount := 0 + + for i, node := range *nodes { + // Validate role + if node.Role != "control-plane" && node.Role != "worker" { + return errors.Errorf("node %d has invalid role %q, must be 'control-plane' or 'worker'", i, node.Role) + } + + // Count nodes by role + if node.Role == "control-plane" { + controlPlaneCount++ + } else { + workerCount++ + } + + // Validate taints + for j, taint := range node.Taints { + if taint.Key == "" { + return errors.Errorf("node %d taint %d has empty key", i, j) + } + if taint.Effect == "" { + return errors.Errorf("node %d taint %d (%s) has empty effect", i, j, taint.Key) + } + // Validate taint effect + validEffects := map[string]bool{ + "NoSchedule": true, + "PreferNoSchedule": true, + "NoExecute": true, + } + if !validEffects[taint.Effect] { + return errors.Errorf("node %d taint %d (%s) has invalid effect %q, must be 'NoSchedule', 'PreferNoSchedule', or 'NoExecute'", + i, j, taint.Key, taint.Effect) + } + } + } + + // Validate exactly one control-plane + if controlPlaneCount == 0 { + // We add a default control-plane node if no control-plane nodes are configured + *nodes = append(*nodes, NodeConfig{ + Role: "control-plane", + }) + controlPlaneCount = 1 + } + if controlPlaneCount > 1 { + return errors.Errorf("nodes configuration must have exactly one control-plane node, found %d", controlPlaneCount) + } + + // Validate maximum 5 workers + if workerCount > 5 { + return errors.Errorf("nodes configuration supports maximum 5 worker nodes, found %d", workerCount) + } + + return nil +} diff --git a/client-programs/pkg/constants/installer.go b/client-programs/pkg/constants/installer.go new file mode 100644 index 000000000..62e196771 --- /dev/null +++ b/client-programs/pkg/constants/installer.go @@ -0,0 +1,10 @@ +package constants + +const ( + EducatesInstallerString = "educates-installer" + EducatesInstallerAppString = "label:installer=educates-installer.app" + EducatesConfigNamespace = "educates" + EducatesConfigConfigMapName = "educates-config" + EducatesProcessedValuesKey = "educates-processed-values.yaml" + EducatesOriginalConfigKey = "educates-original-config.yaml" +) diff --git a/client-programs/pkg/constants/kubernetes.go b/client-programs/pkg/constants/kubernetes.go new file mode 100644 index 000000000..7f3cf9dec --- /dev/null +++ b/client-programs/pkg/constants/kubernetes.go @@ -0,0 +1,16 @@ +package constants + +// KubernetesVersionToKindImage maps Kubernetes versions to their corresponding kind node images +var ( + KubernetesVersionToKindImage = map[string]string{ + "1.35": "kindest/node:v1.35.0@sha256:452d707d4862f52530247495d180205e029056831160e22870e37e3f6c1ac31f", + "1.34": "kindest/node:v1.34.3@sha256:08497ee19eace7b4b5348db5c6a1591d7752b164530a36f855cb0f2bdcbadd48", + "1.33": "kindest/node:v1.33.7@sha256:d26ef333bdb2cbe9862a0f7c3803ecc7b4303d8cea8e814b481b09949d353040", + "1.32": "kindest/node:v1.32.11@sha256:5fc52d52a7b9574015299724bd68f183702956aa4a2116ae75a63cb574b35af8", + "1.31": "kindest/node:v1.31.14@sha256:6f86cf509dbb42767b6e79debc3f2c32e4ee01386f0489b3b2be24b0a55aac2b", + } +) + +const ( + DefaultKubernetesVersion = "1.34" +) diff --git a/client-programs/pkg/constants/names.go b/client-programs/pkg/constants/names.go new file mode 100644 index 000000000..2f6aa9f52 --- /dev/null +++ b/client-programs/pkg/constants/names.go @@ -0,0 +1,42 @@ +package constants + +const ( + EducatesClusterName = "educates" + RegistryImageV3 = "docker.io/library/registry:3" + RegistryConfigTargetPath = "/etc/distribution/config.yml" + ClusterNetworkName = "kind" + EducatesNetworkName = "educates" + EducatesRegistryContainer = "educates-registry" + EducatesControlPlaneContainer = "educates-control-plane" + EducatesResolverContainerName = "educates-resolver" + + // Workshop API Group and Version + EducatesTrainingAPIGroup = "training.educates.dev" + EducatesTrainingAPIVersion = "v1beta1" + EducatesTrainingAPIGroupVersion = "training.educates.dev/v1beta1" + + // Workshop Pod Label/Annotations Keys + EducatesWorkshopLabelAnnotationURL = "training.educates.dev/url" + EducatesWorkshopLabelAnnotationSource = "training.educates.dev/source" + EducatesWorkshopLabelAnnotationSession = "training.educates.dev/session" + EducatesWorkshopLabelAnnotationWorkshop = "training.educates.dev/workshop" + EducatesWorkshopLabelAnnotationComponent = "training.educates.dev/component" + EducatesWorkshopLabelAnnotationComponentPortal = "training.educates.dev/component=portal" + + EducatesTrainingLabelAnnotationDomain = "training.educates.dev/domain" + EducatesTrainingLabelAnnotationEnvironmentName = "training.educates.dev/environment.name" + EducatesTrainingLabelAnnotationPortalName = "training.educates.dev/portal.name" + + // Container Label Keys + EducatesContainersAppLabelKey = "educates.dev/app" + EducatesContainersRoleLabelKey = "educates.dev/role" + EducatesContainersMirrorLabelKey = "educates.dev/mirror" + EducatesContainersURLLabelKey = "educates.dev/url" + EducatesContainersUsernameLabelKey = "educates.dev/username" + // Container Label Values + EducatesContainersRegistryRoleLabel = "registry" + EducatesContainersMirrorRoleLabel = "mirror" + EducatesContainersResolverRoleLabel = "resolver" + EducatesContainersWorkshopRoleLabel = "workshop" + EducatesContainersAppLabel = "educates" +) diff --git a/client-programs/pkg/constants/project.go b/client-programs/pkg/constants/project.go new file mode 100644 index 000000000..22e8faf97 --- /dev/null +++ b/client-programs/pkg/constants/project.go @@ -0,0 +1,14 @@ +package constants + +const ( + EducatesHomeDirName = "educates" + + PROJECT_DOCS_URL = "https://docs.educates.dev/" + + DefaultPortalName = "educates-cli" + DefaultPortalCapacity = 5 + + EducatesNamespace = "educates" + EducatesNamespaceLabelMetadataName = "educates" + EducatesSecretsNamespace = "educates-secrets" +) diff --git a/client-programs/pkg/diagnostics/diagnostics.go b/client-programs/pkg/diagnostics/diagnostics.go index 3b875d667..d129414ad 100644 --- a/client-programs/pkg/diagnostics/diagnostics.go +++ b/client-programs/pkg/diagnostics/diagnostics.go @@ -8,6 +8,8 @@ import ( "strings" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" ) type ClusterDiagnostics struct { @@ -35,22 +37,22 @@ func (c *ClusterDiagnostics) Run() error { clusterDiagnosticsFetcher := &ClusterDiagnosticsFetcher{c.clusterConfig, tempDir, c.verbose} // Fetch all Educates training related resources - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(trainingportalResource, "training-portals.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.TrainingPortalResource, "training-portals.yaml"); err != nil { fmt.Println("Error fetching training portals: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(workshopResource, "workshops.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.WorkshopResource, "workshops.yaml"); err != nil { fmt.Println("Error fetching workshops: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(workshopsessionsResource, "workshop-sessions.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.WorkshopsessionsResource, "workshop-sessions.yaml"); err != nil { fmt.Println("Error fetching workshop sessions: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(workshoprequestsResource, "workshop-requests.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.WorkshoprequestsResource, "workshop-requests.yaml"); err != nil { fmt.Println("Error fetching workshop requests: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(workshopenvironmentsResource, "workshop-environments.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.WorkshopenvironmentsResource, "workshop-environments.yaml"); err != nil { fmt.Println("Error fetching workshop environments: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(workshopallocationsResource, "workshop-allocations.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.WorkshopallocationsResource, "workshop-allocations.yaml"); err != nil { fmt.Println("Error fetching workshop allocations: ", err) } @@ -60,28 +62,28 @@ func (c *ClusterDiagnostics) Run() error { } // Fetch all Educates secrets related resources - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(secretcopierResource, "secret-copiers.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.SecretcopierResource, "secret-copiers.yaml"); err != nil { fmt.Println("Error fetching secret copiers: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(secretinjectorsResource, "secret-injectors.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.SecretinjectorsResource, "secret-injectors.yaml"); err != nil { fmt.Println("Error fetching secret injectors: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(secretexportersResource, "secret-exporters.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.SecretexportersResource, "secret-exporters.yaml"); err != nil { fmt.Println("Error fetching secret injectors: ", err) } - if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(secretimportersResource, "secret-importers.yaml"); err != nil { + if err = clusterDiagnosticsFetcher.fetchDynamicallyResources(educatesTypes.SecretimportersResource, "secret-importers.yaml"); err != nil { fmt.Println("Error fetching secret injectors: ", err) } // fetch logs for the session-manager, secret-manager deploymentments - if err = clusterDiagnosticsFetcher.fetchLogsForDeployment("deployment=session-manager", "educates", "session-manager.log"); err != nil { + if err = clusterDiagnosticsFetcher.fetchLogsForDeployment("deployment=session-manager", constants.EducatesNamespace, "session-manager.log"); err != nil { fmt.Println("Error fetching logs for session-manager: ", err) } - if err = clusterDiagnosticsFetcher.fetchLogsForDeployment("deployment=secrets-manager", "educates", "secrets-manager.log"); err != nil { + if err = clusterDiagnosticsFetcher.fetchLogsForDeployment("deployment=secrets-manager", constants.EducatesNamespace, "secrets-manager.log"); err != nil { fmt.Println("Error fetching logs for secrets-manager: ", err) } // dump logs for all training-portal deployments - if err = clusterDiagnosticsFetcher.fetchLogsForDeployment("deployment=training-portal", "training.educates.dev/component=portal", "training-portal-%v.log"); err != nil { + if err = clusterDiagnosticsFetcher.fetchLogsForDeployment("deployment=training-portal", constants.EducatesWorkshopLabelAnnotationComponentPortal, "training-portal-%v.log"); err != nil { fmt.Println("Error fetching logs for secrets-manager: ", err) } // Fetch workshop_list from Rest API for each training-portal diff --git a/client-programs/pkg/diagnostics/fetcher.go b/client-programs/pkg/diagnostics/fetcher.go index 9e000e865..e195926d6 100644 --- a/client-programs/pkg/diagnostics/fetcher.go +++ b/client-programs/pkg/diagnostics/fetcher.go @@ -10,7 +10,9 @@ import ( "strings" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" - "github.com/educates/educates-training-platform/client-programs/pkg/educatesrestapi" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + educatesrestapi "github.com/educates/educates-training-platform/client-programs/pkg/educates/restapi" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -19,16 +21,6 @@ import ( "k8s.io/cli-runtime/pkg/printers" ) -var workshopResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshops"} -var trainingportalResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "trainingportals"} -var workshopsessionsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopsessions"} -var workshoprequestsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshoprequests"} -var workshopenvironmentsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopenvironments"} -var workshopallocationsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopallocations"} -var secretcopierResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretcopiers"} -var secretinjectorsResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretinjectors"} -var secretexportersResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretexporters"} -var secretimportersResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretimporters"} type ClusterDiagnosticsFetcher struct { clusterConfig *cluster.ClusterConfig @@ -43,7 +35,7 @@ func (c *ClusterDiagnosticsFetcher) getEducatesNamespaces(fileName string) error } namespaces, err := client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{ - // LabelSelector: "training.educates.dev/component", + // LabelSelector: constants.EducatesWorkshopLabelAnnotationComponent, }) if err != nil { return err @@ -92,11 +84,11 @@ func (c *ClusterDiagnosticsFetcher) getEducatesNamespacesEvents(fileName string) y := printers.YAMLPrinter{} for _, namespace := range namespaces.Items { - if !strings.HasPrefix(namespace.Labels["kubernetes.io/metadata.name"], "educates") { + if !strings.HasPrefix(namespace.Labels["kubernetes.io/metadata.name"], constants.EducatesNamespaceLabelMetadataName) { continue } events, err := client.CoreV1().Events(namespace.Name).List(context.TODO(), metav1.ListOptions{ - // LabelSelector: "training.educates.dev/component", + // LabelSelector: constants.EducatesWorkshopLabelAnnotationComponent, }) for _, object := range events.Items { object.SetManagedFields(nil) // Remove managedFields from the object @@ -216,7 +208,7 @@ func (c *ClusterDiagnosticsFetcher) fetchTrainingPortalDetailsAtRest(fileNamePat if err != nil { return err } - dynClient := dynamicClient.Resource(trainingportalResource) + dynClient := dynamicClient.Resource(educatesTypes.TrainingPortalResource) trainingPortals, err := dynClient.List(context.TODO(), metav1.ListOptions{}) if err != nil { return err diff --git a/client-programs/pkg/docker/extension_backend.go b/client-programs/pkg/docker/extension_backend.go new file mode 100644 index 000000000..c4b4cb797 --- /dev/null +++ b/client-programs/pkg/docker/extension_backend.go @@ -0,0 +1,103 @@ +package docker + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "regexp" + "syscall" + + "github.com/pkg/errors" +) + +type DockerExtensionBackendConfig struct { + Socket string +} + +type DockerExtensionBackend struct { + Api *DockerWorkshopsApi +} + +func NewDockerExtensionBackend(version string, imageRepository string) DockerExtensionBackend { + return DockerExtensionBackend{ + Api: &DockerWorkshopsApi{ + Manager: NewDockerWorkshopsManager(), + ImageRepository: imageRepository, + ImageVersion: version, + }, + } +} + +func (b *DockerExtensionBackend) Run(config *DockerExtensionBackendConfig) error { + if config.Socket == "" { + return errors.New("invalid socket for HTTP server") + } + + router := http.NewServeMux() + + versionHandler := func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, b.Api.ImageVersion) + } + + router.HandleFunc("/version", versionHandler) + + router.HandleFunc("/workshop/list", b.Api.ListWorkhops) + router.HandleFunc("/workshop/deploy", b.Api.DeployWorkshop) + router.HandleFunc("/workshop/delete", b.Api.DeleteWorkshop) + + server := http.Server{ + Handler: router, + } + + // The socket string can either be of the form host:nnn, or it can be a file + // system path (absolute or relative). In the first case we start up a + // normal HTTP server accepting connections over an INET socket connection. + // In the second case connections will be accepted over a UNIX socket. + + inetRegexPattern := `^([a-zA-Z0-9.-]+):(\d+)$` + + match, err := regexp.MatchString(inetRegexPattern, config.Socket) + + if err != nil { + return errors.Wrap(err, "failed to perform regex match on socket") + } + + var listener net.Listener + + if match { + listener, err = net.Listen("tcp", config.Socket) + + if err != nil { + return errors.Wrap(err, "unable to create INET HTTP server socket") + } + } else { + listener, err = net.Listen("unix", config.Socket) + + if err != nil { + return errors.Wrap(err, "unable to create UNIX HTTP server socket") + } + + defer os.Remove(config.Socket) + } + + defer listener.Close() + + go func() { + server.Serve(listener) + }() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + <-sigChan + + err = server.Shutdown(context.TODO()) + + if err != nil { + return errors.Wrap(err, "failed to shutdown HTTP server") + } + + return nil +} diff --git a/client-programs/pkg/docker/extension_backend_api.go b/client-programs/pkg/docker/extension_backend_api.go new file mode 100644 index 000000000..f4bbbb81e --- /dev/null +++ b/client-programs/pkg/docker/extension_backend_api.go @@ -0,0 +1,145 @@ +package docker + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "strings" +) + +type DockerWorkshopsApi struct { + Manager DockerWorkshopsManager + ImageRepository string + ImageVersion string +} + +// func NewDockerWorkshopsApi(version string, imageRepository string) *DockerWorkshopsApi { +// return &DockerWorkshopsApi{ +// Manager: NewDockerWorkshopsManager(), +// ImageRepository: imageRepository, +// ImageVersion: version, +// } +// } + +func (b *DockerWorkshopsApi) ListWorkhops(w http.ResponseWriter, r *http.Request) { + workshops, err := b.Manager.ListWorkshops() + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + jsonData, err := json.Marshal(workshops) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + + w.WriteHeader(http.StatusOK) + w.Write(jsonData) +} + +func (b *DockerWorkshopsApi) DeployWorkshop(w http.ResponseWriter, r *http.Request) { + queryParams := r.URL.Query() + + url := queryParams.Get("url") + + if url == "" { + http.Error(w, "workshop definition url required", http.StatusBadRequest) + return + } + + portString := queryParams.Get("port") + + if portString == "" { + portString = "10081" + } + + port, err := strconv.Atoi(portString) + + if err != nil || port <= 0 { + http.Error(w, "invalid workshop port supplied", http.StatusBadRequest) + return + } + + o := DockerWorkshopDeployConfig{ + Path: url, + Host: "127.0.0.1", + Port: uint(port), + LocalRepository: "localhost:5001", + DisableOpenBrowser: false, + ImageRepository: b.ImageRepository, + ImageVersion: b.ImageVersion, + Cluster: "", + KubeConfig: "", + Assets: "", + } + + name, err := b.Manager.DeployWorkshop(&o, os.Stdout, os.Stderr) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + sessionUrl := fmt.Sprintf("http://workshop.%s.nip.io:%d", strings.ReplaceAll(o.Host, ".", "-"), o.Port) + + workshop := DockerWorkshopDetails{ + Name: name, + Url: sessionUrl, + Source: url, + Status: "Started", + } + + jsonData, err := json.Marshal(workshop) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + + w.WriteHeader(http.StatusOK) + w.Write(jsonData) +} + +func (b *DockerWorkshopsApi) DeleteWorkshop(w http.ResponseWriter, r *http.Request) { + queryParams := r.URL.Query() + + name := queryParams.Get("name") + + if name == "" { + http.Error(w, "workshop session name required", http.StatusBadRequest) + return + } + + err := b.Manager.DeleteWorkshop(name, os.Stdout, os.Stderr) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + workshop := DockerWorkshopDetails{ + Name: name, + Status: "Stopped", + } + + jsonData, err := json.Marshal(workshop) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + + w.WriteHeader(http.StatusOK) + w.Write(jsonData) +} diff --git a/client-programs/pkg/docker/workshop_manager.go b/client-programs/pkg/docker/workshop_manager.go new file mode 100644 index 000000000..e0c7db4e9 --- /dev/null +++ b/client-programs/pkg/docker/workshop_manager.go @@ -0,0 +1,1068 @@ +package docker + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path" + "path/filepath" + "runtime" + "slices" + "strings" + "sync" + "text/template" + + yttcmd "carvel.dev/ytt/pkg/cmd/template" + composeloader "github.com/compose-spec/compose-go/v2/loader" + composetypes "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/flags" + "github.com/docker/compose/v5/pkg/api" + "github.com/docker/compose/v5/pkg/compose" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/volume" + "github.com/docker/docker/client" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + eduk8sWorkshops "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" + "go.yaml.in/yaml/v2" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/kind/pkg/cluster" + "sigs.k8s.io/kind/pkg/cmd" +) + +const ( + // Workshop status constants + WorkshopStatusStarting = "Starting" + WorkshopStatusRunning = "Running" + WorkshopStatusStopping = "Stopping" +) + +const containerScript = `exec bash -s << "EOF" +mkdir -p /opt/eduk8s/config +cat > /opt/eduk8s/config/workshop.yaml << "EOS" +{{ .WorkshopConfig -}} +EOS +{{ if .Assets -}} +cat > /opt/eduk8s/config/vendir-assets-01.yaml << "EOS" +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config +directories: +- path: /opt/assets/files + contents: + - directory: + path: /opt/eduk8s/mnt/assets + path: . +EOS +{{ else -}} +{{ range $k, $v := .VendirFilesConfig -}} +{{ $off := inc $k -}} +cat > /opt/eduk8s/config/vendir-assets-{{ printf "%02d" $off }}.yaml << "EOS" +{{ $v -}} +EOS +{{ end -}} +{{ end -}} +{{ if .VendirPackagesConfig -}} +cat > /opt/eduk8s/config/vendir-packages.yaml << "EOS" +{{ .VendirPackagesConfig -}} +EOS +{{ end -}} +{{ if .KubeConfig -}} +mkdir -p /opt/kubeconfig +cat > /opt/kubeconfig/config << "EOS" +{{ .KubeConfig -}} +EOS +{{ end -}} +exec start-container +EOF +` + +var ( + containerScriptTemplateOnce sync.Once + containerScriptTemplateCached *template.Template + containerScriptTemplateErr error +) + +type DockerWorkshopsManager struct { + Statuses map[string]DockerWorkshopDetails + StatusesMutex sync.Mutex + composeService api.Compose + composeServiceMu sync.Mutex + dockerClient *client.Client + dockerClientMu sync.RWMutex +} + +func NewDockerWorkshopsManager() DockerWorkshopsManager { + return DockerWorkshopsManager{ + Statuses: map[string]DockerWorkshopDetails{}, + StatusesMutex: sync.Mutex{}, + } +} + +type DockerWorkshopDetails struct { + Name string `json:"name"` + Url string `json:"url,omitempty"` + Source string `json:"source,omitempty"` + Status string `json:"status"` +} + +type DockerWorkshopDeployConfig struct { + Path string + Host string + Port uint + LocalRepository string + DisableOpenBrowser bool + ImageRepository string + ImageVersion string + Cluster string + KubeConfig string + Assets string + WorkshopFile string + WorkshopImage string + WorkshopVersion string + DataValuesFlags yttcmd.DataValuesFlags +} + + +func (m *DockerWorkshopsManager) WorkshopStatus(name string) (DockerWorkshopDetails, bool) { + workshops, err := m.ListWorkshops() + + if err != nil { + return DockerWorkshopDetails{}, false + } + + for _, workshop := range workshops { + if workshop.Name == name { + return workshop, true + } + } + + return DockerWorkshopDetails{}, false +} + +func (m *DockerWorkshopsManager) SetWorkshopStatus(name string, url string, source string, status string) { + m.StatusesMutex.Lock() + + m.Statuses[name] = DockerWorkshopDetails{ + Name: name, + Url: url, + Source: source, + Status: status, + } + + m.StatusesMutex.Unlock() +} + +func (m *DockerWorkshopsManager) ClearWorkshopStatus(name string) { + m.StatusesMutex.Lock() + + delete(m.Statuses, name) + + m.StatusesMutex.Unlock() +} + +func (m *DockerWorkshopsManager) ListWorkshops() ([]DockerWorkshopDetails, error) { + ctx := context.Background() + + cli, err := m.GetDockerClient() + if err != nil { + return nil, err + } + + containers, err := cli.ContainerList(ctx, container.ListOptions{Filters: getWorkshopContainerLabelFilters()}) + if err != nil { + return nil, errors.Wrap(err, "unable to list Educates workshop containers") + } + + // Copy statuses while holding lock briefly + m.StatusesMutex.Lock() + setOfWorkshops := make(map[string]DockerWorkshopDetails, len(m.Statuses)) + for _, details := range m.Statuses { + if details.Status == WorkshopStatusStarting { + setOfWorkshops[details.Name] = details + } + } + statusesCopy := make(map[string]DockerWorkshopDetails, len(m.Statuses)) + for k, v := range m.Statuses { + statusesCopy[k] = v + } + m.StatusesMutex.Unlock() + + for _, ctr := range containers { + url, found := ctr.Labels[constants.EducatesWorkshopLabelAnnotationURL] + source := ctr.Labels[constants.EducatesWorkshopLabelAnnotationSource] + instance := ctr.Labels[constants.EducatesWorkshopLabelAnnotationSession] + + status := WorkshopStatusRunning + if details, statusFound := statusesCopy[instance]; statusFound { + status = details.Status + } + + if found && url != "" && len(ctr.Names) != 0 { + setOfWorkshops[instance] = DockerWorkshopDetails{ + Name: instance, + Url: url, + Source: source, + Status: status, + } + } + } + + workshopsList := make([]DockerWorkshopDetails, 0, len(setOfWorkshops)) + for _, details := range setOfWorkshops { + workshopsList = append(workshopsList, details) + } + + return workshopsList, nil +} + +// GetComposeService returns a ComposeService instance, initializing it if necessary. +// It uses a singleton pattern to reuse the same service instance across operations. +func (m *DockerWorkshopsManager) GetComposeService(stdout io.Writer, stderr io.Writer) (api.Compose, error) { + m.composeServiceMu.Lock() + defer m.composeServiceMu.Unlock() + + if m.composeService != nil { + return m.composeService, nil + } + + dockerCLI, err := command.NewDockerCli() + if err != nil { + return nil, errors.Wrap(err, "unable to create docker CLI") + } + + err = dockerCLI.Initialize(&flags.ClientOptions{}) + if err != nil { + return nil, errors.Wrap(err, "unable to initialize docker CLI") + } + + // Create ComposeService with options for I/O redirection and non-interactive mode + service, err := compose.NewComposeService( + dockerCLI, + compose.WithOutputStream(stdout), + compose.WithErrorStream(stderr), + compose.WithPrompt(compose.AlwaysOkPrompt()), + compose.WithMaxConcurrency(4), + ) + if err != nil { + return nil, errors.Wrap(err, "unable to create compose service") + } + + m.composeService = service + return service, nil +} + +// GetDockerClient returns a Docker client instance, initializing it if necessary. +// It uses a singleton pattern to reuse the same client instance across operations. +func (m *DockerWorkshopsManager) GetDockerClient() (*client.Client, error) { + // Try read lock first for fast path + m.dockerClientMu.RLock() + if m.dockerClient != nil { + defer m.dockerClientMu.RUnlock() + return m.dockerClient, nil + } + m.dockerClientMu.RUnlock() + + // Acquire write lock to initialize + m.dockerClientMu.Lock() + defer m.dockerClientMu.Unlock() + + // Double-check after acquiring write lock + if m.dockerClient != nil { + return m.dockerClient, nil + } + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return nil, errors.Wrap(err, "unable to create docker client") + } + + m.dockerClient = cli + return cli, nil +} + +// getContainerScriptTemplate returns the cached container script template. +func getContainerScriptTemplate() (*template.Template, error) { + containerScriptTemplateOnce.Do(func() { + funcMap := template.FuncMap{ + "inc": func(i int) int { return i + 1 }, + } + containerScriptTemplateCached, containerScriptTemplateErr = + template.New("entrypoint").Funcs(funcMap).Parse(containerScript) + }) + return containerScriptTemplateCached, containerScriptTemplateErr +} + + +// isDockerSocketEnabled checks if Docker socket is enabled in the workshop spec. +func isDockerSocketEnabled(workshop *unstructured.Unstructured) bool { + dockerEnabled, found, _ := unstructured.NestedBool( + workshop.Object, "spec", "session", "applications", "docker", "enabled") + if !found || !dockerEnabled { + return false + } + + extraServices, _, _ := unstructured.NestedMap( + workshop.Object, "spec", "session", "applications", "docker", "compose") + + socketEnabledDefault := len(extraServices) == 0 + socketEnabled, found, _ := unstructured.NestedBool( + workshop.Object, "spec", "session", "applications", "docker", "socket", "enabled") + + if !found { + return socketEnabledDefault + } + return socketEnabled +} + +// applyWorkshopVariables replaces workshop-related variables in a string efficiently. +func applyWorkshopVariables(content, name, localRepository, version string) string { + replacer := strings.NewReplacer( + "$(image_repository)", localRepository, + "$(workshop_name)", name, + "$(workshop_version)", version, + "$(platform_arch)", runtime.GOARCH, + ) + return replacer.Replace(content) +} + +func (m *DockerWorkshopsManager) DeployWorkshop(o *DockerWorkshopDeployConfig, stdout io.Writer, stderr io.Writer) (string, error) { + var err error + + // If path not provided assume the current working directory. When loading + // the workshop will then expect the workshop definition to reside in the + // resources/workshop.yaml file under the directory, the same as if a + // directory path was provided explicitly. + + if o.Path == "" { + o.Path = "." + } + + // Load the workshop definition. The path can be a HTTP/HTTPS URL for a + // local file system path for a directory or file. + + var workshop *unstructured.Unstructured + + definitionConfig := eduk8sWorkshops.WorkshopDefinitionConfig{ + Name: "", + Path: o.Path, + Portal: constants.DefaultPortalName, + WorkshopFile: o.WorkshopFile, + WorkshopVersion: o.WorkshopVersion, + DataValueFlags: o.DataValuesFlags, + } + if workshop, err = eduk8sWorkshops.LoadWorkshopDefinition(&definitionConfig); err != nil { + return "", err + } + + name := workshop.GetName() + + m.SetWorkshopStatus(name, "", o.Path, WorkshopStatusStarting) + + defer m.ClearWorkshopStatus(name) + + originalName := workshop.GetAnnotations()[constants.EducatesWorkshopLabelAnnotationWorkshop] + + configFileDir := utils.GetEducatesHomeDir() + composeConfigDir := path.Join(configFileDir, "compose", name) + + err = os.MkdirAll(composeConfigDir, os.ModePerm) + + if err != nil { + return name, errors.Wrapf(err, "unable to create workshops compose directory") + } + + ctx := context.Background() + + cli, err := m.GetDockerClient() + if err != nil { + return name, err + } + + _, err = cli.ContainerInspect(ctx, name) + + if err == nil { + return name, errors.New("this workshop is already running") + } + + registryNetwork := false + + if o.LocalRepository == "localhost:5001" { + o.LocalRepository = "registry.docker.local:5000" + } + + var registryIP string + + registryInfo, err := cli.ContainerInspect(ctx, constants.EducatesRegistryContainer) + + if err == nil { + educatesNetwork, exists := registryInfo.NetworkSettings.Networks[constants.EducatesNetworkName] + + if !exists { + return name, errors.New("registry is not attached to educates network") + } + + registryNetwork = true + registryIP = educatesNetwork.IPAddress + } else { + o.LocalRepository = "" + } + + var kubeConfigData string + + if o.KubeConfig != "" { + kubeConfigBytes, err := os.ReadFile(o.KubeConfig) + + if err != nil { + return name, errors.Wrap(err, "unable to read kubeconfig file") + } + + kubeConfigData = string(kubeConfigBytes) + } + + if o.Cluster != "" { + kubeConfigData, err = generateClusterKubeconfig(o.Cluster) + + if err != nil { + return name, err + } + } + + var workshopConfigData string + var vendirFilesConfigData []string + var vendirPackagesConfigData string + var workshopImageName string + + var workshopPortsConfig []composetypes.ServicePortConfig + var workshopVolumesConfig []composetypes.ServiceVolumeConfig + + var workshopEnvironment []string + var workshopLabels map[string]string + var workshopExtraHosts map[string]string + + var workshopComposeProject *composetypes.Project + + if workshopConfigData, err = generateWorkshopConfig(workshop); err != nil { + return name, err + } + + if vendirFilesConfigData, err = generateVendirFilesConfig(workshop, originalName, o.LocalRepository, o.WorkshopVersion); err != nil { + return name, err + } + + if vendirPackagesConfigData, err = generateVendirPackagesConfig(workshop, originalName, o.LocalRepository, o.WorkshopVersion); err != nil { + return name, err + } + + if workshopImageName, err = generateWorkshopImageName(workshop, o.LocalRepository, o.ImageRepository, o.ImageVersion, o.WorkshopImage, o.WorkshopVersion); err != nil { + return name, err + } + + if workshopPortsConfig, err = composetypes.ParsePortConfig(fmt.Sprintf("%s:%d:10081", o.Host, o.Port)); err != nil { + return name, errors.Wrap(err, "unable to generate workshop ports config") + } + + if workshopVolumesConfig, err = generateWorkshopVolumeMounts(workshop, o.Assets); err != nil { + return name, err + } + + if workshopEnvironment, err = generateWorkshopEnvironment(workshop, o.LocalRepository, o.Host, o.Port); err != nil { + return name, err + } + + if workshopLabels, err = generateWorkshopLabels(workshop, o.Host, o.Port); err != nil { + return name, err + } + + if registryIP != "" { + if workshopExtraHosts, err = generateWorkshopExtraHosts(workshop, registryIP); err != nil { + return name, err + } + } + + if workshopComposeProject, err = extractWorkshopComposeConfig(workshop); err != nil { + return name, err + } + + type TemplateInputs struct { + WorkshopConfig string + VendirFilesConfig []string + VendirPackagesConfig string + KubeConfig string + Assets string + } + + inputs := TemplateInputs{ + WorkshopConfig: workshopConfigData, + VendirFilesConfig: vendirFilesConfigData, + VendirPackagesConfig: vendirPackagesConfigData, + KubeConfig: kubeConfigData, + Assets: o.Assets, + } + + containerScriptTemplate, err := getContainerScriptTemplate() + if err != nil { + return name, errors.Wrap(err, "not able to parse container script template") + } + + var containerScriptData bytes.Buffer + + err = containerScriptTemplate.Execute(&containerScriptData, inputs) + + if err != nil { + return name, errors.Wrap(err, "not able to generate container script") + } + + networks := map[string]*composetypes.ServiceNetworkConfig{ + "default": {}, + } + + if registryNetwork { + networks[constants.EducatesNetworkName] = &composetypes.ServiceNetworkConfig{} + } + + var extraHostsList composetypes.HostsList + if len(workshopExtraHosts) > 0 { + extraHostsList = make(composetypes.HostsList, len(workshopExtraHosts)) + for hostname, ip := range workshopExtraHosts { + extraHostsList[hostname] = []string{ip} + } + } + + workshopServiceConfig := composetypes.ServiceConfig{ + Name: "workshop", + Image: workshopImageName, + Command: composetypes.ShellCommand([]string{"bash", "-c", containerScriptData.String()}), + User: "1001:0", + Ports: workshopPortsConfig, + Volumes: workshopVolumesConfig, + Environment: composetypes.NewMappingWithEquals(workshopEnvironment), + Labels: composetypes.Labels(workshopLabels), + ExtraHosts: extraHostsList, + DependsOn: composetypes.DependsOnConfig{}, + Networks: networks, + } + + if o.Cluster != "" { + workshopServiceConfig.Networks["kind"] = &composetypes.ServiceNetworkConfig{} + } + + if isDockerSocketEnabled(workshop) { + workshopServiceConfig.GroupAdd = []string{"docker"} + } + + workshopServices := composetypes.Services{ + "workshop": workshopServiceConfig, + } + + composeConfig := composetypes.Project{ + Name: originalName, + Services: workshopServices, + Networks: composetypes.Networks{ + "educates": composetypes.NetworkConfig{Name: constants.EducatesNetworkName, External: true}, + }, + Volumes: composetypes.Volumes{ + "workshop": composetypes.VolumeConfig{}, + }, + } + + if workshopComposeProject != nil { + for serviceName, extraService := range workshopComposeProject.Services { + // TODO: Maybe modify extraService.Ports to add the host IP + composeConfig.Services[serviceName] = extraService + + workshopServiceConfig.DependsOn[serviceName] = composetypes.ServiceDependency{ + Condition: composetypes.ServiceConditionHealthy, + } + } + + for volumeName, extraVolume := range workshopComposeProject.Volumes { + if volumeName != "workshop" { + composeConfig.Volumes[volumeName] = extraVolume + } + } + } + + if o.Cluster != "" { + composeConfig.Networks["kind"] = composetypes.NetworkConfig{Name: "kind", External: true} + } + + composeConfigBytes, err := yaml.Marshal(&composeConfig) + + if err != nil { + return name, errors.Wrap(err, "failed to generate compose config") + } + + composeConfigFilePath := path.Join(composeConfigDir, "docker-compose.yaml") + + composeConfigFile, err := os.OpenFile(composeConfigFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) + + if err != nil { + return name, errors.Wrapf(err, "unable to create workshop config file %s", composeConfigFilePath) + } + + if _, err = composeConfigFile.Write(composeConfigBytes); err != nil { + return name, errors.Wrapf(err, "unable to write workshop config file %s", composeConfigFilePath) + } + + if err := composeConfigFile.Close(); err != nil { + return name, errors.Wrapf(err, "unable to close workshop config file %s", composeConfigFilePath) + } + + // Get ComposeService instance + service, err := m.GetComposeService(stdout, stderr) + if err != nil { + return name, errors.Wrap(err, "unable to get compose service") + } + + // Load the project from the compose file + project, err := service.LoadProject(ctx, api.ProjectLoadOptions{ + ConfigPaths: []string{composeConfigFilePath}, + ProjectName: name, + }) + if err != nil { + return name, errors.Wrap(err, "failed to load project") + } + + // Start the services using SDK + err = service.Up(ctx, project, api.UpOptions{ + Create: api.CreateOptions{ + Recreate: api.RecreateDiverged, + RecreateDependencies: api.RecreateDiverged, + RemoveOrphans: false, + }, + Start: api.StartOptions{}, + }) + if err != nil { + return name, errors.Wrap(err, "unable to start workshop") + } + + return name, nil +} + +func (m *DockerWorkshopsManager) DeleteWorkshop(name string, stdout io.Writer, stderr io.Writer) error { + m.SetWorkshopStatus(name, "", "", WorkshopStatusStopping) + + defer m.ClearWorkshopStatus(name) + + ctx := context.Background() + + // Get ComposeService instance + service, err := m.GetComposeService(stdout, stderr) + if err != nil { + return errors.Wrap(err, "unable to get compose service") + } + + // Load the project to get the project name + configFileDir := utils.GetEducatesHomeDir() + composeConfigDir := path.Join(configFileDir, "compose", name) + composeConfigFilePath := path.Join(composeConfigDir, "docker-compose.yaml") + + // Try to load project, but if file doesn't exist, just use the name + project, err := service.LoadProject(ctx, api.ProjectLoadOptions{ + ConfigPaths: []string{composeConfigFilePath}, + ProjectName: name, + }) + if err != nil { + // If project can't be loaded, still try to remove by name + project = nil + } + + projectName := name + if project != nil { + projectName = project.Name + } + + // Stop and remove services using SDK + err = service.Down(ctx, projectName, api.DownOptions{ + RemoveOrphans: true, + Volumes: true, + }) + if err != nil { + return errors.Wrap(err, "unable to stop workshop") + } + + cli, err := m.GetDockerClient() + if err != nil { + return err + } + + // List volumes that match the workshop name pattern and remove them + filters := filters.NewArgs() + filters.Add("name", fmt.Sprintf("%s_workshop", name)) + volumesListResponse, err := cli.VolumeList(ctx, volume.ListOptions{Filters: filters}) + if err != nil { + return errors.Wrap(err, "unable to list workshop volumes") + } + + for _, volume := range volumesListResponse.Volumes { + if err := cli.VolumeRemove(ctx, volume.Name, false); err != nil { + return errors.Wrap(err, "unable to delete workshop volume") + } + } + workshopConfigDir := path.Join(configFileDir, "workshops", name) + + if err := os.RemoveAll(workshopConfigDir); err != nil { + fmt.Fprintf(stderr, "Warning: failed to remove workshop config dir: %v\n", err) + } + if err := os.RemoveAll(composeConfigDir); err != nil { + fmt.Fprintf(stderr, "Warning: failed to remove compose config dir: %v\n", err) + } + + return nil +} + + +func generateWorkshopConfig(workshop *unstructured.Unstructured) (string, error) { + workshopTitle, _, _ := unstructured.NestedFieldNoCopy(workshop.Object, "spec", "title") + workshopDescription, _, _ := unstructured.NestedFieldNoCopy(workshop.Object, "spec", "description") + applicationsConfig, _, _ := unstructured.NestedFieldNoCopy(workshop.Object, "spec", "session", "applications") + ingressesConfig, _, _ := unstructured.NestedSlice(workshop.Object, "spec", "session", "ingresses") + dashboardsConfig, _, _ := unstructured.NestedSlice(workshop.Object, "spec", "session", "dashboards") + + workshopConfig := map[string]interface{}{ + "spec": map[string]interface{}{ + "title": workshopTitle, + "description": workshopDescription, + "session": map[string]interface{}{ + "applications": applicationsConfig, + "ingresses": ingressesConfig, + "dashboards": dashboardsConfig, + }, + }, + } + + workshopConfigData, err := yaml.Marshal(&workshopConfig) + + if err != nil { + return "", errors.Wrap(err, "failed to generate workshop config") + } + + return string(workshopConfigData), nil +} + +func generateVendirFilesConfig(workshop *unstructured.Unstructured, name string, localRepository string, version string) ([]string, error) { + var vendirConfigs []string + + workshopVersion, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") + if !found { + workshopVersion = version + } + + filesItems, found, _ := unstructured.NestedSlice(workshop.Object, "spec", "workshop", "files") + if !found || len(filesItems) == 0 { + return vendirConfigs, nil + } + + for _, filesItem := range filesItems { + filesMap, ok := filesItem.(map[string]interface{}) + if !ok { + continue + } + + directoriesConfig := []map[string]interface{}{} + + var filesItemPath string + if tmpPath, found := filesMap["path"]; found { + if pathStr, ok := tmpPath.(string); ok { + filesItemPath = pathStr + } else { + filesItemPath = "." + } + } else { + filesItemPath = "." + } + + filesItemPath = filepath.Clean(path.Join("/opt/assets/files", filesItemPath)) + filesMap["path"] = "." + + directoriesConfig = append(directoriesConfig, map[string]interface{}{ + "path": filesItemPath, + "contents": []interface{}{filesItem}, + }) + + vendirConfig := map[string]interface{}{ + "apiVersion": "vendir.k14s.io/v1alpha1", + "kind": "Config", + "directories": directoriesConfig, + } + + vendirConfigBytes, err := yaml.Marshal(&vendirConfig) + if err != nil { + return []string{}, errors.Wrap(err, "failed to generate vendir config") + } + + vendirConfigString := applyWorkshopVariables(string(vendirConfigBytes), name, localRepository, workshopVersion) + vendirConfigs = append(vendirConfigs, vendirConfigString) + } + + return vendirConfigs, nil +} + +func generateVendirPackagesConfig(workshop *unstructured.Unstructured, name string, localRepository string, version string) (string, error) { + workshopVersion, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") + if !found { + workshopVersion = version + } + + packagesItems, found, _ := unstructured.NestedSlice(workshop.Object, "spec", "workshop", "packages") + if !found || len(packagesItems) == 0 { + return "", nil + } + + directoriesConfig := []map[string]interface{}{} + + for _, packagesItem := range packagesItems { + tmpPackagesItem, ok := packagesItem.(map[string]interface{}) + if !ok { + continue + } + + tmpName, found := tmpPackagesItem["name"] + if !found { + continue + } + + nameStr, ok := tmpName.(string) + if !ok { + continue + } + + packagesItemPath := filepath.Clean(path.Join("/opt/packages", nameStr)) + + tmpPackagesFilesItem, found := tmpPackagesItem["files"] + if !found { + continue + } + + packagesFilesItem, ok := tmpPackagesFilesItem.([]interface{}) + if !ok { + continue + } + + for _, tmpEntry := range packagesFilesItem { + if entry, ok := tmpEntry.(map[string]interface{}); ok { + if _, found := entry["path"]; !found { + entry["path"] = "." + } + } + } + + directoriesConfig = append(directoriesConfig, map[string]interface{}{ + "path": packagesItemPath, + "contents": packagesFilesItem, + }) + } + + vendirConfig := map[string]interface{}{ + "apiVersion": "vendir.k14s.io/v1alpha1", + "kind": "Config", + "directories": directoriesConfig, + } + + vendirConfigBytes, err := yaml.Marshal(&vendirConfig) + if err != nil { + return "", errors.Wrap(err, "failed to generate vendir config") + } + + return applyWorkshopVariables(string(vendirConfigBytes), name, localRepository, workshopVersion), nil +} + +func generateWorkshopImageName(workshop *unstructured.Unstructured, localRepository string, imageRepository string, baseImageVersion string, workshopImage string, workshopVersion string) (string, error) { + if version, found, _ := unstructured.NestedString(workshop.Object, "spec", "version"); found { + workshopVersion = version + } + + image, found, err := unstructured.NestedString(workshop.Object, "spec", "workshop", "image") + if err != nil { + return "", errors.Wrapf(err, "unable to parse workshop definition") + } + + if !found || image == "" { + image = "base-environment:*" + } + + if workshopImage != "" { + return workshopImage, nil + } + + defaultImageVersion := strings.TrimSpace(baseImageVersion) + + // Map of environment placeholders to their image names + imageMap := map[string]string{ + "base-environment:*": "educates-base-environment", + "jdk8-environment:*": "educates-jdk8-environment", + "jdk11-environment:*": "educates-jdk11-environment", + "jdk17-environment:*": "educates-jdk17-environment", + "jdk21-environment:*": "educates-jdk21-environment", + "conda-environment:*": "educates-conda-environment", + } + + repo := imageRepository + if defaultImageVersion == "latest" { + repo = "localhost:5001" + } + + for placeholder, imageName := range imageMap { + replacement := fmt.Sprintf("%s/%s:%s", repo, imageName, defaultImageVersion) + image = strings.ReplaceAll(image, placeholder, replacement) + } + + return applyWorkshopVariables(image, "", localRepository, workshopVersion), nil +} + +func generateWorkshopVolumeMounts(workshop *unstructured.Unstructured, assets string) ([]composetypes.ServiceVolumeConfig, error) { + filesMounts := []composetypes.ServiceVolumeConfig{ + { + Type: "volume", + Source: "workshop", + Target: "/home/eduk8s", + }, + } + + if assets != "" { + assets = filepath.Clean(assets) + absAssets, err := filepath.Abs(assets) + if err != nil { + return []composetypes.ServiceVolumeConfig{}, errors.Wrap(err, "can't resolve local workshop assets path") + } + + filesMounts = append(filesMounts, composetypes.ServiceVolumeConfig{ + Type: "bind", + Source: absAssets, + Target: "/opt/eduk8s/mnt/assets", + ReadOnly: true, + }) + } + + if isDockerSocketEnabled(workshop) { + dockerSocketSource := "/var/run/docker.sock" + if runtime.GOOS != "linux" { + dockerSocketSource = "/var/run/docker.sock.raw" + } + + filesMounts = append(filesMounts, composetypes.ServiceVolumeConfig{ + Type: "bind", + Source: dockerSocketSource, + Target: "/var/run/docker/docker.sock", + ReadOnly: true, + }) + } + + return filesMounts, nil +} + +func generateWorkshopEnvironment(workshop *unstructured.Unstructured, localRepository string, host string, port uint) ([]string, error) { + domain := fmt.Sprintf("%s.nip.io", strings.ReplaceAll(host, ".", "-")) + + return []string{ + fmt.Sprintf("WORKSHOP_NAME=%s", workshop.GetName()), + "SESSION_NAME=workshop", + fmt.Sprintf("SESSION_URL=http://workshop.%s:%d", domain, port), + "INGRESS_PROTOCOL=http", + fmt.Sprintf("INGRESS_DOMAIN=%s", domain), + fmt.Sprintf("INGRESS_PORT_SUFFIX=:%d", port), + fmt.Sprintf("IMAGE_REPOSITORY=%s", localRepository), + }, nil +} + +func generateWorkshopLabels(workshop *unstructured.Unstructured, host string, port uint) (map[string]string, error) { + labels := workshop.GetAnnotations() + + domain := fmt.Sprintf("%s.nip.io", strings.ReplaceAll(host, ".", "-")) + + labels[constants.EducatesContainersAppLabelKey] = constants.EducatesContainersAppLabel + labels[constants.EducatesContainersRoleLabelKey] = constants.EducatesContainersWorkshopRoleLabel + labels[constants.EducatesWorkshopLabelAnnotationURL] = fmt.Sprintf("http://workshop.%s:%d", domain, port) + labels[constants.EducatesWorkshopLabelAnnotationSession] = workshop.GetName() + + return labels, nil +} + +func generateWorkshopExtraHosts(workshop *unstructured.Unstructured, registryIP string) (map[string]string, error) { + hosts := map[string]string{} + + if registryIP != "" { + hosts["registry.docker.local"] = registryIP + } + + return hosts, nil +} + +func extractWorkshopComposeConfig(workshop *unstructured.Unstructured) (*composetypes.Project, error) { + composeConfigObj, found, _ := unstructured.NestedMap(workshop.Object, "spec", "session", "applications", "docker", "compose") + + if found { + composeConfigObjBytes, err := yaml.Marshal(&composeConfigObj) + + if err != nil { + return nil, errors.Wrap(err, "unable to parse workshop docker compose config") + } + + configFiles := composetypes.ConfigFile{ + Content: composeConfigObjBytes, + } + + composeConfigDetails := composetypes.ConfigDetails{ + ConfigFiles: []composetypes.ConfigFile{configFiles}, + } + + return composeloader.LoadWithContext(context.Background(), composeConfigDetails, func(options *composeloader.Options) { + options.SkipConsistencyCheck = true + options.SkipNormalization = true + options.ResolvePaths = false + options.SkipValidation = true + }) + } + + return nil, nil +} + +func generateClusterKubeconfig(name string) (string, error) { + provider := cluster.NewProvider( + cluster.ProviderWithLogger(cmd.NewLogger()), + ) + + clusters, err := provider.List() + + if err != nil { + return "", errors.Wrap(err, "unable to get list of clusters") + } + + if !slices.Contains(clusters, name) { + return "", errors.Errorf("cluster %s doesn't exist", name) + } + + file, err := os.CreateTemp("", "kubeconfig-") + + if err != nil { + return "", errors.Wrap(err, "unable to generate kubeconfig file") + } + + defer os.Remove(file.Name()) + + err = provider.ExportKubeConfig(name, file.Name(), true) + + if err != nil { + return "", errors.Wrap(err, "unable to generate kubeconfig file") + } + + kubeConfigData, err := os.ReadFile(file.Name()) + + if err != nil { + return "", errors.Wrap(err, "unable to generate kubeconfig file") + } + + return string(kubeConfigData), nil +} + +func getWorkshopContainerLabelFilters() filters.Args { + return filters.NewArgs( + filters.Arg("label", constants.EducatesContainersAppLabelKey+"="+constants.EducatesContainersAppLabel), + filters.Arg("label", constants.EducatesContainersRoleLabelKey+"="+constants.EducatesContainersWorkshopRoleLabel), + ) +} diff --git a/client-programs/pkg/workshops/publish.go b/client-programs/pkg/educates/local/workshops/manager.go similarity index 54% rename from client-programs/pkg/workshops/publish.go rename to client-programs/pkg/educates/local/workshops/manager.go index 73da15aa9..b75d4dc64 100644 --- a/client-programs/pkg/workshops/publish.go +++ b/client-programs/pkg/educates/local/workshops/manager.go @@ -1,30 +1,58 @@ package workshops import ( - "bytes" "fmt" - "log" "os" "path/filepath" + "strconv" "strings" imgpkgcmd "carvel.dev/imgpkg/pkg/imgpkg/cmd" - "carvel.dev/kapp/pkg/kapp/cmd" vendirsync "carvel.dev/vendir/pkg/vendir/cmd" yttcmd "carvel.dev/ytt/pkg/cmd/template" - yttcmdui "carvel.dev/ytt/pkg/cmd/ui" - "carvel.dev/ytt/pkg/files" - "carvel.dev/ytt/pkg/yamlmeta" - "github.com/cppforlife/go-cli-ui/ui" + + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + eduk8sWorkshops "github.com/educates/educates-training-platform/client-programs/pkg/educates/resources/workshops" + "github.com/educates/educates-training-platform/client-programs/pkg/logger" + "github.com/educates/educates-training-platform/client-programs/pkg/templates" "github.com/pkg/errors" - "gopkg.in/yaml.v2" + "go.yaml.in/yaml/v2" + "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/kubectl/pkg/scheme" ) -type FilesPublishOptions struct { +type WorkshopManager struct { + +} + +type WorkshopNewConfig struct { + Template string + Name string + Title string + Description string + Image string + TargetDirectory string + Overwrite bool + WithKubernetesAccess bool + WithGitHubAction bool + WithVirtualCluster bool + WithDockerDaemon bool + WithImageRegistry bool + WithKubernetesConsole bool + WithEditor bool + WithTerminal bool +} + +type WorkshopExportConfig struct { + Repository string + WorkshopFile string + WorkshopVersion string + DataValuesFlags yttcmd.DataValuesFlags +} + +type WorkshopPublishConfig struct { Image string Repository string WorkshopFile string @@ -34,35 +62,111 @@ type FilesPublishOptions struct { DataValuesFlags yttcmd.DataValuesFlags } -func (o *FilesPublishOptions) Run(args []string) error { +func NewWorkshopManager() *WorkshopManager { + return &WorkshopManager{} +} + +func (m *WorkshopManager) NewWorkshop(directory string,o *WorkshopNewConfig) error { var err error - var workshopDir string + parameters := map[string]string{ + "WorkshopName": o.Name, + "WorkshopTitle": o.Title, + "WorkshopDescription": o.Description, + "WorkshopImage": o.Image, + "WithKubernetesAccess": strconv.FormatBool(o.WithKubernetesAccess), + "WithVirtualCluster": strconv.FormatBool(o.WithVirtualCluster), + "WithDockerDaemon": strconv.FormatBool(o.WithDockerDaemon), + "WithImageRegistry": strconv.FormatBool(o.WithImageRegistry), + "WithKubernetesConsole": strconv.FormatBool(o.WithKubernetesConsole), + "WithEditor": strconv.FormatBool(o.WithEditor), + "WithTerminal": strconv.FormatBool(o.WithTerminal), + } + + template := templates.InternalTemplate(o.Template) + + err = template.ApplyFiles(directory, parameters) - if len(args) != 0 { - workshopDir = filepath.Clean(args[0]) - } else { - workshopDir = "." + if err != nil { + return errors.Wrap(err, "unable to apply template") } - if workshopDir, err = filepath.Abs(workshopDir); err != nil { - return errors.Wrap(err, "couldn't convert workshop directory to absolute path") + if o.WithGitHubAction { + template := templates.InternalTemplate("single") + err = template.ApplyGitHubAction(directory, parameters) } - fileInfo, err := os.Stat(workshopDir) + return err +} + +func (m *WorkshopManager) Export(directory string,o *WorkshopExportConfig) (string, error) { + // If image name hasn't been supplied read workshop definition file and + // try to work out image name to Export workshop as. + + rootDirectory := directory + workshopFilePath := o.WorkshopFile - if err != nil || !fileInfo.IsDir() { - return errors.New("workshop directory does not exist or path is not a workshop directory") + if !filepath.IsAbs(workshopFilePath) { + workshopFilePath = filepath.Join(rootDirectory, workshopFilePath) + } + + workshopFileData, err := os.ReadFile(workshopFilePath) + + if err != nil { + return "", errors.Wrapf(err, "cannot open workshop definition %q", workshopFilePath) + } + + // Process the workshop YAML data for ytt templating and data variables. + + if workshopFileData, err = eduk8sWorkshops.ProcessWorkshopDefinition(workshopFileData, o.DataValuesFlags); err != nil { + return "", errors.Wrap(err, "unable to process workshop definition as template") } - return o.Publish(workshopDir) + workshopFileData = []byte(strings.ReplaceAll(string(workshopFileData), "$(image_repository)", o.Repository)) + workshopFileData = []byte(strings.ReplaceAll(string(workshopFileData), "$(workshop_version)", o.WorkshopVersion)) + + decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder() + + workshop := &unstructured.Unstructured{} + + err = runtime.DecodeInto(decoder, workshopFileData, workshop) + + if err != nil { + return "", errors.Wrap(err, "couldn't parse workshop definition") + } + + if workshop.GetAPIVersion() != constants.EducatesTrainingAPIGroupVersion || workshop.GetKind() != "Workshop" { + return "", errors.New("invalid type for workshop definition") + } + + // Insert workshop version property if not specified. + + _, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") + + if !found && o.WorkshopVersion != "latest" { + unstructured.SetNestedField(workshop.Object, o.WorkshopVersion, "spec", "version") + } + + // Remove the publish section as will not be accurate after publising. + + unstructured.RemoveNestedField(workshop.Object, "spec", "publish") + + // Export modified workshop definition file. + + workshopFileData, err = yaml.Marshal(&workshop.Object) + + if err != nil { + return "", errors.Wrap(err, "couldn't convert workshop definition back to YAML") + } + + return string(workshopFileData), nil } -func (o *FilesPublishOptions) Publish(workshopDir string) error { +func (m *WorkshopManager) Publish(directory string,o *WorkshopPublishConfig) error { // If image name hasn't been supplied read workshop definition file and // try to work out image name to publish workshop as. - rootDirectory := workshopDir + rootDirectory := directory workshopFilePath := o.WorkshopFile workingDirectory, err := os.Getwd() @@ -71,7 +175,7 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { return errors.Wrap(err, "cannot determine current working directory") } - includePaths := []string{workshopDir} + includePaths := []string{directory} excludePaths := []string{".git"} if !filepath.IsAbs(workshopFilePath) { @@ -86,7 +190,7 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { // Process the workshop YAML data for ytt templating and data variables. - if workshopFileData, err = ProcessWorkshopDefinition(workshopFileData, o.DataValuesFlags); err != nil { + if workshopFileData, err = eduk8sWorkshops.ProcessWorkshopDefinition(workshopFileData, o.DataValuesFlags); err != nil { return errors.Wrap(err, "unable to process workshop definition as template") } @@ -103,9 +207,14 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { return errors.Wrap(err, "couldn't parse workshop definition") } - fmt.Printf("Processing workshop with name %q.\n", workshop.GetName()) + // Extract vendir snippet describing subset of files to package up as the + // workshop image. + + carvelUI := logger.NewCarvelUI() - if workshop.GetAPIVersion() != "training.educates.dev/v1beta1" || workshop.GetKind() != "Workshop" { + carvelUI.PrintLinef("Processing workshop with name %q", workshop.GetName()) + + if workshop.GetAPIVersion() != constants.EducatesTrainingAPIGroupVersion || workshop.GetKind() != "Workshop" { return errors.New("invalid type for workshop definition") } @@ -119,21 +228,6 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { return errors.Errorf("cannot find image name for publishing workshop %q", workshopFilePath) } - // Extract vendir snippet describing subset of files to package up as the - // workshop image. - - confUI := ui.NewConfUI(ui.NewNoopLogger()) - - uiFlags := cmd.UIFlags{ - Color: true, - JSON: false, - NonInteractive: true, - } - - uiFlags.ConfigureUI(confUI) - - defer confUI.Flush() - if fileArtifacts, found, _ := unstructured.NestedSlice(workshop.Object, "spec", "publish", "files"); found && len(fileArtifacts) != 0 { tempDir, err := os.MkdirTemp("", "educates-imgpkg") @@ -159,7 +253,7 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { if directoryConfig, found := artifactEntry.(map[string]interface{})["directory"]; found { if directoryPath, found := directoryConfig.(map[string]interface{})["path"].(string); found { if !filepath.IsAbs(directoryPath) { - directoryConfig.(map[string]interface{})["path"] = filepath.Join(workshopDir, directoryPath) + directoryConfig.(map[string]interface{})["path"] = filepath.Join(directory, directoryPath) } } } @@ -193,7 +287,7 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { return errors.Wrap(err, "unable to write vendir config file") } - syncOptions := vendirsync.NewSyncOptions(confUI) + syncOptions := vendirsync.NewSyncOptions(carvelUI) syncOptions.Directories = nil syncOptions.Files = []string{filepath.Join(tempDir, "vendir.yml")} @@ -221,10 +315,9 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { } // Now publish workshop directory contents as OCI image artifact. + carvelUI.PrintLinef("Publishing workshop files to %q", image) - fmt.Printf("Publishing workshop files to %q.\n", image) - - pushOptions := imgpkgcmd.NewPushOptions(confUI) + pushOptions := imgpkgcmd.NewPushOptions(carvelUI) pushOptions.ImageFlags.Image = image pushOptions.FileFlags.Files = append(pushOptions.FileFlags.Files, includePaths...) @@ -238,11 +331,10 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { return errors.Wrap(err, "unable to push image artifact for workshop") } - // We add a newline to output for better readability. - fmt.Println() + // // We add a newline to output for better readability. + // confUI.PrintLinef("\n") // Export modified workshop definition file. - exportWorkshop := o.ExportWorkshop if exportWorkshop != "" { @@ -254,7 +346,7 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { unstructured.SetNestedField(workshop.Object, o.WorkshopVersion, "spec", "version") } - // Remove the publish section as will not be accurate after publishing. + // Remove the publish section as will not be accurate after publising. unstructured.RemoveNestedField(workshop.Object, "spec", "publish") @@ -285,41 +377,3 @@ func (o *FilesPublishOptions) Publish(workshopDir string) error { return nil } - -/* - * ProcessWorkshopDefinition processes a workshop YAML definition file through the ytt templating engine. - * It takes the raw YAML data as input along with any data value flags for template variable substitution. - * The function returns the processed YAML with template variables replaced, or an error if processing fails. - */ - -func ProcessWorkshopDefinition(yamlData []byte, dataValueFlags yttcmd.DataValuesFlags) ([]byte, error) { - templatingOptions := yttcmd.NewOptions() - - templatingOptions.IgnoreUnknownComments = true - - templatingOptions.DataValuesFlags = dataValueFlags - - var filesToProcess []*files.File - - mainInputFile := files.MustNewFileFromSource(files.NewBytesSource("workshop.yaml", yamlData)) - - filesToProcess = append(filesToProcess, mainInputFile) - - logUI := yttcmdui.NewCustomWriterTTY(false, log.Writer(), log.Writer()) - - output := templatingOptions.RunWithFiles(yttcmd.Input{Files: filesToProcess}, logUI) - - if output.Err != nil { - return []byte{}, fmt.Errorf("execution of ytt failed: %s", output.Err) - } - - if len(output.DocSet.Items) == 0 { - return []byte{}, nil - } - - var buf bytes.Buffer - - yamlmeta.NewYAMLPrinter(&buf).Print(output.DocSet.Items[0]) - - return buf.Bytes(), nil -} diff --git a/client-programs/pkg/educates/resources/portal/manager.go b/client-programs/pkg/educates/resources/portal/manager.go new file mode 100644 index 000000000..df2b45385 --- /dev/null +++ b/client-programs/pkg/educates/resources/portal/manager.go @@ -0,0 +1,283 @@ +package portal + +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" +) + +type PortalManager struct { + client dynamic.Interface +} + +type TrainingPortalCreateConfig struct { + Portal string + Hostname string + Repository string + Capacity uint + Password string + IsPasswordSet bool + ThemeName string + CookieDomain string + Labels []string +} + +type TrainingPortalDeleteConfig struct { + Portal string +} + +type TrainingPortalListConfig struct { +} + +type TrainingPortalOpenConfig struct { + Portal string + Admin bool +} + +type TrainingPortalPasswordConfig struct { + Portal string + Admin bool +} + +func NewPortalManager(client dynamic.Interface) *PortalManager { + return &PortalManager{client: client} +} + +func (m *PortalManager) CreateTrainingPortal(cfg *TrainingPortalCreateConfig) error { + trainingPortalClient := m.client.Resource(educatesTypes.TrainingPortalResource) + + _, err := trainingPortalClient.Get(context.TODO(), cfg.Portal, metav1.GetOptions{}) + + if err != nil { + if !k8serrors.IsNotFound(err) { + return errors.Wrap(err, "unable to query training portal") + } + } else { + return errors.New("training portal already exists") + } + + trainingPortal := &unstructured.Unstructured{} + + if !cfg.IsPasswordSet { + cfg.Password = utils.RandomPassword(12) + } + + type LabelDetails struct { + Name string `json:"name"` + Value string `json:"value"` + } + + var labelOverrides []LabelDetails + + for _, value := range cfg.Labels { + parts := strings.SplitN(value, "=", 2) + labelOverrides = append(labelOverrides, LabelDetails{ + Name: parts[0], + Value: parts[1], + }) + } + + type RegistryDetails struct { + Host string `json:"host"` + Namespace string `json:"namespace"` + } + + registryHost := "" + registryNamespace := "" + + if cfg.Repository != "" { + parts := strings.SplitN(cfg.Repository, "/", 2) + + registryHost = parts[0] + + if len(parts) > 1 { + registryNamespace = parts[1] + } + + } + + trainingPortal.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": constants.EducatesTrainingAPIGroupVersion, + "kind": "TrainingPortal", + "metadata": map[string]interface{}{ + "name": cfg.Portal, + }, + "spec": map[string]interface{}{ + "portal": map[string]interface{}{ + "password": cfg.Password, + "registration": struct { + Type string `json:"type"` + }{ + Type: "anonymous", + }, + "updates": struct { + Workshop bool `json:"workshop"` + }{ + Workshop: true, + }, + "sessions": struct { + Maximum int64 `json:"maximum"` + }{ + Maximum: int64(cfg.Capacity), + }, + "workshop": map[string]interface{}{ + "defaults": struct { + Reserved int `json:"reserved"` + Registry RegistryDetails `json:"registry"` + }{ + Reserved: 0, + Registry: RegistryDetails{ + Host: registryHost, + Namespace: registryNamespace, + }, + }, + }, + "ingress": struct { + Hostname string `json:"hostname"` + }{ + Hostname: cfg.Hostname, + }, + "theme": struct { + Name string `json:"name"` + }{ + Name: cfg.ThemeName, + }, + "cookies": struct { + Domain string `json:"domain"` + }{ + Domain: cfg.CookieDomain, + }, + "labels": labelOverrides, + }, + "workshops": []interface{}{}, + }, + }) + + _, err = trainingPortalClient.Create(context.TODO(), trainingPortal, metav1.CreateOptions{FieldManager: constants.DefaultPortalName}) + + if err != nil { + return errors.Wrapf(err, "unable to create training portal %q in cluster", cfg.Portal) + } + + return nil +} + +func (m *PortalManager) DeleteTrainingPortal(cfg *TrainingPortalDeleteConfig) error { + trainingPortalClient := m.client.Resource(educatesTypes.TrainingPortalResource) + + _, err := trainingPortalClient.Get(context.TODO(), cfg.Portal, metav1.GetOptions{}) + + if k8serrors.IsNotFound(err) { + return errors.New("no portal found") + } + + err = trainingPortalClient.Delete(context.TODO(), cfg.Portal, metav1.DeleteOptions{}) + + if err != nil { + return errors.Wrap(err, "unable to delete portal") + } + + return nil +} + + +func (m *PortalManager) ListTrainingPortals(cfg *TrainingPortalListConfig) (string, error) { + trainingPortalClient := m.client.Resource(educatesTypes.TrainingPortalResource) + + trainingPortals, err := trainingPortalClient.List(context.TODO(), metav1.ListOptions{}) + + if k8serrors.IsNotFound(err) { + fmt.Println("No portals found.") + return "", nil + } + + var data [][]string + for _, item := range trainingPortals.Items { + name := item.GetName() + + sessionsMaximum, propertyExists, err := unstructured.NestedInt64(item.Object, "spec", "portal", "sessions", "maximum") + + var capacity string + + if err == nil && propertyExists { + capacity = fmt.Sprintf("%d", sessionsMaximum) + } + + url, _, _ := unstructured.NestedString(item.Object, "status", "educates", "url") + + data = append(data, []string{name, capacity, url}) + } + return utils.PrintTable([]string{"NAME", "CAPACITY", "URL"}, data), nil +} + +func (m *PortalManager) GetTrainingPortalBrowserUrl(cfg *TrainingPortalOpenConfig) (string, error) { + trainingPortalClient := m.client.Resource(educatesTypes.TrainingPortalResource) + + trainingPortal, err := trainingPortalClient.Get(context.TODO(), cfg.Portal, metav1.GetOptions{}) + + if k8serrors.IsNotFound(err) { + return "", errors.New("no workshops deployed") + } + + targetUrl, found, _ := unstructured.NestedString(trainingPortal.Object, "status", "educates", "url") + + if !found { + return "", errors.New("workshops not available") + } + + if cfg.Admin { + targetUrl = targetUrl + "/admin" + } else { + password, _, _ := unstructured.NestedString(trainingPortal.Object, "spec", "portal", "password") + + if password != "" { + values := url.Values{} + values.Add("redirect_url", "/") + values.Add("password", password) + + targetUrl = fmt.Sprintf("%s/workshops/access/?%s", targetUrl, values.Encode()) + } + } + + return targetUrl, nil +} + +func (m *PortalManager) GetTrainingPortalPassword(cfg *TrainingPortalPasswordConfig) (string, error) { + trainingPortalClient := m.client.Resource(educatesTypes.TrainingPortalResource) + + trainingPortal, err := trainingPortalClient.Get(context.TODO(), cfg.Portal, metav1.GetOptions{}) + + if k8serrors.IsNotFound(err) { + return "", errors.New("no workshops deployed") + } + + if cfg.Admin { + username, found, err := unstructured.NestedString(trainingPortal.Object, "status", "educates", "credentials", "admin", "username") + + if err != nil || !found { + return "", errors.New("unable to access credentials") + } + + password, found, err := unstructured.NestedString(trainingPortal.Object, "status", "educates", "credentials", "admin", "password") + + if err != nil || !found { + return "", errors.New("unable to access credentials") + } + + return utils.PrintTable([]string{"USERNAME", "PASSWORD"}, [][]string{{username, password}}), nil + } else { + password, _, _ := unstructured.NestedString(trainingPortal.Object, "spec", "portal", "password") + + return password, nil + } +} diff --git a/client-programs/pkg/educates/resources/sessions/manager.go b/client-programs/pkg/educates/resources/sessions/manager.go new file mode 100644 index 000000000..e8bf970fb --- /dev/null +++ b/client-programs/pkg/educates/resources/sessions/manager.go @@ -0,0 +1,167 @@ +package sessions + +import ( + "context" + "fmt" + + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + educatesrestapi "github.com/educates/educates-training-platform/client-programs/pkg/educates/restapi" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" +) + +type SessionManager struct { +} + +func NewSessionManager() *SessionManager { + return &SessionManager{} +} + + +type ListSessionsConfig struct { + Client dynamic.Interface + Portal string + Environment string +} + +type ExtendSessionConfig struct { + ClusterConfig *cluster.ClusterConfig + Portal string + Name string +} + +type SessionStatusConfig struct { + ClusterConfig *cluster.ClusterConfig + Portal string + Name string +} + +type TerminateSessionConfig struct { + ClusterConfig *cluster.ClusterConfig + Portal string + Name string +} + +func (m *SessionManager) ListSessions(cfg ListSessionsConfig) (string, error) { + workshopSessionClient := cfg.Client.Resource(educatesTypes.WorkshopsessionsResource) + + workshopSessions, err := workshopSessionClient.List(context.TODO(), metav1.ListOptions{}) + + if k8serrors.IsNotFound(err) { + return "No sessions found.", nil + } + + var sessions []unstructured.Unstructured + + for _, item := range workshopSessions.Items { + labels := item.GetLabels() + + portal, ok := labels[constants.EducatesTrainingLabelAnnotationPortalName] + + if ok && portal == cfg.Portal { + if cfg.Environment != "" { + environment, ok := labels[constants.EducatesTrainingLabelAnnotationEnvironmentName] + + if ok && environment == cfg.Environment { + sessions = append(sessions, item) + } + } else { + sessions = append(sessions, item) + } + + } + } + + if len(sessions) == 0 { + return "No sessions found.", nil + } + + var data [][]string + for _, item := range sessions { + name := item.GetName() + labels := item.GetLabels() + portal := labels[constants.EducatesTrainingLabelAnnotationPortalName] + environment := labels[constants.EducatesTrainingLabelAnnotationEnvironmentName] + + status, _, _ := unstructured.NestedString(item.Object, "status", "educates", "phase") + + data = append(data, []string{name, portal, environment, status}) + } + + return utils.PrintTable([]string{"NAME", "PORTAL", "ENVIRONMENT", "STATUS"}, data), nil +} + +func (m *SessionManager) ExtendSession(cfg ExtendSessionConfig) (string, error) { + catalogApiRequester := educatesrestapi.NewWorkshopsCatalogRequester( + cfg.ClusterConfig, + cfg.Portal, + ) + logout, err := catalogApiRequester.Login() + defer logout() + if err != nil { + return "", errors.Wrap(err, "failed to login to training portal") + } + + details, err := catalogApiRequester.ExtendWorkshopSession(cfg.Name) + if err != nil { + return "", err + } + + return printStatus(details), nil +} + +func (m *SessionManager) SessionStatus(cfg SessionStatusConfig) (string, error) { + catalogApiRequester := educatesrestapi.NewWorkshopsCatalogRequester( + cfg.ClusterConfig, + cfg.Portal, + ) + logout, err := catalogApiRequester.Login() + defer logout() + if err != nil { + return "", errors.Wrap(err, "failed to login to training portal") + } + + details, err := catalogApiRequester.GetWorkshopSession(cfg.Name) + if err != nil { + return "", err + } + + return printStatus(details), nil +} + +func (m *SessionManager) TerminateSession(cfg TerminateSessionConfig) (string, error) { + catalogApiRequester := educatesrestapi.NewWorkshopsCatalogRequester( + cfg.ClusterConfig, + cfg.Portal, + ) + logout, err := catalogApiRequester.Login() + defer logout() + if err != nil { + return "", errors.Wrap(err, "failed to login to training portal") + } + + details, err := catalogApiRequester.TerminateWorkshopSession(cfg.Name) + if err != nil { + return "", err + } + + return printStatus(details), nil + +} + +func printStatus(details *educatesrestapi.WorkshopSessionDetails) string { + return utils.PrintKeyValuesTable([][]string{ + {"Started", details.Started}, + {"Expires", details.Expires}, + {"Expiring", fmt.Sprintf("%t", details.Expiring)}, + {"Countdown", fmt.Sprintf("%d", details.Countdown)}, + {"Extendable", fmt.Sprintf("%t", details.Extendable)}, + {"Status", details.Status}}, + ) +} diff --git a/client-programs/pkg/educates/resources/workshops/manager.go b/client-programs/pkg/educates/resources/workshops/manager.go new file mode 100644 index 000000000..4d08d3e4b --- /dev/null +++ b/client-programs/pkg/educates/resources/workshops/manager.go @@ -0,0 +1,717 @@ +package workshops + +import ( + "bytes" + "context" + "crypto/sha1" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + yttcmd "carvel.dev/ytt/pkg/cmd/template" + "carvel.dev/ytt/pkg/files" + "carvel.dev/ytt/pkg/yamlmeta" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" + "github.com/educates/educates-training-platform/client-programs/pkg/logger" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + + // "github.com/educates/educates-training-platform/client-programs/pkg/workshops" + "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/kubectl/pkg/scheme" +) + +type WorkshopManager struct { + Client dynamic.Interface +} + +func NewWorkshopManager(client dynamic.Interface) *WorkshopManager { + return &WorkshopManager{Client: client} +} + +type DeployWorkshopConfig struct { + Workshop *unstructured.Unstructured + Alias string + Portal string + Capacity uint + Reserved uint + Initial uint + Expires string + Overtime string + Deadline string + Orphaned string + Overdue string + Refresh string + Registry string + Environ []string + Labels []string + OpenBrowser bool +} + +type UpdateWorkshopResourceConfig struct { + Workshop *unstructured.Unstructured +} + +type WorkshopDefinitionConfig struct { + Name string + Path string + Portal string + WorkshopFile string + WorkshopVersion string + DataValueFlags yttcmd.DataValuesFlags +} + +type ListWorkshopResourcesConfig struct { + Portal string +} + +type DeleteWorkshopResourceConfig struct { + Name string + Alias string + Portal string +} + +func (m *WorkshopManager) DeployWorkshopResource(o *DeployWorkshopConfig) error { + trainingPortalClient := m.Client.Resource(educatesTypes.TrainingPortalResource) + + trainingPortal, err := trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) + + var trainingPortalExists = true + + if k8serrors.IsNotFound(err) { + trainingPortalExists = false + + trainingPortal = &unstructured.Unstructured{} + + trainingPortal.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": constants.EducatesTrainingAPIGroupVersion, + "kind": "TrainingPortal", + "metadata": map[string]interface{}{ + "name": o.Portal, + }, + "spec": map[string]interface{}{ + "portal": map[string]interface{}{ + "password": utils.RandomPassword(12), + "registration": struct { + Type string `json:"type"` + }{ + Type: "anonymous", + }, + "updates": struct { + Workshop bool `json:"workshop"` + }{ + Workshop: true, + }, + "sessions": struct { + Maximum int64 `json:"maximum"` + }{ + Maximum: 5, + }, + "workshop": map[string]interface{}{ + "defaults": struct { + Reserved int `json:"reserved"` + }{ + Reserved: 0, + }, + }, + }, + "workshops": []interface{}{}, + }, + }) + } + + workshops, _, err := unstructured.NestedSlice(trainingPortal.Object, "spec", "workshops") + + if err != nil { + return errors.Wrap(err, "unable to retrieve workshops from training portal") + } + + var updatedWorkshops []interface{} + + if o.Expires == "" { + duration, propertyExists, err := unstructured.NestedString(o.Workshop.Object, "spec", "duration") + + if err != nil || !propertyExists { + o.Expires = "60m" + } else { + o.Expires = duration + } + } + + type EnvironDetails struct { + Name string `json:"name"` + Value string `json:"value"` + } + + var environVariables []EnvironDetails + + for _, value := range o.Environ { + parts := strings.SplitN(value, "=", 2) + environVariables = append(environVariables, EnvironDetails{ + Name: parts[0], + Value: parts[1], + }) + } + + type LabelDetails struct { + Name string `json:"name"` + Value string `json:"value"` + } + + var labelOverrides []LabelDetails + + for _, value := range o.Labels { + parts := strings.SplitN(value, "=", 2) + labelOverrides = append(labelOverrides, LabelDetails{ + Name: parts[0], + Value: parts[1], + }) + } + + var foundWorkshop = false + + for _, item := range workshops { + object := item.(map[string]interface{}) + + updatedWorkshops = append(updatedWorkshops, object) + + if object["name"] == o.Workshop.GetName() && object["alias"] == o.Alias { + foundWorkshop = true + + object["reserved"] = int64(o.Reserved) + object["initial"] = int64(o.Initial) + + if o.Capacity != 0 { + object["capacity"] = int64(o.Capacity) + } else { + delete(object, "capacity") + } + + if o.Expires != "" { + object["expires"] = o.Expires + } else { + delete(object, "expires") + } + + if o.Overtime != "" { + object["overtime"] = o.Overtime + } else { + delete(object, "overtime") + } + + if o.Deadline != "" { + object["deadline"] = o.Deadline + } else { + delete(object, "deadline") + } + + if o.Orphaned != "" { + object["orphaned"] = o.Orphaned + } else { + delete(object, "orphaned") + } + + if o.Overdue != "" { + object["overdue"] = o.Overdue + } else { + delete(object, "overdue") + } + + if o.Refresh != "" { + object["refresh"] = o.Refresh + } else { + delete(object, "refresh") + } + + var tmpEnvironVariables []interface{} + + for _, item := range environVariables { + tmpEnvironVariables = append(tmpEnvironVariables, map[string]interface{}{ + "name": item.Name, + "value": item.Value, + }) + } + + object["env"] = tmpEnvironVariables + + var tmpLabelOverrides []interface{} + + for _, item := range labelOverrides { + tmpLabelOverrides = append(tmpLabelOverrides, map[string]interface{}{ + "name": item.Name, + "value": item.Value, + }) + } + + object["labels"] = tmpLabelOverrides + } + } + + type RegistryDetails struct { + Host string `json:"host"` + Namespace string `json:"namespace,omitempty"` + } + + type WorkshopDetails struct { + Name string `json:"name"` + Alias string `json:"alias"` + Capacity int64 `json:"capacity,omitempty"` + Initial int64 `json:"initial"` + Reserved int64 `json:"reserved"` + Expires string `json:"expires,omitempty"` + Overtime string `json:"overtime,omitempty"` + Deadline string `json:"deadline,omitempty"` + Orphaned string `json:"orphaned,omitempty"` + Overdue string `json:"overdue,omitempty"` + Refresh string `json:"refresh,omitempty"` + Registry *RegistryDetails `json:"registry,omitempty"` + Environ []EnvironDetails `json:"env"` + Labels []LabelDetails `json:"labels"` + } + + if !foundWorkshop { + workshopDetails := WorkshopDetails{ + Name: o.Workshop.GetName(), + Alias: o.Alias, + Initial: int64(o.Initial), + Reserved: int64(o.Reserved), + Expires: o.Expires, + Overtime: o.Overtime, + Deadline: o.Deadline, + Orphaned: o.Orphaned, + Overdue: o.Overdue, + Refresh: o.Refresh, + Environ: environVariables, + Labels: labelOverrides, + } + + if o.Capacity != 0 { + workshopDetails.Capacity = int64(o.Capacity) + } + + if o.Registry != "" { + parts := strings.SplitN(o.Registry, "/", 2) + + host := parts[0] + var namespace string + + if len(parts) > 1 { + namespace = parts[1] + } + + registryDetails := RegistryDetails{ + Host: host, + Namespace: namespace, + } + + workshopDetails.Registry = ®istryDetails + } + + var workshopDetailsMap map[string]interface{} + + data, _ := json.Marshal(workshopDetails) + json.Unmarshal(data, &workshopDetailsMap) + + updatedWorkshops = append(updatedWorkshops, workshopDetailsMap) + } + + unstructured.SetNestedSlice(trainingPortal.Object, updatedWorkshops, "spec", "workshops") + + if trainingPortalExists { + fmt.Printf("Updating existing training portal %q.\n", trainingPortal.GetName()) + _, err = trainingPortalClient.Update(context.TODO(), trainingPortal, metav1.UpdateOptions{FieldManager: constants.DefaultPortalName}) + } else { + fmt.Printf("Creating new training portal %q.\n", trainingPortal.GetName()) + _, err = trainingPortalClient.Create(context.TODO(), trainingPortal, metav1.CreateOptions{FieldManager: constants.DefaultPortalName}) + } + + if err != nil { + return errors.Wrapf(err, "unable to update training portal %q in cluster", o.Portal) + } + + fmt.Print("Workshop added to training portal.\n") + + if o.OpenBrowser { + // Need to refetch training portal because if was just created the URL + // for access may not have been set yet. + + var targetUrl string + + fmt.Print("Checking training portal is ready.\n") + + spinner := func(iteration int) string { + spinners := `|/-\` + return string(spinners[iteration%len(spinners)]) + } + + for i := 1; i < 60; i++ { + fmt.Printf("\r[%s] Waiting...", spinner(i)) + + time.Sleep(time.Second) + + trainingPortal, err = trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) + + if err != nil { + return errors.Wrapf(err, "unable to fetch training portal %q in cluster", o.Portal) + } + + var found bool + + targetUrl, found, _ = unstructured.NestedString(trainingPortal.Object, "status", "educates", "url") + + if found { + break + } + } + + rootUrl := targetUrl + + password, _, _ := unstructured.NestedString(trainingPortal.Object, "spec", "portal", "password") + + if password != "" { + values := url.Values{} + values.Add("redirect_url", "/") + values.Add("password", password) + + targetUrl = fmt.Sprintf("%s/workshops/access/?%s", targetUrl, values.Encode()) + } + + for i := 1; i < 300; i++ { + fmt.Printf("\r[%s] Waiting...", spinner(i)) + + time.Sleep(time.Second) + + resp, err := http.Get(rootUrl) + + if err != nil || resp.StatusCode == 503 { + continue + } + + defer resp.Body.Close() + io.ReadAll(resp.Body) + + break + } + + fmt.Print("\r \r") + + fmt.Printf("Opening training portal %s.\n", targetUrl) + + return utils.OpenBrowser(targetUrl) + } + + return nil +} + + +func (m *WorkshopManager) UpdateWorkshopResource(o *UpdateWorkshopResourceConfig) error { + workshopsClient := m.Client.Resource(educatesTypes.WorkshopResource) + + // _, err := workshopsClient.Apply(context.TODO(), workshop.GetName(), workshop, metav1.ApplyOptions{FieldManager: constants.DefaultPortalName, Force: true}) + + workshopBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, o.Workshop) + + if err != nil { + return errors.Wrapf(err, "unable to update workshop definition in cluster %q", o.Workshop.GetName()) + } + + _, err = workshopsClient.Patch(context.TODO(), o.Workshop.GetName(), types.ApplyPatchType, workshopBytes, metav1.ApplyOptions{FieldManager: constants.DefaultPortalName, Force: true}.ToPatchOptions()) + + if err != nil { + return errors.Wrapf(err, "unable to update workshop definition in cluster %q", o.Workshop.GetName()) + } + + return nil +} + +func (m *WorkshopManager) ListWorkshopResources(o *ListWorkshopResourcesConfig) (string, error) { + trainingPortalClient := m.Client.Resource(educatesTypes.TrainingPortalResource) + + trainingPortal, err := trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) + + if k8serrors.IsNotFound(err) { + return "No workshops found.", nil + } + + sessionsMaximum, sessionsMaximumExists, _ := unstructured.NestedInt64(trainingPortal.Object, "spec", "portal", "sessions", "maximum") + + workshops, _, err := unstructured.NestedSlice(trainingPortal.Object, "spec", "workshops") + + if err != nil { + return "", errors.Wrap(err, "unable to retrieve workshops from training portal") + } + + if len(workshops) == 0 { + return "No workshops found.", nil + } + + workshopsClient := m.Client.Resource(educatesTypes.WorkshopResource) + + var data [][]string + for _, item := range workshops { + object := item.(map[string]interface{}) + name := object["name"].(string) + alias := object["alias"].(string) + + var capacityField string + + capacity, capacityExists := object["capacity"] + + if capacityExists { + capacityField = fmt.Sprintf("%d", capacity) + } else if sessionsMaximumExists { + capacityField = fmt.Sprintf("%d", sessionsMaximum) + } + + workshop, err := workshopsClient.Get(context.TODO(), name, metav1.GetOptions{}) + + source := "" + + if err == nil { + annotations := workshop.GetAnnotations() + + if val, ok := annotations[constants.EducatesWorkshopLabelAnnotationSource]; ok { + source = val + } + } + + data = append(data, []string{name, alias, capacityField, source}) + } + + return utils.PrintTable([]string{"NAME", "ALIAS", "CAPACITY", "SOURCE"}, data), nil +} + +func (m *WorkshopManager) DeleteWorkshopResource(o *DeleteWorkshopResourceConfig) error { + trainingPortalClient := m.Client.Resource(educatesTypes.TrainingPortalResource) + + trainingPortal, err := trainingPortalClient.Get(context.TODO(), o.Portal, metav1.GetOptions{}) + + if k8serrors.IsNotFound(err) { + return nil + } + + workshops, _, err := unstructured.NestedSlice(trainingPortal.Object, "spec", "workshops") + + if err != nil { + return errors.Wrap(err, "unable to retrieve workshops from training portal") + } + + var found = false + + var updatedWorkshops []interface{} + + for _, item := range workshops { + object := item.(map[string]interface{}) + + if object["name"] != o.Name || object["alias"] != o.Alias { + updatedWorkshops = append(updatedWorkshops, object) + } else { + found = true + } + } + + if !found { + return nil + } + + unstructured.SetNestedSlice(trainingPortal.Object, updatedWorkshops, "spec", "workshops") + + _, err = trainingPortalClient.Update(context.TODO(), trainingPortal, metav1.UpdateOptions{FieldManager: constants.DefaultPortalName}) + + if err != nil { + return errors.Wrapf(err, "unable to update training portal %q in cluster", o.Portal) + } + + return nil +} + +func LoadWorkshopDefinition(o *WorkshopDefinitionConfig) (*unstructured.Unstructured, error) { + // Parse the workshop location so we can determine if it is a local file + // or accessible using a HTTP/HTTPS URL. + + var urlInfo *url.URL + var err error + + if urlInfo, err = url.Parse(o.Path); err != nil { + return nil, errors.Wrap(err, "unable to parse workshop location") + } + + // Check if file system path first (not HTTP/HTTPS) and if so normalize + // the path. If it the path references a directory, then extend the path + // so we look for the workshop file within that directory. + + if urlInfo.Scheme != "http" && urlInfo.Scheme != "https" { + o.Path = filepath.Clean(o.Path) + + if o.Path, err = filepath.Abs(o.Path); err != nil { + return nil, errors.Wrap(err, "couldn't convert workshop location to absolute path") + } + + if !filepath.IsAbs(o.WorkshopFile) { + fileInfo, err := os.Stat(o.Path) + + if err != nil { + return nil, errors.Wrap(err, "couldn't test if workshop location is a directory") + } + + if fileInfo.IsDir() { + o.Path = filepath.Join(o.Path, o.WorkshopFile) + } + } else { + o.Path = o.WorkshopFile + } + } + + // Read in the workshop definition as raw data ready for parsing. + var workshopData []byte + + if urlInfo.Scheme != "http" && urlInfo.Scheme != "https" { + if workshopData, err = os.ReadFile(o.Path); err != nil { + return nil, errors.Wrap(err, "couldn't read workshop definition data file") + } + } else { + var client http.Client + + resp, err := client.Get(o.Path) + + if err != nil { + return nil, errors.Wrap(err, "couldn't download workshop definition from host") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, errors.New("failed to download workshop definition from host") + } + + workshopData, err = io.ReadAll(resp.Body) + + if err != nil { + return nil, errors.Wrap(err, "failed to read workshop definition from host") + } + } + + // Process the workshop YAML data in case it contains ytt templating. + + if workshopData, err = ProcessWorkshopDefinition(workshopData, o.DataValueFlags); err != nil { + return nil, errors.Wrap(err, "unable to process workshop definition as template") + } + + // Parse the workshop definition. + + decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder() + + workshop := &unstructured.Unstructured{} + + err = runtime.DecodeInto(decoder, workshopData, workshop) + + if err != nil { + return nil, errors.Wrap(err, "couldn't parse workshop definition") + } + + // Verify the type of resource definition. + + if workshop.GetAPIVersion() != constants.EducatesTrainingAPIGroupVersion || workshop.GetKind() != "Workshop" { + return nil, errors.New("invalid type for workshop definition") + } + + // Add annotations recording details about original workshop location. + + annotations := workshop.GetAnnotations() + + if annotations == nil { + annotations = map[string]string{} + } + + annotations[constants.EducatesWorkshopLabelAnnotationWorkshop] = workshop.GetName() + + if urlInfo.Scheme != "http" && urlInfo.Scheme != "https" { + annotations[constants.EducatesWorkshopLabelAnnotationSource] = fmt.Sprintf("file://%s", o.Path) + } else { + annotations[constants.EducatesWorkshopLabelAnnotationSource] = o.Path + } + + workshop.SetAnnotations(annotations) + + // Update the name for the workshop such that it incorporates a hash of + // the workshop location. + + if o.Name == "" { + o.Name = generateWorkshopName(o.Path, workshop, o.Portal) + } + + workshop.SetName(o.Name) + + // Insert workshop version property if not specified. + + _, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") + + if !found && o.WorkshopVersion != "latest" { + unstructured.SetNestedField(workshop.Object, o.WorkshopVersion, "spec", "version") + } + + // Remove the publish section as will not be accurate after publising. + + unstructured.RemoveNestedField(workshop.Object, "spec", "publish") + + return workshop, nil +} + +func ProcessWorkshopDefinition(yamlData []byte, dataValueFlags yttcmd.DataValuesFlags) ([]byte, error) { + templatingOptions := yttcmd.NewOptions() + + templatingOptions.IgnoreUnknownComments = true + + templatingOptions.DataValuesFlags = dataValueFlags + + var filesToProcess []*files.File + + mainInputFile := files.MustNewFileFromSource(files.NewBytesSource("workshop.yaml", yamlData)) + + filesToProcess = append(filesToProcess, mainInputFile) + + output := templatingOptions.RunWithFiles(yttcmd.Input{Files: filesToProcess}, logger.NewStdoutUI()) + + if output.Err != nil { + return []byte{}, fmt.Errorf("execution of ytt failed: %s", output.Err) + } + + if len(output.DocSet.Items) == 0 { + return []byte{}, nil + } + + var buf bytes.Buffer + + yamlmeta.NewYAMLPrinter(&buf).Print(output.DocSet.Items[0]) + + return buf.Bytes(), nil +} + + + +func generateWorkshopName(path string, workshop *unstructured.Unstructured, portal string) string { + name := workshop.GetName() + + h := sha1.New() + + io.WriteString(h, path) + + hv := fmt.Sprintf("%x", h.Sum(nil)) + + name = fmt.Sprintf("%s--%s-%s", portal, name, hv[len(hv)-7:]) + + return name +} diff --git a/client-programs/pkg/educatesrestapi/catalog.go b/client-programs/pkg/educates/restapi/catalog.go similarity index 98% rename from client-programs/pkg/educatesrestapi/catalog.go rename to client-programs/pkg/educates/restapi/catalog.go index 745b3bd85..23664cf58 100644 --- a/client-programs/pkg/educatesrestapi/catalog.go +++ b/client-programs/pkg/educates/restapi/catalog.go @@ -1,4 +1,4 @@ -package educatesrestapi +package restapi import ( "bytes" @@ -12,12 +12,12 @@ import ( "strings" "time" - "github.com/pkg/errors" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" + "github.com/pkg/errors" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" ) type AuthDetails struct { @@ -300,7 +300,7 @@ func (c *WorkshopsCatalogRequester) Login() (func(), error) { return nil, errors.Wrapf(err, "unable to create Kubernetes client") } - trainingPortalClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "trainingportals"}) + trainingPortalClient := dynamicClient.Resource(educatesTypes.TrainingPortalResource) var trainingPortal *unstructured.Unstructured var executions = 0 diff --git a/client-programs/pkg/educatesrestapi/types.go b/client-programs/pkg/educates/restapi/types.go similarity index 98% rename from client-programs/pkg/educatesrestapi/types.go rename to client-programs/pkg/educates/restapi/types.go index 1f7fb4cac..44c5c9f62 100644 --- a/client-programs/pkg/educatesrestapi/types.go +++ b/client-programs/pkg/educates/restapi/types.go @@ -1,4 +1,4 @@ -package educatesrestapi +package restapi // WorkshopCatalog // -------------------------------------------- diff --git a/client-programs/pkg/educates/types/educates.go b/client-programs/pkg/educates/types/educates.go new file mode 100644 index 000000000..88272d1fc --- /dev/null +++ b/client-programs/pkg/educates/types/educates.go @@ -0,0 +1,14 @@ +package types + +import "k8s.io/apimachinery/pkg/runtime/schema" + +var TrainingPortalResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "trainingportals"} +var WorkshopResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshops"} +var WorkshopsessionsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopsessions"} +var WorkshoprequestsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshoprequests"} +var WorkshopenvironmentsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopenvironments"} +var WorkshopallocationsResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopallocations"} +var SecretcopierResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretcopiers"} +var SecretinjectorsResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretinjectors"} +var SecretexportersResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretexporters"} +var SecretimportersResource = schema.GroupVersionResource{Group: "secrets.educates.dev", Version: "v1beta1", Resource: "secretimporters"} diff --git a/client-programs/pkg/installer/installer.go b/client-programs/pkg/installer/installer.go index 8fc6e6c0a..7ab54535a 100644 --- a/client-programs/pkg/installer/installer.go +++ b/client-programs/pkg/installer/installer.go @@ -9,13 +9,12 @@ import ( "github.com/educates/educates-training-platform/client-programs/pkg/cluster" "github.com/educates/educates-training-platform/client-programs/pkg/config" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/logger" "github.com/educates/educates-training-platform/client-programs/pkg/utils" - "github.com/cppforlife/go-cli-ui/ui" "github.com/pkg/errors" - "carvel.dev/imgpkg/pkg/imgpkg/cmd" "carvel.dev/imgpkg/pkg/imgpkg/registry" imgpkgv1 "carvel.dev/imgpkg/pkg/imgpkg/v1" @@ -32,13 +31,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const EducatesInstallerString = "educates-installer" -const EducatesInstallerAppString = "label:installer=educates-installer.app" -const educatesConfigNamespace = "educates" -const educatesConfigConfigMapName = "educates-config" -const processedValuesKey = "educates-processed-values.yaml" -const originalConfigKey = "educates-original-config.yaml" - // We use a NullWriter to suppress the output of some commands, like kbld type NullWriter int @@ -52,12 +44,10 @@ func NewInstaller() *Installer { } func (inst *Installer) DryRun(version string, packageRepository string, fullConfig *config.InstallationConfig, verbose bool, showPackagesValues bool, skipImageResolution bool) error { - if verbose { - fmt.Println("Installing educates (DryRun) ...") - } + fmt.Println("⚙️ Running dry run installation...") // Create a temporary directory - tempDir, err := os.MkdirTemp("", EducatesInstallerString) + tempDir, err := os.MkdirTemp("", constants.EducatesInstallerString) if err != nil { return err } @@ -106,12 +96,10 @@ func (inst *Installer) DryRun(version string, packageRepository string, fullConf } func (inst *Installer) Run(version string, packageRepository string, fullConfig *config.InstallationConfig, clusterConfig *cluster.ClusterConfig, verbose bool, showPackagesValues bool, skipImageResolution bool, showDiff bool) error { - if verbose { - fmt.Println("Installing educates ...") - } + fmt.Println("⚙️ Running full installation...") // Create a temporary directory - tempDir, err := os.MkdirTemp("", EducatesInstallerString) + tempDir, err := os.MkdirTemp("", constants.EducatesInstallerString) if err != nil { return err } @@ -179,15 +167,15 @@ func (inst *Installer) GetValuesFromCluster(kubeconfig string, kubeContext strin return "", errors.Wrapf(err, "unable to create Kubernetes client") } - configMapClient := client.CoreV1().ConfigMaps(educatesConfigNamespace) + configMapClient := client.CoreV1().ConfigMaps(constants.EducatesConfigNamespace) - values, err := configMapClient.Get(context.TODO(), educatesConfigConfigMapName, metav1.GetOptions{}) + values, err := configMapClient.Get(context.TODO(), constants.EducatesConfigConfigMapName, metav1.GetOptions{}) if err != nil { return "", errors.Wrap(err, "error querying the cluster") } - valuesData, ok := values.Data[processedValuesKey] + valuesData, ok := values.Data[constants.EducatesProcessedValuesKey] if !ok { return "", errors.New("no platform configuration found") @@ -205,15 +193,15 @@ func (inst *Installer) GetConfigFromCluster(kubeconfig string, kubeContext strin return "", errors.Wrapf(err, "unable to create Kubernetes client") } - configMapClient := client.CoreV1().ConfigMaps(educatesConfigNamespace) + configMapClient := client.CoreV1().ConfigMaps(constants.EducatesConfigNamespace) - values, err := configMapClient.Get(context.TODO(), educatesConfigConfigMapName, metav1.GetOptions{}) + values, err := configMapClient.Get(context.TODO(), constants.EducatesConfigConfigMapName, metav1.GetOptions{}) if err != nil { return "", errors.Wrap(err, "error querying the cluster") } - valuesData, ok := values.Data[originalConfigKey] + valuesData, ok := values.Data[constants.EducatesOriginalConfigKey] if !ok { return "", errors.New("no platform configuration found") @@ -234,7 +222,9 @@ func (inst *Installer) fetch(tempDir string, version string, packageRepository s } // TODO: Remove some logging from here fetchOutputDir := filepath.Join(tempDir, "fetch") - _, err := imgpkgv1.Pull(inst.getBundleImageRef(version, packageRepository, verbose), fetchOutputDir, pullOpts, registry.Opts{}) + installerImageRef := inst.getBundleImageRef(version, packageRepository) + fmt.Println("Using installer image: ", installerImageRef) + _, err := imgpkgv1.Pull(installerImageRef, fetchOutputDir, pullOpts, registry.Opts{}) if err != nil { // TODO: There might be more potential issues here return "", errors.Wrapf(err, "Installer image not found") @@ -331,17 +321,9 @@ func (inst *Installer) resolve(tempDir string, inputDir string, verbose bool) (s return "", err } - // ui - confUI := ui.NewConfUI(ui.NewNoopLogger()) - uiFlags := cmd.UIFlags{ - Color: true, - JSON: false, - NonInteractive: true, - } - uiFlags.ConfigureUI(confUI) - defer confUI.Flush() + carvelUI := logger.NewCarvelUI() - resolveOptions := kbldcmd.NewResolveOptions(confUI) + resolveOptions := kbldcmd.NewResolveOptions(carvelUI) resolveOptions.FileFlags.Files = []string{inputDir} // Apply defaults from CLI resolveOptions.ImagesAnnotation = false @@ -376,19 +358,12 @@ func (inst *Installer) deploy(tempDir string, inputDir string, clusterConfig *cl fmt.Println("Running deploy ...") } - confUI := ui.NewConfUI(ui.NewNoopLogger()) - uiFlags := cmd.UIFlags{ - Color: true, - JSON: false, - NonInteractive: true, - } - uiFlags.ConfigureUI(confUI) - defer confUI.Flush() + carvelUI := logger.NewCarvelUI() depsFactory := NewKappDepsFactoryImpl(clusterConfig) - deployOptions := app.NewDeployOptions(confUI, depsFactory, logger.NewKappLogger(), nil) - deployOptions.AppFlags.Name = EducatesInstallerAppString - deployOptions.AppFlags.AppNamespace = EducatesInstallerString + deployOptions := app.NewDeployOptions(carvelUI, depsFactory, logger.NewKappLogger(), nil) + deployOptions.AppFlags.Name = constants.EducatesInstallerAppString + deployOptions.AppFlags.AppNamespace = constants.EducatesInstallerString deployOptions.FileFlags.Files = []string{inputDir, filepath.Join(tempDir, "fetch/config/kapp/")} deployOptions.ApplyFlags.ClusterChangeOpts.Wait = true deployOptions.ApplyFlags.ClusterChangeOpts.ApplyIgnored = false @@ -419,22 +394,12 @@ func (inst *Installer) deploy(tempDir string, inputDir string, clusterConfig *cl func (inst *Installer) delete(clusterConfig *cluster.ClusterConfig) error { fmt.Println("Running delete ...") - confUI := ui.NewConfUI(ui.NewNoopLogger()) - - uiFlags := cmd.UIFlags{ - Color: true, - JSON: false, - NonInteractive: true, - } - - uiFlags.ConfigureUI(confUI) - - defer confUI.Flush() + carvelUI := logger.NewCarvelUI() depsFactory := NewKappDepsFactoryImpl(clusterConfig) - deleteOptions := app.NewDeleteOptions(confUI, depsFactory, logger.NewKappLogger()) - deleteOptions.AppFlags.Name = EducatesInstallerAppString - deleteOptions.AppFlags.AppNamespace = EducatesInstallerString + deleteOptions := app.NewDeleteOptions(carvelUI, depsFactory, logger.NewKappLogger()) + deleteOptions.AppFlags.Name = constants.EducatesInstallerAppString + deleteOptions.AppFlags.AppNamespace = constants.EducatesInstallerString deleteOptions.ApplyFlags.ClusterChangeOpts.Wait = true deleteOptions.ApplyFlags.ApplyingChangesOpts.Concurrency = 5 deleteOptions.ApplyFlags.WaitingChangesOpts.CheckInterval = time.Duration(1) * time.Second @@ -448,10 +413,7 @@ func (inst *Installer) delete(clusterConfig *cluster.ClusterConfig) error { return nil } -func (inst *Installer) getBundleImageRef(version string, packageRepository string, verbose bool) string { - bundleImageRef := fmt.Sprintf("%s/%s:%s", packageRepository, EducatesInstallerString, version) - if verbose { - fmt.Printf("Using installer image: %s\n", bundleImageRef) - } +func (inst *Installer) getBundleImageRef(version string, packageRepository string) string { + bundleImageRef := fmt.Sprintf("%s/%s:%s", packageRepository, constants.EducatesInstallerString, version) return bundleImageRef } diff --git a/client-programs/pkg/logger/CarvelUI.go b/client-programs/pkg/logger/CarvelUI.go new file mode 100644 index 000000000..45cfa6e69 --- /dev/null +++ b/client-programs/pkg/logger/CarvelUI.go @@ -0,0 +1,101 @@ +package logger + +import ( + "fmt" + "io" + "os" + + "github.com/cppforlife/go-cli-ui/ui" + "github.com/cppforlife/go-cli-ui/ui/table" +) + +// CarvelUI is a minimal implementation of ui.UI for use with Carvel tools +// (imgpkg, vendir, kapp, kbld). It provides simple stdout/stderr output +// without the complexity of ConfUI. It can not be used as replacement for +// ytt UI. +type CarvelUI struct { + stdout io.Writer + stderr io.Writer +} + +// Ensure CarvelUI implements ui.UI interface at compile time +var _ ui.UI = &CarvelUI{} + +// NewCarvelUI creates a new CarvelUI with stdout/stderr writers. +func NewCarvelUI() *CarvelUI { + return &CarvelUI{ + stdout: os.Stdout, + stderr: os.Stderr, + } +} + +// NewCarvelUIWithWriters creates a CarvelUI with custom writers. +func NewCarvelUIWithWriters(stdout, stderr io.Writer) *CarvelUI { + return &CarvelUI{ + stdout: stdout, + stderr: stderr, + } +} + +// ErrorLinef prints formatted error line to stderr. +func (u *CarvelUI) ErrorLinef(pattern string, args ...interface{}) { + fmt.Fprintf(u.stderr, pattern+"\n", args...) +} + +// PrintLinef prints formatted line to stdout. +func (u *CarvelUI) PrintLinef(pattern string, args ...interface{}) { + fmt.Fprintf(u.stdout, pattern+"\n", args...) +} + +// BeginLinef starts a line (no newline). +func (u *CarvelUI) BeginLinef(pattern string, args ...interface{}) { + fmt.Fprintf(u.stdout, pattern, args...) +} + +// EndLinef ends a line with newline. +func (u *CarvelUI) EndLinef(pattern string, args ...interface{}) { + fmt.Fprintf(u.stdout, pattern+"\n", args...) +} + +// PrintBlock prints a block of bytes to stdout. +func (u *CarvelUI) PrintBlock(block []byte) { + fmt.Fprint(u.stdout, string(block)) +} + +// PrintErrorBlock prints error block to stderr. +func (u *CarvelUI) PrintErrorBlock(block string) { + fmt.Fprint(u.stderr, block) +} + +// PrintTable prints a table using the table's own Print method. +func (u *CarvelUI) PrintTable(t table.Table) { + t.Print(u.stdout) +} + +// AskForText - non-interactive mode, returns default value. +func (u *CarvelUI) AskForText(opts ui.TextOpts) (string, error) { + return opts.Default, nil +} + +// AskForChoice - non-interactive mode, returns default choice. +func (u *CarvelUI) AskForChoice(opts ui.ChoiceOpts) (int, error) { + return opts.Default, nil +} + +// AskForPassword - non-interactive mode, returns empty string. +func (u *CarvelUI) AskForPassword(label string) (string, error) { + return "", nil +} + +// AskForConfirmation - non-interactive mode, returns nil (auto-confirmed). +func (u *CarvelUI) AskForConfirmation() error { + return nil +} + +// IsInteractive returns false as this is a non-interactive implementation. +func (u *CarvelUI) IsInteractive() bool { + return false +} + +// Flush is a no-op for this simple implementation. +func (u *CarvelUI) Flush() {} diff --git a/client-programs/pkg/logger/StdoutUI.go b/client-programs/pkg/logger/StdoutUI.go new file mode 100644 index 000000000..93ebd82b5 --- /dev/null +++ b/client-programs/pkg/logger/StdoutUI.go @@ -0,0 +1,58 @@ +package logger + +import ( + "fmt" + "io" + "os" +) + +// StdoutUI is a local implementation that replaces the ui.UI interface from +// carvel.dev/ytt/pkg/cmd/ui. This allows us to avoid importing that package +// while still being compatible with ytt's RunWithFiles method. +// +// Go's structural typing for interfaces means this type satisfies the ui.UI +// interface as long as it implements the same method signatures: +// - Printf(string, ...interface{}) +// - Debugf(string, ...interface{}) +// - Warnf(str string, args ...interface{}) +// - DebugWriter() io.Writer +type StdoutUI struct { + stdout io.Writer + stderr io.Writer +} + +// NewStdoutUI creates a new StdoutUI with default stdout and stderr writers. +func NewStdoutUI() StdoutUI { + return StdoutUI{os.Stdout, os.Stderr} +} + +// Printf writes formatted output to stdout. +func (y StdoutUI) Printf(str string, args ...interface{}) { + fmt.Fprintf(y.stdout, str, args...) +} + +// Warnf writes formatted warning output to stderr. +func (y StdoutUI) Warnf(str string, args ...interface{}) { + fmt.Fprintf(y.stderr, str, args...) +} + +// Debugf writes formatted debug output to stderr if debug mode is enabled. +func (y StdoutUI) Debugf(str string, args ...interface{}) { + // Do nothing +} + +// DebugWriter returns an io.Writer for debug output. +// Returns stderr if debug mode is enabled, otherwise returns a no-op writer. +func (y StdoutUI) DebugWriter() io.Writer { + // Do nothing + return noopWriter{} +} + +// noopWriter is a writer that discards all data. +type noopWriter struct{} + +var _ io.Writer = noopWriter{} + +func (w noopWriter) Write(data []byte) (int, error) { + return len(data), nil +} diff --git a/client-programs/pkg/lookup/lookup.go b/client-programs/pkg/lookup/lookup.go new file mode 100644 index 000000000..54236a8ce --- /dev/null +++ b/client-programs/pkg/lookup/lookup.go @@ -0,0 +1,93 @@ +package lookup + +import ( + "context" + "encoding/base64" + "fmt" + + "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + + +type LookupConfig struct { + Kubeconfig string + Context string +} + +type LookupService struct { +} + +func NewLookupService() *LookupService { + return &LookupService{} +} + +func (o *LookupService) RemoteAccessKubeconfig(config *LookupConfig) (string, error) { + var err error + + clusterConfig, err := cluster.NewClusterConfigIfAvailable(config.Kubeconfig, config.Context) + if err != nil { + return "", err + } + + client, err := clusterConfig.GetClient() + + if err != nil { + return "", err + } + + // We need to fetch the secret called "remote-access-token" from the + // "educates" namespace. This contains a Kubernetes access token secret + // giving access to just the Educates custom resources. + + secretsClient := client.CoreV1().Secrets("educates") + + secret, err := secretsClient.Get(context.TODO(), "remote-access-token", metav1.GetOptions{}) + + if err != nil { + return "", errors.Wrapf(err, "unable to fetch remote-access secret") + } + + // Within the secret are data fields for "ca.crt" and "token". We need to + // extract these and use them to create a kubeconfig file. Note that there + // is no "server" property in the secret, so when constructing the kubeconfig + // we need to use the server from the same cluster as we are requesting the + // secret from. + + caCrt := secret.Data["ca.crt"] + token := secret.Data["token"] + + // Get the server from the client for Kubernetes cluster access. + + serverScheme := client.CoreV1().RESTClient().Get().URL().Scheme + serverHost := client.CoreV1().RESTClient().Get().URL().Host + + serverUrl := fmt.Sprintf("%s://%s", serverScheme, serverHost) + + // Construct the kubeconfig file. We need to base64 encode the ca.crt file + // as it is a binary file. + + kubeconfig := fmt.Sprintf(`apiVersion: v1 +kind: Config +clusters: +- name: training-platform + cluster: + server: %s + certificate-authority-data: %s +contexts: +- name: training-platform + context: + cluster: training-platform + user: remote-access +current-context: training-platform +users: +- name: remote-access + user: + token: %s +`, serverUrl, base64.StdEncoding.EncodeToString(caCrt), token) + + // Write out the kubeconfig to the output path if provided, otherwise + // print it to stdout. + return kubeconfig, nil +} diff --git a/client-programs/pkg/registry/base.go b/client-programs/pkg/registry/base.go new file mode 100644 index 000000000..7350bb677 --- /dev/null +++ b/client-programs/pkg/registry/base.go @@ -0,0 +1,369 @@ +package registry + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "fmt" + "io" + "os" + "path" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/educates/educates-training-platform/client-programs/pkg/config" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/pkg/errors" +) + +// baseContainer contains common configuration and methods for registry and mirror containers. +type baseContainer struct { + containerName string + bindIP string + labels map[string]string + envVars []string + hostPort string +} + +// ensureNetwork creates the specified docker network if it doesn't exist. +func (b *baseContainer) ensureNetwork(cli *client.Client, networkName string) error { + ctx := context.Background() + + _, err := cli.NetworkInspect(ctx, networkName, network.InspectOptions{}) + if err != nil { + _, err = cli.NetworkCreate(ctx, networkName, network.CreateOptions{}) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("cannot create %s network", networkName)) + } + } + return nil +} + +// containerExists checks if the container already exists. +func (b *baseContainer) containerExists(cli *client.Client) (bool, error) { + ctx := context.Background() + _, err := cli.ContainerInspect(ctx, b.containerName) + if err == nil { + return true, nil + } + return false, nil +} + +// pullRegistryImage pulls the registry image. +func (b *baseContainer) pullRegistryImage(cli *client.Client) error { + ctx := context.Background() + + reader, err := cli.ImagePull(ctx, constants.RegistryImageV3, image.PullOptions{}) + if err != nil { + return errors.Wrap(err, "cannot pull registry image") + } + + defer reader.Close() + io.Copy(os.Stdout, reader) + return nil +} + +// createAndStartContainer creates and starts the container with the given configuration. +func (b *baseContainer) createAndStartContainer(cli *client.Client) (string, error) { + ctx := context.Background() + + hostConfig := &container.HostConfig{ + PortBindings: nat.PortMap{ + "5000/tcp": []nat.PortBinding{ + { + HostIP: b.bindIP, + HostPort: b.hostPort, + }, + }, + }, + RestartPolicy: container.RestartPolicy{ + Name: "always", + }, + } + + containerConfig := &container.Config{ + Image: constants.RegistryImageV3, + Tty: false, + ExposedPorts: nat.PortSet{ + "5000/tcp": struct{}{}, + }, + Labels: b.labels, + Env: b.envVars, + } + + resp, err := cli.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, b.containerName) + if err != nil { + return "", errors.Wrap(err, "cannot create container") + } + + if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + return "", errors.Wrap(err, "unable to start container") + } + + return resp.ID, nil +} + +// connectToNetwork connects the container to the specified network. +func (b *baseContainer) connectToNetwork(cli *client.Client, networkName string) error { + ctx := context.Background() + + cli.NetworkDisconnect(ctx, networkName, b.containerName, false) + + err := cli.NetworkConnect(ctx, networkName, b.containerName, &network.EndpointSettings{}) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to connect container to %s network", networkName)) + } + + return nil +} + +// linkToNetwork connects the container to the specified network. +func (b *baseContainer) linkToNetwork(cli *client.Client, networkName string) error { + ctx := context.Background() + + fmt.Println("Linking local image registry to cluster") + + cli.NetworkDisconnect(ctx, networkName, b.containerName, false) + + err := cli.NetworkConnect(ctx, networkName, b.containerName, &network.EndpointSettings{}) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to connect container to %s network", networkName)) + } + + return nil +} + +// stopAndRemoveContainer stops and removes the container. +func (b *baseContainer) stopAndRemoveContainer(cli *client.Client) error { + ctx := context.Background() + + exists, _ := b.containerExists(cli) + if !exists { + return nil + } + + timeout := 30 + err := cli.ContainerStop(ctx, b.containerName, container.StopOptions{ + Timeout: &timeout, + }) + if err != nil { + return errors.Wrap(err, "unable to stop container") + } + + err = cli.ContainerRemove(ctx, b.containerName, container.RemoveOptions{}) + if err != nil { + return errors.Wrap(err, "unable to delete container") + } + + return nil +} + +// getEducatesKindNodeContainers returns all kind node containers for the educates cluster +func getEducatesKindNodeContainers(cli *client.Client) ([]string, error) { + ctx := context.Background() + + // Kind labels all node containers with io.x-k8s.kind.cluster= + nodeFilters := filters.NewArgs() + nodeFilters.Add("label", fmt.Sprintf("io.x-k8s.kind.cluster=%s", constants.EducatesClusterName)) + + containers, err := cli.ContainerList(ctx, container.ListOptions{ + All: true, + Filters: nodeFilters, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to list kind node containers") + } + + if len(containers) == 0 { + return nil, errors.New("no kind node containers found for educates cluster") + } + + containerIDs := make([]string, len(containers)) + for i, c := range containers { + containerIDs[i] = c.ID + } + + return containerIDs, nil +} + +// addRegistryConfigToNode adds the registry config to a single kind node container +func addRegistryConfigToNode(cli *client.Client, containerID, repositoryName, content string) error { + ctx := context.Background() + + registryDir := "/etc/containerd/certs.d/" + repositoryName + + cmdStatement := []string{"mkdir", "-p", registryDir} + + optionsCreateExecuteScript := container.ExecOptions{ + AttachStdout: true, + AttachStderr: true, + Cmd: cmdStatement, + } + + response, err := cli.ContainerExecCreate(ctx, containerID, optionsCreateExecuteScript) + if err != nil { + return errors.Wrap(err, "unable to create exec command") + } + hijackedResponse, err := cli.ContainerExecAttach(ctx, response.ID, container.ExecAttachOptions{}) + if err != nil { + return errors.Wrap(err, "unable to attach exec command") + } + + hijackedResponse.Close() + + buffer, err := tarFile([]byte(content), path.Join("/etc/containerd/certs.d/"+repositoryName, "hosts.toml"), 0x644) + if err != nil { + return err + } + err = cli.CopyToContainer(context.Background(), + containerID, "/", + buffer, + container.CopyToContainerOptions{ + AllowOverwriteDirWithFile: true, + }) + if err != nil { + return errors.Wrap(err, "unable to copy file to container") + } + + return nil +} + +// addRegistryConfigToKindNodes adds the registry config to all kind nodes. +// It is used when creating a new local registry or registry mirror. +func addRegistryConfigToKindNodes(repositoryName string, content string) error { + fmt.Printf("Adding local image registry config (%s) to Kind nodes\n", repositoryName) + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return errors.Wrap(err, "unable to create docker client") + } + + containerIDs, err := getEducatesKindNodeContainers(cli) + if err != nil { + return err + } + + // Apply config to all nodes (control-plane and workers) + for _, containerID := range containerIDs { + if err := addRegistryConfigToNode(cli, containerID, repositoryName, content); err != nil { + return errors.Wrapf(err, "failed to add registry config to node %s", containerID) + } + } + + return nil +} + +// removeRegistryConfigFromNode removes the registry config from a single kind node container +func removeRegistryConfigFromNode(cli *client.Client, containerID, repositoryName string) error { + ctx := context.Background() + + registryDir := "/etc/containerd/certs.d/" + repositoryName + + cmdStatement := []string{"rm", "-rf", registryDir} + + optionsCreateExecuteScript := container.ExecOptions{ + AttachStdout: true, + AttachStderr: true, + Cmd: cmdStatement, + } + + response, err := cli.ContainerExecCreate(ctx, containerID, optionsCreateExecuteScript) + if err != nil { + return errors.Wrap(err, "unable to create exec command") + } + + hijackedResponse, err := cli.ContainerExecAttach(ctx, response.ID, container.ExecAttachOptions{}) + if err != nil { + return errors.Wrap(err, "unable to attach exec command") + } + + hijackedResponse.Close() + + return nil +} + +// removeRegistryConfigFromKindNodes removes the registry config from all kind nodes. +// It is used when deleting a local registry mirror. +func removeRegistryConfigFromKindNodes(repositoryName string) error { + fmt.Printf("Removing local image registry config (%s) from Kind nodes\n", repositoryName) + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return errors.Wrap(err, "unable to create docker client") + } + + containerIDs, err := getEducatesKindNodeContainers(cli) + if err != nil { + // If nodes don't exist, nothing to remove + return nil + } + + // Remove config from all nodes (control-plane and workers) + for _, containerID := range containerIDs { + if err := removeRegistryConfigFromNode(cli, containerID, repositoryName); err != nil { + return errors.Wrapf(err, "failed to remove registry config from node %s", containerID) + } + } + + return nil +} + +// tarFile creates a tar archive with a single file. +func tarFile(fileContent []byte, basePath string, fileMode int64) (*bytes.Buffer, error) { + buffer := &bytes.Buffer{} + + zr := gzip.NewWriter(buffer) + tw := tar.NewWriter(zr) + + hdr := &tar.Header{ + Name: basePath, + Mode: fileMode, + Size: int64(len(fileContent)), + } + if err := tw.WriteHeader(hdr); err != nil { + return buffer, err + } + if _, err := tw.Write(fileContent); err != nil { + return buffer, err + } + + // produce tar + if err := tw.Close(); err != nil { + return buffer, fmt.Errorf("error closing tar file: %w", err) + } + // produce gzip + if err := zr.Close(); err != nil { + return buffer, fmt.Errorf("error closing gzip file: %w", err) + } + + return buffer, nil +} + +func getRegistryMirrorLabelFilters() filters.Args { + return filters.NewArgs( + filters.Arg("label", constants.EducatesContainersRoleLabelKey+"="+constants.EducatesContainersMirrorRoleLabel), + filters.Arg("label", constants.EducatesContainersAppLabelKey+"="+constants.EducatesContainersAppLabel), + ) +} + +func newRegistryContainerLabels() map[string]string { + return map[string]string{ + constants.EducatesContainersRoleLabelKey: constants.EducatesContainersRegistryRoleLabel, + constants.EducatesContainersAppLabelKey: constants.EducatesContainersAppLabel, + } +} + +func newMirrorContainerLabels(mirrorConfig *config.RegistryMirrorConfig) map[string]string { + return map[string]string{ + constants.EducatesContainersRoleLabelKey: constants.EducatesContainersMirrorRoleLabel, + constants.EducatesContainersAppLabelKey: constants.EducatesContainersAppLabel, + constants.EducatesContainersMirrorLabelKey: mirrorConfig.Mirror, + constants.EducatesContainersURLLabelKey: mirrorConfig.URL, + constants.EducatesContainersUsernameLabelKey: mirrorConfig.Username, + } +} diff --git a/client-programs/pkg/registry/interface.go b/client-programs/pkg/registry/interface.go new file mode 100644 index 000000000..695992d97 --- /dev/null +++ b/client-programs/pkg/registry/interface.go @@ -0,0 +1,14 @@ +package registry + +// ContainerManager defines the interface for managing registry and mirror containers. +// Both Registry and Mirror types implement this interface. +type ContainerManager interface { + // DeployAndLinkToCluster creates the container and configures the cluster to use it + DeployAndLinkToCluster() error + + // DeleteAndUnlinkFromCluster removes the container and cleans up cluster configuration + DeleteAndUnlinkFromCluster() error + + // Delete removes the container only without cluster configuration cleanup + Delete() error +} diff --git a/client-programs/pkg/registry/mirror.go b/client-programs/pkg/registry/mirror.go new file mode 100644 index 000000000..59cc035c2 --- /dev/null +++ b/client-programs/pkg/registry/mirror.go @@ -0,0 +1,219 @@ +package registry + +import ( + "context" + _ "embed" + "fmt" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/educates/educates-training-platform/client-programs/pkg/config" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" +) + +const hostMirrorTomlTemplate = `[host."http://%s:5000"] + capabilities = ["pull", "resolve"] +` + +// Mirror represents a registry mirror container. +type Mirror struct { + baseContainer + config *config.RegistryMirrorConfig +} + +// NewMirror creates a new Mirror instance. +func NewMirror(mirrorConfig *config.RegistryMirrorConfig) *Mirror { + return &Mirror{ + baseContainer: baseContainer{ + containerName: fmt.Sprintf("%s-mirror-%s", constants.EducatesRegistryContainer, mirrorConfig.Mirror), + bindIP: "127.0.0.1", + hostPort: "", // dynamic port + labels: newMirrorContainerLabels(mirrorConfig), + envVars: buildMirrorEnvVars(mirrorConfig), + }, + config: mirrorConfig, + } +} + +// buildMirrorEnvVars creates the environment variables for a mirror container. +func buildMirrorEnvVars(mirrorConfig *config.RegistryMirrorConfig) []string { + envs := []string{} + mirrorURL := mirrorConfig.URL + if mirrorURL == "" { + mirrorURL = mirrorConfig.Mirror + } + envs = append(envs, fmt.Sprintf("REGISTRY_PROXY_REMOTEURL=https://%s", mirrorURL)) + if mirrorConfig.Username != "" { + envs = append(envs, fmt.Sprintf("REGISTRY_PROXY_USERNAME=%s", mirrorConfig.Username)) + } + if mirrorConfig.Password != "" { + envs = append(envs, fmt.Sprintf("REGISTRY_PROXY_PASSWORD=%s", mirrorConfig.Password)) + } + return envs +} + +// DeployAndLinkToCluster deploys a registry mirror and links it to the cluster. +func (m *Mirror) DeployAndLinkToCluster() error { + err := m.Deploy() + if err != nil { + return errors.Wrap(err, "failed to deploy registry mirror "+m.config.Mirror) + } + + content := fmt.Sprintf(hostMirrorTomlTemplate, m.containerName) + err = addRegistryConfigToKindNodes(m.config.Mirror, content) + if err != nil { + fmt.Println("Warning: Mirror not added to Kind nodes") + } + + return nil +} + +// Deploy creates the mirror container. +func (m *Mirror) Deploy() error { + fmt.Printf("Deploying local image registry mirror %s\n", m.config.Mirror) + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return errors.Wrap(err, "unable to create docker client") + } + + exists, _ := m.containerExists(cli) + if exists { + // If we can retrieve a container of required name we assume it is + // running okay. Technically it could be restarting, stopping or + // have exited and container was not removed, but if that is the case + // then leave it up to the user to sort out. + fmt.Printf("Registry mirror %s already exists\n", m.config.Mirror) + return nil + } + + if err = m.ensureNetwork(cli, constants.EducatesNetworkName); err != nil { + return err + } + + if _, err = m.createAndStartContainer(cli); err != nil { + return errors.Wrap(err, "cannot create local registry mirror container") + } + + if err = m.connectToNetwork(cli, constants.EducatesNetworkName); err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to connect local registry mirror to %s network", constants.EducatesNetworkName)) + } + + if err = m.linkToNetwork(cli, constants.ClusterNetworkName); err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to link local registry mirror to %s network", constants.ClusterNetworkName)) + } + + return nil +} + +// DeleteAndUnlinkFromCluster deletes a local registry mirror and unlinks it from the cluster. +func (m *Mirror) DeleteAndUnlinkFromCluster() error { + fmt.Printf("Deleting local image registry mirror %s\n", m.config.Mirror) + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return errors.Wrap(err, "unable to create docker client") + } + + exists, _ := m.containerExists(cli) + if !exists { + fmt.Printf("Registry mirror %s does not exist\n", m.config.Mirror) + return nil + } + + err = m.stopAndRemoveContainer(cli) + if err != nil { + return err + } + + // Remove the registry config from the kind nodes + err = removeRegistryConfigFromKindNodes(m.config.Mirror) + if err != nil { + return errors.Wrap(err, "unable to remove registry config from kind nodes") + } + + return nil +} + +// Delete removes the mirror container. +func (m *Mirror) Delete() error { + fmt.Printf("Deleting local image registry mirror %s\n", m.config.Mirror) + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return errors.Wrap(err, "unable to create docker client") + } + + return m.stopAndRemoveContainer(cli) +} + +// DeleteRegistryMirrors deletes all local image registry mirrors. +func DeleteRegistryMirrors() error { + ctx := context.Background() + + fmt.Println("Deleting local image registry mirrors") + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return errors.Wrap(err, "unable to create docker client") + } + + mirrors, err := cli.ContainerList(ctx, container.ListOptions{ + Filters: getRegistryMirrorLabelFilters(), + }) + if err != nil { + return errors.Wrap(err, "unable to list registry mirrors") + } + + for _, mirror := range mirrors { + timeout := 30 + + err = cli.ContainerStop(ctx, mirror.ID, container.StopOptions{Timeout: &timeout}) + if err != nil { + return errors.Wrap(err, "unable to stop registry mirror container "+mirror.ID) + } + + err = cli.ContainerRemove(ctx, mirror.ID, container.RemoveOptions{}) + if err != nil { + return errors.Wrap(err, "unable to delete registry mirror container "+mirror.ID) + } + } + + return nil +} + +// ListRegistryMirrors lists all local image registry mirrors. +func ListRegistryMirrors() (string, error) { + ctx := context.Background() + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return "", errors.Wrap(err, "unable to create docker client") + } + + mirrors, err := cli.ContainerList(ctx, container.ListOptions{ + Filters: getRegistryMirrorLabelFilters(), + }) + if err != nil { + return "", errors.Wrap(err, "unable to list registry mirrors") + } + + var data [][]string + for _, item := range mirrors { + name := item.Labels["mirror"] + url := item.Labels["url"] + if url == "" { + url = item.Labels["mirror"] + } + username := item.Labels["username"] + status := item.Status + containerName := utils.GetContainerName(item) + data = append(data, []string{name, url, username, status, containerName}) + } + return utils.PrintTable([]string{"NAME", "URL", "USERNAME", "STATUS", "CONTAINER_NAME"}, data), nil +} + +// Compile-time check that Mirror implements ContainerManager +var _ ContainerManager = (*Mirror)(nil) diff --git a/client-programs/pkg/registry/registry.go b/client-programs/pkg/registry/registry.go index 8ed27cf7b..bafdbc602 100644 --- a/client-programs/pkg/registry/registry.go +++ b/client-programs/pkg/registry/registry.go @@ -1,24 +1,14 @@ package registry import ( - "archive/tar" - "bytes" - "compress/gzip" "context" _ "embed" "fmt" - "io" - "os" - "path" - "strings" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/network" - "github.com/docker/go-connections/nat" - "github.com/educates/educates-training-platform/client-programs/pkg/config" - "github.com/educates/educates-training-platform/client-programs/pkg/docker" + "github.com/docker/docker/client" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" v1 "k8s.io/api/core/v1" @@ -29,390 +19,117 @@ import ( "k8s.io/client-go/kubernetes" ) -const hostMirrorTomlTemplate = `[host."http://%s:5000"] - capabilities = ["pull", "resolve"] -` - const hostRegistryTomlTemplate = `[host."http://%s:5000"]` -const ( - RegistryImageV3 = "docker.io/library/registry:3" - RegistryConfigTargetPath = "/etc/distribution/config.yml" - EducatesNetworkName = "educates" - EducatesRegistryContainer = "educates-registry" - EducatesControlPlaneContainer = "educates-control-plane" - EducatesRegistryRoleLabel = "registry" - EducatesMirrorRoleLabel = "mirror" - EducatesAppLabel = "educates" -) +// Registry represents the educates image registry container. +type Registry struct { + baseContainer + k8sClient *kubernetes.Clientset +} -/** - * This function is used to deploy the registry and link it to the cluster. - * It is used when creating a new local cluster. - */ -func DeployRegistryAndLinkToCluster(bindIP string, client *kubernetes.Clientset) error { +// NewRegistry creates a new Registry instance. +func NewRegistry(bindIP string, k8sClient *kubernetes.Clientset) *Registry { + return &Registry{ + baseContainer: baseContainer{ + containerName: constants.EducatesRegistryContainer, + bindIP: bindIP, + hostPort: "5001", + labels: newRegistryContainerLabels(), + }, + k8sClient: k8sClient, + } +} - err := createRegistryContainer(bindIP) +// DeployAndLinkToCluster deploys the registry and links it to the cluster. +// It is used when creating a new local cluster. +func (r *Registry) DeployAndLinkToCluster() error { + err := r.Deploy() if err != nil { return errors.Wrap(err, "failed to deploy registry") } // This is needed to make containerd use the local registry - - if err = addRegistryConfigToKindNodes("localhost:5001", fmt.Sprintf(hostRegistryTomlTemplate, EducatesRegistryContainer)); err != nil { + if err = addRegistryConfigToKindNodes("localhost:5001", fmt.Sprintf(hostRegistryTomlTemplate, constants.EducatesRegistryContainer)); err != nil { return errors.Wrap(err, "failed to add registry config to kind nodes") } - if err = addRegistryConfigToKindNodes("registry.default.svc.cluster.local", fmt.Sprintf(hostRegistryTomlTemplate, EducatesRegistryContainer)); err != nil { + if err = addRegistryConfigToKindNodes("registry.default.svc.cluster.local", fmt.Sprintf(hostRegistryTomlTemplate, constants.EducatesRegistryContainer)); err != nil { return errors.Wrap(err, "failed to add registry config to kind nodes") } // This is needed so that kubernetes nodes can pull images from the local registry - if err = documentLocalRegistry(client); err != nil { + if err = r.documentLocalRegistry(); err != nil { return errors.Wrap(err, "failed to document registry config in cluster") } return nil } -/** - * This function is used to deploy a registry. - * It is used when creating a new local registry. - * It will not link the registry to the cluster. - */ -func DeployRegistry(bindIP string) error { - err := createRegistryContainer(bindIP) - if err != nil { - return errors.Wrap(err, "failed to deploy registry") - } - - return nil -} - -/** - * This private function only creates the registry container. - */ -func createRegistryContainer(bindIP string) error { - ctx := context.Background() - +// Deploy creates the registry container without linking to cluster. +// It is used when creating a new local registry standalone. +func (r *Registry) Deploy() error { fmt.Println("Deploying local image registry") - cli, err := docker.NewDockerClient() - + cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return errors.Wrap(err, "unable to create docker client") } - _, err = cli.ContainerInspect(ctx, EducatesRegistryContainer) - - if err == nil { + exists, _ := r.containerExists(cli) + if exists { // If we can retrieve a container of required name we assume it is // running okay. Technically it could be restarting, stopping or // have exited and container was not removed, but if that is the case // then leave it up to the user to sort out. - return nil } - reader, err := cli.ImagePull(ctx, RegistryImageV3, image.PullOptions{}) - if err != nil { - return errors.Wrap(err, "cannot pull registry image") - } - - defer reader.Close() - io.Copy(os.Stdout, reader) - - _, err = cli.NetworkInspect(ctx, EducatesNetworkName, network.InspectOptions{}) - - if err != nil { - _, err = cli.NetworkCreate(ctx, EducatesNetworkName, network.CreateOptions{}) - - if err != nil { - return errors.Wrap(err, "cannot create educates network") - } - } - - hostConfig := &container.HostConfig{ - PortBindings: nat.PortMap{ - "5000/tcp": []nat.PortBinding{ - { - HostIP: bindIP, - HostPort: "5001", - }, - }, - }, - RestartPolicy: container.RestartPolicy{ - Name: "always", - }, + if err = r.pullRegistryImage(cli); err != nil { + return err } - labels := map[string]string{ - "app": EducatesAppLabel, - "role": EducatesRegistryRoleLabel, + if err = r.ensureNetwork(cli, constants.EducatesNetworkName); err != nil { + return err } - resp, err := cli.ContainerCreate(ctx, &container.Config{ - Image: RegistryImageV3, - Tty: false, - ExposedPorts: nat.PortSet{ - "5000/tcp": struct{}{}, - }, - Labels: labels, - }, hostConfig, nil, nil, EducatesRegistryContainer) - - if err != nil { + if _, err = r.createAndStartContainer(cli); err != nil { return errors.Wrap(err, "cannot create registry container") } - if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { - return errors.Wrap(err, "unable to start registry") - } - - cli.NetworkDisconnect(ctx, EducatesNetworkName, EducatesRegistryContainer, false) - - err = cli.NetworkConnect(ctx, EducatesNetworkName, EducatesRegistryContainer, &network.EndpointSettings{}) - - if err != nil { - return errors.Wrap(err, "unable to connect registry to educates network") + if err = r.connectToNetwork(cli, constants.EducatesNetworkName); err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to connect registry to %s network", constants.EducatesNetworkName)) } - if err = linkRegistryToClusterNetwork(EducatesRegistryContainer); err != nil { - return errors.Wrap(err, "failed to link registry to cluster") + if err = r.linkToNetwork(cli, constants.ClusterNetworkName); err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to link registry to %s network", constants.ClusterNetworkName)) } return nil } -/** - * This function is used to deploy a registry mirror and link it to the cluster. - * It is used when creating a new local registry mirror. - */ -func DeployMirrorAndLinkToCluster(mirrorConfig *config.RegistryMirrorConfig) error { - err := createMirrorContainer(mirrorConfig) - - if err != nil { - return errors.Wrap(err, "failed to deploy registry mirror "+mirrorConfig.Mirror) - } - - content := fmt.Sprintf(hostMirrorTomlTemplate, registryMirrorContainerName(mirrorConfig)) - err = addRegistryConfigToKindNodes(mirrorConfig.Mirror, content) - - if err != nil { - fmt.Println("Warning: Mirror not added to Kind nodes") - } - - return nil +// DeleteAndUnlinkFromCluster removes the registry and cleans up cluster configuration. +// For the registry, this is the same as Delete since the cluster config is tied to the cluster lifecycle. +func (r *Registry) DeleteAndUnlinkFromCluster() error { + return r.Delete() } -/** - * This private function only creates the registry mirror container. - */ -func createMirrorContainer(mirrorConfig *config.RegistryMirrorConfig) error { - ctx := context.Background() - - fmt.Printf("Deploying local image registry mirror %s\n", mirrorConfig.Mirror) - - cli, err := docker.NewDockerClient() - - if err != nil { - return errors.Wrap(err, "unable to create docker client") - } - - mirrorContainerName := registryMirrorContainerName(mirrorConfig) - _, err = cli.ContainerInspect(ctx, mirrorContainerName) - - if err == nil { - // If we can retrieve a container of required name we assume it is - // running okay. Technically it could be restarting, stopping or - // have exited and container was not removed, but if that is the case - // then leave it up to the user to sort out. - fmt.Printf("Registry mirror %s already exists\n", mirrorConfig.Mirror) - - return nil - } - - // Prepare environment variables for the registry mirror - envs := []string{} - mirrorURL := mirrorConfig.URL - if mirrorURL == "" { - mirrorURL = mirrorConfig.Mirror - } - envs = append(envs, fmt.Sprintf("REGISTRY_PROXY_REMOTEURL=https://%s", mirrorURL)) - if mirrorConfig.Username != "" { - envs = append(envs, fmt.Sprintf("REGISTRY_PROXY_USERNAME=%s", mirrorConfig.Username)) - } - if mirrorConfig.Password != "" { - envs = append(envs, fmt.Sprintf("REGISTRY_PROXY_PASSWORD=%s", mirrorConfig.Password)) - } - - _, err = cli.NetworkInspect(ctx, EducatesNetworkName, network.InspectOptions{}) - - if err != nil { - _, err = cli.NetworkCreate(ctx, EducatesNetworkName, network.CreateOptions{}) - - if err != nil { - return errors.Wrap(err, "cannot create educates network") - } - } - - hostConfig := &container.HostConfig{ - PortBindings: nat.PortMap{ - "5000/tcp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - // HostPort: mirrorConfig.Port, - }, - }, - }, - RestartPolicy: container.RestartPolicy{ - Name: "always", - }, - } - - labels := map[string]string{ - "app": EducatesAppLabel, - "role": EducatesMirrorRoleLabel, - "mirror": mirrorConfig.Mirror, - } - - resp, err := cli.ContainerCreate(ctx, &container.Config{ - Image: RegistryImageV3, - Tty: false, - Env: envs, - ExposedPorts: nat.PortSet{ - "5000/tcp": struct{}{}, - }, - Labels: labels, - }, hostConfig, nil, nil, mirrorContainerName) - - if err != nil { - return errors.Wrap(err, "cannot create local registry mirror container") - } - - if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { - return errors.Wrap(err, "unable to start local registry mirror") - } - - cli.NetworkDisconnect(ctx, EducatesNetworkName, mirrorContainerName, false) - - err = cli.NetworkConnect(ctx, EducatesNetworkName, mirrorContainerName, &network.EndpointSettings{}) - - if err != nil { - return errors.Wrap(err, "unable to connect local registry mirror to educates network") - } - - if err = linkRegistryToClusterNetwork(mirrorContainerName); err != nil { - return errors.Wrap(err, "failed to link local registry mirror to cluster") - } - - return nil -} - -/** - * This function is used to add the registry config to the kind nodes. - * It is used when creating a new local registry or registry mirror. - */ -func addRegistryConfigToKindNodes(repositoryName string, content string) error { - ctx := context.Background() - - fmt.Printf("Adding local image registry config (%s) to Kind nodes\n", repositoryName) - - cli, err := docker.NewDockerClient() +// Delete removes the registry container. +func (r *Registry) Delete() error { + fmt.Println("Deleting local image registry") + cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return errors.Wrap(err, "unable to create docker client") } - containerID, _ := getContainerInfo(EducatesControlPlaneContainer) - - registryDir := "/etc/containerd/certs.d/" + repositoryName - - cmdStatement := []string{"mkdir", "-p", registryDir} - - optionsCreateExecuteScript := container.ExecOptions{ - AttachStdout: true, - AttachStderr: true, - Cmd: cmdStatement, - } - - response, err := cli.ContainerExecCreate(ctx, containerID, optionsCreateExecuteScript) - if err != nil { - return errors.Wrap(err, "unable to create exec command") - } - hijackedResponse, err := cli.ContainerExecAttach(ctx, response.ID, container.ExecAttachOptions{}) - if err != nil { - return errors.Wrap(err, "unable to attach exec command") - } - - hijackedResponse.Close() - - buffer, err := tarFile([]byte(content), path.Join("/etc/containerd/certs.d/"+repositoryName, "hosts.toml"), 0x644) - if err != nil { - return err - } - err = cli.CopyToContainer(context.Background(), - containerID, "/", - buffer, - container.CopyToContainerOptions{ - AllowOverwriteDirWithFile: true, - }) - if err != nil { - return errors.Wrap(err, "unable to copy file to container") - } - - return nil + return r.stopAndRemoveContainer(cli) } -/** - * This function is used to remove the registry config from the kind nodes. - * It is used when deleting a local registry mirror. - */ -func removeRegistryConfigFromKindNodes(repositoryName string) error { - ctx := context.Background() - - fmt.Printf("Removing local image registry config (%s) from Kind nodes\n", repositoryName) - - cli, err := docker.NewDockerClient() - - if err != nil { - return errors.Wrap(err, "unable to create docker client") - } - - containerID, _ := getContainerInfo(EducatesControlPlaneContainer) - - if containerID == "" { +// documentLocalRegistry creates the ConfigMap that documents the local registry in the cluster. +func (r *Registry) documentLocalRegistry() error { + if r.k8sClient == nil { return nil } - registryDir := "/etc/containerd/certs.d/" + repositoryName - - cmdStatement := []string{"rm", "-rf", registryDir} - - optionsCreateExecuteScript := container.ExecOptions{ - AttachStdout: true, - AttachStderr: true, - Cmd: cmdStatement, - } - - response, err := cli.ContainerExecCreate(ctx, containerID, optionsCreateExecuteScript) - if err != nil { - return errors.Wrap(err, "unable to create exec command") - } - - hijackedResponse, err := cli.ContainerExecAttach(ctx, response.ID, container.ExecAttachOptions{}) - if err != nil { - return errors.Wrap(err, "unable to attach exec command") - } - - hijackedResponse.Close() - - return nil -} - -/** - * This function is used to document the local registry in the cluster. - * It is used when creating a new local registry. - */ -func documentLocalRegistry(client *kubernetes.Clientset) error { yamlBytes, err := yaml.Marshal(`host: "localhost:5001"`) if err != nil { return err @@ -428,13 +145,13 @@ func documentLocalRegistry(client *kubernetes.Clientset) error { }, } - if _, err := client.CoreV1().ConfigMaps("kube-public").Get(context.TODO(), "local-registry-hosting", metav1.GetOptions{}); k8serrors.IsNotFound(err) { - _, err = client.CoreV1().ConfigMaps("kube-public").Create(context.TODO(), configMap, metav1.CreateOptions{}) + if _, err := r.k8sClient.CoreV1().ConfigMaps("kube-public").Get(context.TODO(), "local-registry-hosting", metav1.GetOptions{}); k8serrors.IsNotFound(err) { + _, err = r.k8sClient.CoreV1().ConfigMaps("kube-public").Create(context.TODO(), configMap, metav1.CreateOptions{}) if err != nil { return errors.Wrap(err, "unable to create local registry hosting config map") } } else { - _, err = client.CoreV1().ConfigMaps("kube-public").Update(context.TODO(), configMap, metav1.UpdateOptions{}) + _, err = r.k8sClient.CoreV1().ConfigMaps("kube-public").Update(context.TODO(), configMap, metav1.UpdateOptions{}) if err != nil { return errors.Wrap(err, "unable to update local registry hosting config map") } @@ -443,180 +160,16 @@ func documentLocalRegistry(client *kubernetes.Clientset) error { return nil } -/** - * This function is used to link the registry to the cluster network, which is the kind network. - * It is used when creating a new local registry or registry mirror containers. - */ -func linkRegistryToClusterNetwork(containerName string) error { - ctx := context.Background() - - fmt.Println("Linking local image registry to cluster") - - cli, err := docker.NewDockerClient() - - if err != nil { - return errors.Wrap(err, "unable to create docker client") - } - - cli.NetworkDisconnect(ctx, "kind", containerName, false) - - err = cli.NetworkConnect(ctx, "kind", containerName, &network.EndpointSettings{}) - - if err != nil { - return errors.Wrap(err, "unable to connect registry to cluster network") - } - - return nil -} - -/** - * This function is used to delete the local registry. - * It is used when deleting a local registry or deleting all components of the local cluster. - */ -func DeleteRegistry() error { - ctx := context.Background() - - fmt.Println("Deleting local image registry") - - cli, err := docker.NewDockerClient() - - if err != nil { - return errors.Wrap(err, "unable to create docker client") - } - - _, err = cli.ContainerInspect(ctx, EducatesRegistryContainer) - - if err != nil { - // If we can't retrieve a container of required name we assume it does - // not actually exist. - - return nil - } - - timeout := 30 - - err = cli.ContainerStop(ctx, EducatesRegistryContainer, container.StopOptions{Timeout: &timeout}) - - // timeout := time.Duration(30) * time.Second - - // err = cli.ContainerStop(ctx, EducatesRegistryContainer, &timeout) - - if err != nil { - return errors.Wrap(err, "unable to stop registry container") - } - - err = cli.ContainerRemove(ctx, EducatesRegistryContainer, container.RemoveOptions{}) - - if err != nil { - return errors.Wrap(err, "unable to delete registry container") - } - - return nil -} - -/** - * This function is used to delete a local registry mirror and unlink it from the cluster. - * It is used when deleting a local registry mirror. - */ -func DeleteMirrorAndUnlinkFromCluster(mirrorConfig *config.RegistryMirrorConfig) error { - ctx := context.Background() - - fmt.Printf("Deleting local image registry mirror %s\n", mirrorConfig.Mirror) - - cli, err := docker.NewDockerClient() - - if err != nil { - return errors.Wrap(err, "unable to create docker client") - } - - containerName := registryMirrorContainerName(mirrorConfig) - _, err = cli.ContainerInspect(ctx, containerName) - - if err != nil { - // If we can't retrieve a container of required name we assume it does - // not actually exist. - - fmt.Printf("Registry mirror %s does not exist\n", mirrorConfig.Mirror) - return nil - } - - timeout := 30 - - err = cli.ContainerStop(ctx, containerName, container.StopOptions{Timeout: &timeout}) - - if err != nil { - return errors.Wrap(err, "unable to stop registry mirror container "+containerName) - } - - err = cli.ContainerRemove(ctx, containerName, container.RemoveOptions{}) - - if err != nil { - return errors.Wrap(err, "unable to delete registry mirror container "+containerName) - } - - // Remove the registry config from the kind nodes - err = removeRegistryConfigFromKindNodes(mirrorConfig.Mirror) - - if err != nil { - return errors.Wrap(err, "unable to remove registry config from kind nodes") - } - - return nil -} - -func DeleteRegistryMirrors() error { - ctx := context.Background() - - fmt.Println("Deleting local image registry mirrors") - - cli, err := docker.NewDockerClient() - - if err != nil { - return errors.Wrap(err, "unable to create docker client") - } - - mirrors, err := cli.ContainerList(ctx, container.ListOptions{ - Filters: filters.NewArgs( - filters.Arg("label", "role="+EducatesMirrorRoleLabel), - filters.Arg("label", "app="+EducatesAppLabel), - ), - }) - - if err != nil { - return errors.Wrap(err, "unable to list registry mirrors") - } - - for _, mirror := range mirrors { - - timeout := 30 - - err = cli.ContainerStop(ctx, mirror.ID, container.StopOptions{Timeout: &timeout}) - - if err != nil { - return errors.Wrap(err, "unable to stop registry mirror container "+mirror.ID) - } - - err = cli.ContainerRemove(ctx, mirror.ID, container.RemoveOptions{}) - - if err != nil { - return errors.Wrap(err, "unable to delete registry mirror container "+mirror.ID) - } - +// UpdateK8SService updates the registry k8s service. +// It is used when creating a cluster or a registry in order to update the k8s service to point to the new registry. +func (r *Registry) UpdateK8SService() error { + if r.k8sClient == nil { + return errors.New("kubernetes client is required for UpdateK8SService") } - return nil -} - -/** - * TODO: Learn whether this is needed or not - * This function is used to update the registry k8s service. - * It is used when creating a cluster or a registry in order to update the k8s service to point to the new registry. - */ -func UpdateRegistryK8SService(k8sclient *kubernetes.Clientset) error { ctx := context.Background() - cli, err := docker.NewDockerClient() - + cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return errors.Wrap(err, "unable to create docker client") } @@ -641,14 +194,12 @@ func UpdateRegistryK8SService(k8sclient *kubernetes.Clientset) error { endpointAppProtocol := "http" endpointProtocol := v1.ProtocolTCP - registryInfo, err := cli.ContainerInspect(ctx, EducatesRegistryContainer) - + registryInfo, err := cli.ContainerInspect(ctx, constants.EducatesRegistryContainer) if err != nil { return errors.Wrapf(err, "unable to inspect container for registry") } kindNetwork, exists := registryInfo.NetworkSettings.Networks["kind"] - if !exists { return errors.New("registry is not attached to kind network") } @@ -678,22 +229,20 @@ func UpdateRegistryK8SService(k8sclient *kubernetes.Clientset) error { }, } - endpointSliceClient := k8sclient.DiscoveryV1().EndpointSlices("default") + endpointSliceClient := r.k8sClient.DiscoveryV1().EndpointSlices("default") endpointSliceClient.Delete(context.TODO(), "registry-1", *metav1.NewDeleteOptions(0)) - servicesClient := k8sclient.CoreV1().Services("default") + servicesClient := r.k8sClient.CoreV1().Services("default") servicesClient.Delete(context.TODO(), "registry", *metav1.NewDeleteOptions(0)) _, err = endpointSliceClient.Create(context.TODO(), &endpointSlice, metav1.CreateOptions{}) - if err != nil { return errors.Wrap(err, "unable to create registry headless service endpoint") } _, err = servicesClient.Create(context.TODO(), &service, metav1.CreateOptions{}) - if err != nil { return errors.Wrap(err, "unable to create registry headless service") } @@ -701,20 +250,23 @@ func UpdateRegistryK8SService(k8sclient *kubernetes.Clientset) error { return nil } -func PruneRegistry() error { +// Prune runs garbage collection on the registry. +func (r *Registry) Prune() error { ctx := context.Background() fmt.Println("Pruning local image registry") - cli, err := docker.NewDockerClient() - + cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return errors.Wrap(err, "unable to create docker client") } - containerID, _ := getContainerInfo(EducatesRegistryContainer) + containerID, _ := utils.GetContainerInfo(constants.EducatesRegistryContainer) + if containerID == "" { + return nil + } - cmdStatement := []string{"registry", "garbage-collect", RegistryConfigTargetPath, "--delete-untagged=true"} + cmdStatement := []string{"registry", "garbage-collect", constants.RegistryConfigTargetPath, "--delete-untagged=true"} optionsCreateExecuteScript := container.ExecOptions{ AttachStdout: false, @@ -736,75 +288,5 @@ func PruneRegistry() error { return nil } -/** - * This function is used to get the container name of a registry mirror. - */ -func registryMirrorContainerName(mirrorConfig *config.RegistryMirrorConfig) string { - return fmt.Sprintf("%s-mirror-%s", EducatesRegistryContainer, mirrorConfig.Mirror) -} - -/** - * This function is used to get the container id and status of a container. - * If the container does not exist, it will return an empty string for the container id and status. - */ -func getContainerInfo(containerName string) (containerID string, status string) { - ctx := context.Background() - - cli, err := docker.NewDockerClient() - if err != nil { - panic(err) - } - - filters := filters.NewArgs() - filters.Add( - "name", containerName, - ) - - resp, err := cli.ContainerList(ctx, container.ListOptions{Filters: filters}) - if err != nil { - panic(err) - } - - if len(resp) > 0 { - containerID = resp[0].ID - containerStatus := strings.Split(resp[0].Status, " ") - status = containerStatus[0] //fmt.Println(status[0]) - } else { - fmt.Printf("container '%s' does not exists\n", containerName) - } - - return -} - -/** - * This function is used to tar a file to be copied into a container. - */ -func tarFile(fileContent []byte, basePath string, fileMode int64) (*bytes.Buffer, error) { - buffer := &bytes.Buffer{} - - zr := gzip.NewWriter(buffer) - tw := tar.NewWriter(zr) - - hdr := &tar.Header{ - Name: basePath, - Mode: fileMode, - Size: int64(len(fileContent)), - } - if err := tw.WriteHeader(hdr); err != nil { - return buffer, err - } - if _, err := tw.Write(fileContent); err != nil { - return buffer, err - } - - // produce tar - if err := tw.Close(); err != nil { - return buffer, fmt.Errorf("error closing tar file: %w", err) - } - // produce gzip - if err := zr.Close(); err != nil { - return buffer, fmt.Errorf("error closing gzip file: %w", err) - } - - return buffer, nil -} +// Compile-time check that Registry implements ContainerManager +var _ ContainerManager = (*Registry)(nil) diff --git a/client-programs/pkg/renderer/hugo.go b/client-programs/pkg/renderer/hugo.go index f3b65d5bb..19572b371 100644 --- a/client-programs/pkg/renderer/hugo.go +++ b/client-programs/pkg/renderer/hugo.go @@ -23,13 +23,14 @@ import ( "syscall" "time" - "github.com/pkg/errors" "github.com/educates/educates-training-platform/client-programs/pkg/cluster" + educatesTypes "github.com/educates/educates-training-platform/client-programs/pkg/educates/types" + "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" "gopkg.in/yaml.v2" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" ) //go:embed all:files/* @@ -61,7 +62,7 @@ func copyFiles(fs embed.FS, src string, dst string) error { return errors.Wrapf(err, "unable to open source file %q", srcFile) } - err = ioutil.WriteFile(dstFile, input, 0644) + err = os.WriteFile(dstFile, input, 0644) if err != nil { return errors.Wrapf(err, "unable to create source file %q", dstFile) @@ -104,7 +105,19 @@ type WorkshopConfig struct { Params []WorkshopParamsConfig `yaml:"params,omitempty"` } -var workshopSessionResource = schema.GroupVersionResource{Group: "training.educates.dev", Version: "v1beta1", Resource: "workshopsessions"} +type RunHugoServerConfig struct { + WorkshopRoot string + Kubeconfig string + Context string + Workshop string + Portal string + LocalHost string + LocalPort int + HugoPort int + Token string + Files bool + CleanupFunc ServerCleanupFunc +} func fetchWorkshopSessionAndValidate(kubeconfig string, kubeContext string, workshop string, portal string, session string) (string, string, error) { // Returns session URL, config password and error. @@ -119,7 +132,7 @@ func fetchWorkshopSessionAndValidate(kubeconfig string, kubeContext string, work return "", "", errors.Wrapf(err, "unable to create Kubernetes client") } - workshopSessionClient := dynamicClient.Resource(workshopSessionResource) + workshopSessionClient := dynamicClient.Resource(educatesTypes.WorkshopsessionsResource) workshopSession, err := workshopSessionClient.Get(context.TODO(), session, metav1.GetOptions{}) @@ -180,7 +193,7 @@ func fetchSessionVariables(sessionURL string, password string) (map[string]strin return params, errors.New("unexpected failure querying workshop session config") } - resBody, err := ioutil.ReadAll(res.Body) + resBody, err := io.ReadAll(res.Body) if err != nil { return params, errors.Wrapf(err, "failed to read workshop session config") @@ -375,7 +388,7 @@ func startHugoServer(workshopDir string, tempDir string, port int, sessionURL st } func populateTemporaryDirectory() (string, error) { - tempDir, err := ioutil.TempDir("", "educates") + tempDir, err := os.MkdirTemp("", "educates") if err != nil { return "", errors.Wrapf(err, "unable to create hugo files directory") @@ -392,11 +405,11 @@ func populateTemporaryDirectory() (string, error) { type ServerCleanupFunc func() -func RunHugoServer(workshopRoot string, kubeconfig string, context string, workshop string, portal string, localHost string, localPort int, hugoPort int, token string, files bool, cleanupFunc ServerCleanupFunc) error { +func RunHugoServer(o *RunHugoServerConfig) error { var err error var tempDir string - workshopDir := filepath.Join(workshopRoot, "workshop") + workshopDir := filepath.Join(o.WorkshopRoot, "workshop") // First create directory to hold unpacked files for Hugo to use. @@ -417,8 +430,8 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works os.RemoveAll(tempDir) - if cleanupFunc != nil { - cleanupFunc() + if o.CleanupFunc != nil { + o.CleanupFunc() } os.Exit(1) @@ -434,10 +447,10 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works proxyHandler := func(w http.ResponseWriter, r *http.Request) { // If an access token is provided validate it. - if token != "" { + if o.Token != "" { accessToken := r.Header.Get("X-Access-Token") - if accessToken != token { + if accessToken != o.Token { w.WriteHeader(http.StatusForbidden) w.Write([]byte("403 - Invalid access token")) @@ -463,7 +476,7 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works if sessionName != lastSessionName { // First validate that can access workshop session. - sessionURL, password, err := fetchWorkshopSessionAndValidate(kubeconfig, context, workshop, portal, sessionName) + sessionURL, password, err := fetchWorkshopSessionAndValidate(o.Kubeconfig, o.Context, o.Workshop, o.Portal, sessionName) if err != nil { fmt.Println("Error validating workshop session:", err) @@ -512,7 +525,7 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works if !hugoStarted { fmt.Println("Starting Hugo server") - go startHugoServer(workshopDir, tempDir, hugoPort, sessionURL) + go startHugoServer(workshopDir, tempDir, o.HugoPort, sessionURL) time.Sleep(4 * time.Second) @@ -528,7 +541,7 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works serverDetailsLock.Unlock() - hugoServerURL := fmt.Sprintf("http://localhost:%d", hugoPort) + hugoServerURL := fmt.Sprintf("http://localhost:%d", o.HugoPort) target, _ := url.Parse(hugoServerURL) proxy := httputil.NewSingleHostReverseProxy(target) @@ -543,10 +556,10 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works http.HandleFunc("/workshop/content/", proxyHandler) filesHandler := func(w http.ResponseWriter, r *http.Request) { - if token != "" { + if o.Token != "" { accessToken := r.URL.Query().Get("token") - if accessToken != token { + if accessToken != o.Token { w.WriteHeader(http.StatusForbidden) w.Write([]byte("403 - Invalid access token")) @@ -558,7 +571,7 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works tw := tar.NewWriter(w) - filepath.Walk(workshopRoot, func(file string, fi os.FileInfo, err error) error { + filepath.Walk(o.WorkshopRoot, func(file string, fi os.FileInfo, err error) error { if err != nil { return err } @@ -568,7 +581,7 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works return err } - header.Name, err = filepath.Rel(workshopRoot, filepath.ToSlash(file)) + header.Name, err = filepath.Rel(o.WorkshopRoot, filepath.ToSlash(file)) if err != nil { return err @@ -596,11 +609,11 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works }) } - if files { + if o.Files { http.HandleFunc("/workshop/files.tar", filesHandler) } - portString := fmt.Sprintf("%s:%d", localHost, localPort) + portString := fmt.Sprintf("%s:%d", o.LocalHost, o.LocalPort) fmt.Println("Proxy listening on:", portString) @@ -608,3 +621,48 @@ func RunHugoServer(workshopRoot string, kubeconfig string, context string, works return nil } + +func GenerateAccessToken(refresh bool) (string, error) { + configFileDir := utils.GetEducatesHomeDir() + accessTokenFile := path.Join(configFileDir, "live-reload-token.dat") + + err := os.MkdirAll(configFileDir, os.ModePerm) + + if err != nil { + return "", errors.Wrapf(err, "unable to create config directory") + } + + var accessToken string + + if refresh { + accessToken = utils.RandomPassword(32) + + err := os.WriteFile(accessTokenFile, []byte(accessToken), 0644) + + if err != nil { + return "", err + } + } else { + if _, err := os.Stat(accessTokenFile); err == nil { + accessTokenBytes, err := ioutil.ReadFile(accessTokenFile) + + if err != nil { + return "", err + } + + accessToken = string(accessTokenBytes) + } else if os.IsNotExist(err) { + accessToken = utils.RandomPassword(32) + + err = os.WriteFile(accessTokenFile, []byte(accessToken), 0644) + + if err != nil { + return "", err + } + } else { + return "", err + } + } + + return accessToken, nil +} diff --git a/client-programs/pkg/resolver/resolver.go b/client-programs/pkg/resolver/resolver.go index e21dadc6d..bfbbe6ec8 100644 --- a/client-programs/pkg/resolver/resolver.go +++ b/client-programs/pkg/resolver/resolver.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/api/types/mount" "github.com/docker/go-connections/nat" "github.com/educates/educates-training-platform/client-programs/pkg/config" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/docker" "github.com/educates/educates-training-platform/client-programs/pkg/utils" "github.com/pkg/errors" @@ -33,8 +34,7 @@ address=/{{.IngressDomain}}/{{.TargetAddress}} address=/{{$Domain}}/{{$.TargetAddress}} {{- end }} ` - dnsmasqImage = "ghcr.io/dockur/dnsmasq:2.90" - resolverContainerName = "educates-resolver" + dnsmasqImage = "ghcr.io/dockur/dnsmasq:2.92" ) func DeployResolver(domain string, targetAddress string, extraDomains []string) error { @@ -48,7 +48,7 @@ func DeployResolver(domain string, targetAddress string, extraDomains []string) return errors.Wrap(err, "unable to create docker client") } - _, err = cli.ContainerInspect(ctx, resolverContainerName) + _, err = cli.ContainerInspect(ctx, constants.EducatesResolverContainerName) if err == nil { // If we can retrieve a container of required name we assume it is @@ -105,7 +105,8 @@ func DeployResolver(domain string, targetAddress string, extraDomains []string) ExposedPorts: nat.PortSet{ "53/udp": struct{}{}, }, - }, hostConfig, nil, nil, resolverContainerName) + Labels: newResolverContainerLabels(), + }, hostConfig, nil, nil, constants.EducatesResolverContainerName) if err != nil { return errors.Wrap(err, "cannot create resolver container") @@ -115,7 +116,7 @@ func DeployResolver(domain string, targetAddress string, extraDomains []string) return errors.Wrap(err, "unable to start resolver") } - fmt.Println("Local DNS resolver running as a Docker container", resolverContainerName) + fmt.Println("Local DNS resolver running as a Docker container", constants.EducatesResolverContainerName) fmt.Println("Local DNS resolver configuration in", configFileName) return nil @@ -132,7 +133,7 @@ func DeleteResolver() error { return errors.Wrap(err, "unable to create docker client") } - _, err = cli.ContainerInspect(ctx, resolverContainerName) + _, err = cli.ContainerInspect(ctx, constants.EducatesResolverContainerName) if err != nil { // If we can't retrieve a container of required name we assume it does @@ -143,13 +144,13 @@ func DeleteResolver() error { timeout := 30 - err = cli.ContainerStop(ctx, resolverContainerName, container.StopOptions{Timeout: &timeout}) + err = cli.ContainerStop(ctx, constants.EducatesResolverContainerName, container.StopOptions{Timeout: &timeout}) if err != nil { return errors.Wrap(err, "unable to stop DNS resolver container") } - err = cli.ContainerRemove(ctx, resolverContainerName, container.RemoveOptions{}) + err = cli.ContainerRemove(ctx, constants.EducatesResolverContainerName, container.RemoveOptions{}) if err != nil { return errors.Wrap(err, "unable to delete DNS resolver container") @@ -168,7 +169,7 @@ func UpdateResolver(domain string, targetAddress string, extraDomains []string) return errors.Wrap(err, "unable to create docker client") } - _, err = cli.ContainerInspect(ctx, resolverContainerName) + _, err = cli.ContainerInspect(ctx, constants.EducatesResolverContainerName) if err != nil { return errors.Wrap(err, "resolver container not found") } @@ -178,7 +179,7 @@ func UpdateResolver(domain string, targetAddress string, extraDomains []string) return err } - err = cli.ContainerRestart(ctx, resolverContainerName, container.StopOptions{}) + err = cli.ContainerRestart(ctx, constants.EducatesResolverContainerName, container.StopOptions{}) if err != nil { return errors.Wrap(err, "failed to restart resolver") } @@ -252,3 +253,10 @@ func generateDnsmasqConfig(domain string, targetAddress string, extraDomains []s return configFileName, nil } + +func newResolverContainerLabels() map[string]string { + return map[string]string{ + constants.EducatesContainersRoleLabelKey: constants.EducatesContainersResolverRoleLabel, + constants.EducatesContainersAppLabelKey: constants.EducatesContainersAppLabel, + } +} diff --git a/client-programs/pkg/secrets/secrets.go b/client-programs/pkg/secrets/secrets.go index 568091f6d..2591332dd 100644 --- a/client-programs/pkg/secrets/secrets.go +++ b/client-programs/pkg/secrets/secrets.go @@ -6,8 +6,9 @@ import ( "path" "strings" - "github.com/pkg/errors" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" "github.com/educates/educates-training-platform/client-programs/pkg/utils" + "github.com/pkg/errors" apiv1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -40,7 +41,7 @@ func LocalCachedSecretForIngressDomain(domain string) string { annotations := secretObj.ObjectMeta.Annotations // Domain name must match. - if val, found := annotations["training.educates.dev/domain"]; !found || val != domain { + if val, found := annotations[constants.EducatesTrainingLabelAnnotationDomain]; !found || val != domain { continue } @@ -83,7 +84,7 @@ func LocalCachedSecretForCertificateAuthority(domain string) string { annotations := secretObj.ObjectMeta.Annotations // Domain name must match. - if val, found := annotations["training.educates.dev/domain"]; !found || val != domain { + if val, found := annotations[constants.EducatesTrainingLabelAnnotationDomain]; !found || val != domain { continue } @@ -169,7 +170,7 @@ func SyncLocalCachedSecretsToCluster(client *kubernetes.Clientset) error { patch = applycorev1.Secret(name, secretsNS).WithType(secretObj.Type).WithData(secretObj.Data) } - _, err = secretsClient.Apply(context.TODO(), patch, metav1.ApplyOptions{FieldManager: "educates-cli", Force: true}) + _, err = secretsClient.Apply(context.TODO(), patch, metav1.ApplyOptions{FieldManager: constants.DefaultPortalName, Force: true}) if err != nil { return errors.Wrapf(err, "unable to update secret in cluster %q", name) diff --git a/client-programs/pkg/templates/files/classic/workshop/content/01-workshop-instructions.md b/client-programs/pkg/templates/files/classic/workshop/content/01-workshop-instructions.md index 2b9a4d3e7..a0df6a0be 100644 --- a/client-programs/pkg/templates/files/classic/workshop/content/01-workshop-instructions.md +++ b/client-programs/pkg/templates/files/classic/workshop/content/01-workshop-instructions.md @@ -1 +1 @@ -This is the first page of the workshop instructions, create as many separate pages as you need to. If necessary pages can be located in sub directories to provided grouping. \ No newline at end of file +This is the first page of the workshop instructions, create as many separate pages as you need to. If necessary pages can be located in sub directories to provided grouping. diff --git a/client-programs/pkg/templates/files/hugo/.gitignore b/client-programs/pkg/templates/files/hugo/.gitignore index 62c7b6daa..47bb55a6e 100644 --- a/client-programs/pkg/templates/files/hugo/.gitignore +++ b/client-programs/pkg/templates/files/hugo/.gitignore @@ -1,2 +1,2 @@ workshop/.hugo_build.lock -workshop/public \ No newline at end of file +workshop/public diff --git a/client-programs/pkg/templates/github/single/.github/workflows/publish-workshop.yaml b/client-programs/pkg/templates/github/single/.github/workflows/publish-workshop.yaml index 045c4ec0a..d322f4e0b 100644 --- a/client-programs/pkg/templates/github/single/.github/workflows/publish-workshop.yaml +++ b/client-programs/pkg/templates/github/single/.github/workflows/publish-workshop.yaml @@ -19,4 +19,4 @@ jobs: - name: Create release uses: educates/educates-github-actions/publish-workshop@v7 with: - token: {{ "${{secrets.GITHUB_TOKEN}}" }} \ No newline at end of file + token: {{ "${{secrets.GITHUB_TOKEN}}" }} diff --git a/client-programs/pkg/tunnel/tunnel.go b/client-programs/pkg/tunnel/tunnel.go new file mode 100644 index 000000000..20eb7e033 --- /dev/null +++ b/client-programs/pkg/tunnel/tunnel.go @@ -0,0 +1,104 @@ +package tunnel + +import ( + "fmt" + "net/http" + "net/url" + "os" + + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +type session struct { + ws *websocket.Conn + errChan chan error +} + +type Tunnel struct { + Url string +} + +func NewTunnel(url string) *Tunnel { + return &Tunnel{ + Url: url, + } +} + +func (t *Tunnel) Start() error { + dest, err := url.Parse(t.Url) + + if err != nil { + return errors.Wrap(err, "unable to parse websocket URL") + } + + originURL := *dest + + origin := originURL.String() + + headers := make(http.Header) + headers.Add("Origin", origin) + + dialer := websocket.Dialer{} + + ws, _, err := dialer.Dial(origin, headers) + + if err != nil { + return errors.Wrap(err, "unable to connect to websocket URL") + } + + sess := &session{ + ws: ws, + errChan: make(chan error), + } + + go sess.readInput() + go sess.readRemote() + + os.Stderr.WriteString(fmt.Sprintf("%s\n", <-sess.errChan)) + + return nil +} + +func (s *session) readInput() { + in := os.Stdin + + const BUF_SIZE = 16384 + bufOut := make([]byte, BUF_SIZE) + + for { + var n int + var err error + + if n, err = in.Read(bufOut); err != nil || n == 0 { + break + } + + if err = s.ws.WriteMessage(websocket.BinaryMessage, bufOut[0:n]); err != nil { + break + } + } +} + +func (s *session) readRemote() { + out := os.Stdout + + for { + msgType, buf, err := s.ws.ReadMessage() + + if err != nil { + s.errChan <- err + return + } + + switch msgType { + case websocket.BinaryMessage: + if _, err = out.Write(buf); err != nil { + return + } + default: + s.errChan <- fmt.Errorf("unexpected websocket frame type: %d", msgType) + return + } + } +} diff --git a/client-programs/pkg/utils/browser.go b/client-programs/pkg/utils/browser.go new file mode 100644 index 000000000..c16dc2621 --- /dev/null +++ b/client-programs/pkg/utils/browser.go @@ -0,0 +1,28 @@ +package utils + +import ( + "fmt" + "os/exec" + "runtime" +) + +func OpenBrowser(url string) error { + var err error + + if url == "" { + return fmt.Errorf("url is required") + } + + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = fmt.Errorf("unsupported platform") + } + + return err +} diff --git a/client-programs/pkg/utils/cmd_error.go b/client-programs/pkg/utils/cmd_error.go new file mode 100644 index 000000000..f02156e73 --- /dev/null +++ b/client-programs/pkg/utils/cmd_error.go @@ -0,0 +1,23 @@ +package utils + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func CmdError(cmd *cobra.Command, errorMessage string, additionalMessage string) error { + return cmdError(cmd, errorMessage, additionalMessage, false) +} + +func CmdErrorFullUsage(cmd *cobra.Command, errorMessage string, additionalMessage string) error { + return cmdError(cmd, errorMessage, additionalMessage, true) +} + +func cmdError(cmd *cobra.Command, errorMessage string, additionalMessage string, fullUsage bool) error { + if fullUsage { + return fmt.Errorf("%s\n\n%s", errorMessage, cmd.UsageString()) + } + + return fmt.Errorf("%s\n\n%s %s\nRun '%s --help' for details.", errorMessage, cmd.CommandPath(), additionalMessage, cmd.CommandPath()) +} diff --git a/client-programs/pkg/utils/dirs.go b/client-programs/pkg/utils/dirs.go index 2a2ea6e68..ebe6d8836 100644 --- a/client-programs/pkg/utils/dirs.go +++ b/client-programs/pkg/utils/dirs.go @@ -4,8 +4,9 @@ import ( "path" "github.com/adrg/xdg" + "github.com/educates/educates-training-platform/client-programs/pkg/constants" ) func GetEducatesHomeDir() string { - return path.Join(xdg.DataHome, "educates") + return path.Join(xdg.DataHome, constants.EducatesHomeDirName) } diff --git a/client-programs/pkg/utils/docker.go b/client-programs/pkg/utils/docker.go new file mode 100644 index 000000000..edb273de4 --- /dev/null +++ b/client-programs/pkg/utils/docker.go @@ -0,0 +1,55 @@ +package utils + +import ( + "context" + "fmt" + "strings" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" +) + +func GetContainerName(container container.Summary) string { + name := "unknown" + + if len(container.Names) > 0 { + // Get the first name and strip the leading slash "/" + name = strings.TrimPrefix(container.Names[0], "/") + } + + return name +} + +/** + * This function is used to get the container id and status of a container. + * If the container does not exist, it will return an empty string for the container id and status. + */ + func GetContainerInfo(containerName string) (containerID string, status string) { + ctx := context.Background() + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + panic(err) + } + + filters := filters.NewArgs() + filters.Add( + "name", containerName, + ) + + resp, err := cli.ContainerList(ctx, container.ListOptions{Filters: filters}) + if err != nil { + panic(err) + } + + if len(resp) > 0 { + containerID = resp[0].ID + containerStatus := strings.Split(resp[0].Status, " ") + status = containerStatus[0] //fmt.Println(status[0]) + } else { + fmt.Printf("container '%s' does not exists\n", containerName) + } + + return +} diff --git a/client-programs/pkg/utils/password.go b/client-programs/pkg/utils/password.go new file mode 100644 index 000000000..a6e18605b --- /dev/null +++ b/client-programs/pkg/utils/password.go @@ -0,0 +1,20 @@ +package utils + +import ( + "hash/maphash" + "math/rand" + "strings" +) + +func RandomPassword(length int) string { + rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64()))) + + chars := []rune("!#%+23456789:=?@ABCDEFGHJKLMNPRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + + var b strings.Builder + + for i := 0; i < length; i++ { + b.WriteRune(chars[rand.Intn(len(chars))]) + } + return b.String() +} diff --git a/client-programs/pkg/utils/printer.go b/client-programs/pkg/utils/printer.go new file mode 100644 index 000000000..abe9fc3f1 --- /dev/null +++ b/client-programs/pkg/utils/printer.go @@ -0,0 +1,44 @@ +package utils + +import ( + "fmt" + "strings" + "text/tabwriter" +) + +func PrintTable(headers []string, data [][]string) string { + var buf strings.Builder + w := tabwriter.NewWriter(&buf, 8, 8, 3, ' ', 0) + + // Print headers + fmt.Fprintln(w, strings.Join(headers, "\t")) + + // Print data rows + for i, row := range data { + if i < len(data) - 1 { + fmt.Fprintln(w, strings.Join(row, "\t")) + }else { + fmt.Fprint(w, strings.Join(row, "\t")) + } + } + + w.Flush() + return buf.String() +} + +func PrintKeyValuesTable(data [][]string) string { + var buf strings.Builder + w := tabwriter.NewWriter(&buf, 8, 8, 3, ' ', 0) + + // Print data rows + for i, row := range data { + if i < len(data) - 1 { + fmt.Fprintf(w, "%s:\t%s\n", row[0], row[1]) + }else { + fmt.Fprintf(w, "%s:\t%s", row[0], row[1]) + } + } + + w.Flush() + return buf.String() +} diff --git a/client-programs/pkg/utils/prompt.go b/client-programs/pkg/utils/prompt.go index 4dc526bee..b5ea73496 100644 --- a/client-programs/pkg/utils/prompt.go +++ b/client-programs/pkg/utils/prompt.go @@ -9,18 +9,24 @@ import ( // YesNoPrompt prompts the user for a yes/no answer to a question. // It returns true if the user answers yes, false if the user answers no, and the default value if the user does not answer. -func YesNoPrompt(label string, def bool) bool { +func YesNoPrompt(labels []string, def bool) bool { choices := "Y/n" if !def { choices = "y/N" } - r := bufio.NewReader(os.Stdin) - var s string + // 1. Construct the prompt string + // Join all strings with a newline + labelText := strings.Join(labels, "\n") + + // Append the choices to the very end + // Result example: "Line1\nLine2 (Y/n) " + fullPrompt := fmt.Sprintf("%s (%s) ", labelText, choices) + r := bufio.NewReader(os.Stdin) for { - fmt.Fprintf(os.Stderr, "%s (%s) ", label, choices) - s, _ = r.ReadString('\n') + fmt.Fprintf(os.Stderr, "%s",fullPrompt) + s, _ := r.ReadString('\n') s = strings.TrimSpace(s) if s == "" { return def diff --git a/client-programs/pkg/workshops/export.go b/client-programs/pkg/workshops/export.go deleted file mode 100644 index 1bf616943..000000000 --- a/client-programs/pkg/workshops/export.go +++ /dev/null @@ -1,108 +0,0 @@ -package workshops - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - yttcmd "carvel.dev/ytt/pkg/cmd/template" - "github.com/pkg/errors" - "gopkg.in/yaml.v2" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/kubectl/pkg/scheme" -) - -type FilesExportOptions struct { - Repository string - WorkshopFile string - WorkshopVersion string - DataValuesFlags yttcmd.DataValuesFlags -} - -func (o *FilesExportOptions) Run(args []string) error { - var err error - - var workshopDir string - - if len(args) != 0 { - workshopDir = filepath.Clean(args[0]) - } else { - workshopDir = "." - } - - if workshopDir, err = filepath.Abs(workshopDir); err != nil { - return errors.Wrap(err, "couldn't convert workshop directory to absolute path") - } - - fileInfo, err := os.Stat(workshopDir) - - if err != nil || !fileInfo.IsDir() { - return errors.New("workshop directory does not exist or path is not a directory") - } - - return o.Export(workshopDir) -} - -func (o *FilesExportOptions) Export(workshopDir string) error { - rootDirectory := workshopDir - workshopFilePath := o.WorkshopFile - - // 1. Find the workshop definition file - if !filepath.IsAbs(workshopFilePath) { - workshopFilePath = filepath.Join(rootDirectory, workshopFilePath) - } - - workshopFileData, err := os.ReadFile(workshopFilePath) - - if err != nil { - return errors.Wrapf(err, "cannot open workshop definition %q", workshopFilePath) - } - - // 2. Process the workshop definition file through the ytt templating engine - if workshopFileData, err = ProcessWorkshopDefinition(workshopFileData, o.DataValuesFlags); err != nil { - return errors.Wrap(err, "unable to process workshop definition as template") - } - - // 3. Replace the image repository and workshop version placeholders with the actual values valid for exporting and publishing - workshopFileData = []byte(strings.ReplaceAll(string(workshopFileData), "$(image_repository)", o.Repository)) - workshopFileData = []byte(strings.ReplaceAll(string(workshopFileData), "$(workshop_version)", o.WorkshopVersion)) - - // 4. Decode the workshop definition and perform validations - decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder() - - workshop := &unstructured.Unstructured{} - - err = runtime.DecodeInto(decoder, workshopFileData, workshop) - - if err != nil { - return errors.Wrap(err, "couldn't parse workshop definition") - } - - if workshop.GetAPIVersion() != "training.educates.dev/v1beta1" || workshop.GetKind() != "Workshop" { - return errors.New("invalid type for workshop definition") - } - - _, found, _ := unstructured.NestedString(workshop.Object, "spec", "version") - - if !found && o.WorkshopVersion != "latest" { - unstructured.SetNestedField(workshop.Object, o.WorkshopVersion, "spec", "version") - } - - // 5. Remove the publish field from the workshop definition - unstructured.RemoveNestedField(workshop.Object, "spec", "publish") - - // 6. Convert the workshop definition back to YAML format - workshopFileData, err = yaml.Marshal(&workshop.Object) - - if err != nil { - return errors.Wrap(err, "couldn't convert workshop definition back to YAML") - } - - // 7. Print the workshop definition to stdout - fmt.Print(string(workshopFileData)) - - return nil -} diff --git a/client-programs/pkg/workshops/new.go b/client-programs/pkg/workshops/new.go deleted file mode 100644 index 260b9ce75..000000000 --- a/client-programs/pkg/workshops/new.go +++ /dev/null @@ -1,101 +0,0 @@ -package workshops - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strconv" - - "github.com/educates/educates-training-platform/client-programs/pkg/templates" - "github.com/educates/educates-training-platform/client-programs/pkg/utils" - "github.com/pkg/errors" -) - -type WorkshopNewOptions struct { - Template string - Name string - Title string - Description string - Image string - TargetDirectory string - Overwrite bool - WithKubernetesAccess bool - WithGitHubAction bool - WithVirtualCluster bool - WithDockerDaemon bool - WithImageRegistry bool - WithKubernetesConsole bool - WithEditor bool - WithTerminal bool -} - -// If o.TargetDirectory is provided, we will use that as the directory to be used, otherwise a new one will be created -func (o *WorkshopNewOptions) Run(args []string) error { - var err error - - workshopDir := filepath.Clean(args[0]) - if o.TargetDirectory != "" { - workshopDir = o.TargetDirectory - } - - if workshopDir, err = filepath.Abs(workshopDir); err != nil { - return errors.Wrapf(err, "could not convert path name %q to absolute path", workshopDir) - } - - if o.TargetDirectory == "" { - if _, err = os.Stat(workshopDir); err == nil { - return errors.Errorf("target path name %q already exists", workshopDir) - } - } else { - // Check if target directory already exist and prompt the user to confirm that they want to overwrite the files in it - if _, err = os.Stat(workshopDir); err == nil { - ok := o.Overwrite - if !o.Overwrite { - ok = utils.YesNoPrompt(fmt.Sprintf("the directory %q already exists. All files will be overwritten. Do you want to use it?", workshopDir), true) - } - if !ok { - return errors.Errorf("operation cancelled") - } - } - - } - - name := o.Name - - if name == "" { - name = filepath.Base(workshopDir) - } - - if match, _ := regexp.MatchString("^[a-z0-9-]+$", name); !match { - return errors.Errorf("invalid workshop name %q. It can only contain lowercase letters, numbers, and hyphens", name) - } - - parameters := map[string]string{ - "WorkshopName": name, - "WorkshopTitle": o.Title, - "WorkshopDescription": o.Description, - "WorkshopImage": o.Image, - "WithKubernetesAccess": strconv.FormatBool(o.WithKubernetesAccess), - "WithVirtualCluster": strconv.FormatBool(o.WithVirtualCluster), - "WithDockerDaemon": strconv.FormatBool(o.WithDockerDaemon), - "WithImageRegistry": strconv.FormatBool(o.WithImageRegistry), - "WithKubernetesConsole": strconv.FormatBool(o.WithKubernetesConsole), - "WithEditor": strconv.FormatBool(o.WithEditor), - "WithTerminal": strconv.FormatBool(o.WithTerminal), - } - - template := templates.InternalTemplate(o.Template) - - err = template.ApplyFiles(workshopDir, parameters) - if err != nil { - return err - } - - if o.WithGitHubAction { - template := templates.InternalTemplate("single") - err = template.ApplyGitHubAction(workshopDir, parameters) - } - - return err -} diff --git a/docker-extension/Dockerfile b/docker-extension/Dockerfile index 3fba46a6a..ace68179d 100644 --- a/docker-extension/Dockerfile +++ b/docker-extension/Dockerfile @@ -4,7 +4,7 @@ ARG TAG=latest FROM ${REPOSITORY}/${CLI_IMAGE_NAME}:${TAG} AS client-programs -FROM node:24-alpine AS client-builder +FROM node:24 AS client-builder WORKDIR /ui # cache packages in layer COPY ui/package.json /ui/package.json @@ -20,9 +20,9 @@ FROM debian:trixie-slim LABEL org.opencontainers.image.title="Educates Docker Desktop Extension" \ org.opencontainers.image.description="Spin up a local Educates Training Platform workshop" \ - org.opencontainers.image.vendor="Educates" \ + org.opencontainers.image.vendor="Educates Team" \ org.opencontainers.image.licenses="Apache-2.0" \ - com.docker.desktop.extension.api.version="0.3.4" \ + com.docker.desktop.extension.api.version="0.4.2" \ com.docker.extension.screenshots="" \ com.docker.desktop.extension.icon="https://raw.githubusercontent.com/educates/educates-training-platform/main/project-assets/educates-logo.svg" \ com.docker.extension.detailed-description="Spin up a local Educates Training Platform workshop" \ diff --git a/docker-extension/Makefile b/docker-extension/Makefile index fdcc64e6e..13a0b878a 100644 --- a/docker-extension/Makefile +++ b/docker-extension/Makefile @@ -1,6 +1,13 @@ IMAGE_REPOSITORY = localhost:5001 PACKAGE_VERSION = latest +# Create an alias for the image repository that sanitizes invalid characters +# Docker Compose project names must consist only of lowercase alphanumeric characters, +# hyphens, and underscores, and start with a letter or number +IMAGE_ALIAS := $(shell echo $(IMAGE_REPOSITORY) | tr '[:upper:]' '[:lower:]' | sed 's/:/_/g' | sed 's/[^a-z0-9_-]//g' | sed 's/^[^a-z0-9]/a&/') +IMAGE = $(IMAGE_REPOSITORY)/educates-docker-extension:$(PACKAGE_VERSION) +IMAGE_ALIAS_FULL = $(IMAGE_ALIAS)/educates-docker-extension:$(PACKAGE_VERSION) + UNAME_SYSTEM := $(shell uname -s | tr '[:upper:]' '[:lower:]') UNAME_MACHINE := $(shell uname -m) @@ -42,26 +49,36 @@ DEV_UI_SOURCE?=http://localhost:3000 build-extension: setup-buildx ## Build service image to be deployed as a desktop extension docker build --progress plain --platform $(MULTIARCH_PLATFORMS) \ $(if $(DOCKER_BUILDER),$(DOCKER_BUILDER)) \ - -t $(IMAGE_REPOSITORY)/educates-docker-extension:$(PACKAGE_VERSION) \ + -t $(IMAGE) \ . -install-extension: build-extension ## Install the extension - docker extension install --force $(IMAGE_REPOSITORY)/educates-docker-extension:$(PACKAGE_VERSION) +# Internal target to ensure alias tag exists when repository has invalid characters +.PHONY: _ensure-alias-tag +_ensure-alias-tag: build-extension + @if [ "$(IMAGE)" != "$(IMAGE_ALIAS_FULL)" ]; then \ + if ! docker image inspect $(IMAGE_ALIAS_FULL) >/dev/null 2>&1; then \ + echo "Tagging image with alias: $(IMAGE_ALIAS_FULL)"; \ + docker tag $(IMAGE) $(IMAGE_ALIAS_FULL) || true; \ + fi; \ + fi + +install-extension: _ensure-alias-tag ## Install the extension + docker extension install --force $(IMAGE_ALIAS_FULL) -update-extension: build-extension ## Update the extension - docker extension update --force $(IMAGE_REPOSITORY)/educates-docker-extension:$(PACKAGE_VERSION) +update-extension: _ensure-alias-tag ## Update the extension + docker extension update --force $(IMAGE_ALIAS_FULL) setup-buildx: ## Create buildx builder for multi-arch build, if not exists docker buildx create --name $(BUILDX_BUILDER) --driver docker-container --driver-opt default-load=true --driver-opt network=host --use || true docker buildx inspect $(BUILDX_BUILDER) --bootstrap .PHONY: debug -debug: ## Enable debug in the extension - docker extension dev debug $(IMAGE_REPOSITORY)/educates-docker-extension:$(PACKAGE_VERSION) +debug: _ensure-alias-tag ## Enable debug in the extension + docker extension dev debug $(IMAGE_ALIAS_FULL) .PHONY: source -source: ## Replace the UI source of the extension - docker extension dev ui-source $(IMAGE_REPOSITORY)/educates-docker-extension:$(PACKAGE_VERSION) $(DEV_UI_SOURCE) +source: _ensure-alias-tag ## Replace the UI source of the extension + docker extension dev ui-source $(IMAGE_ALIAS_FULL) $(DEV_UI_SOURCE) .PHONY: dev-enable dev-enable: source debug diff --git a/docker-extension/ui/package-lock.json b/docker-extension/ui/package-lock.json index fdf371a62..d1c015d06 100644 --- a/docker-extension/ui/package-lock.json +++ b/docker-extension/ui/package-lock.json @@ -9,17 +9,16 @@ "version": "0.1.0", "dependencies": { "@docker/docker-mui-theme": "<0.1.0", - "@docker/extension-api-client": "0.3.4", - "@emotion/react": "^11.10.4", - "@emotion/styled": "^11.10.4", - "@mui/icons-material": "^5.14.12", - "@mui/lab": "^5.0.0-alpha.147", - "@mui/material": "^5.14.12", + "@docker/extension-api-client": "0.4.2", + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.1", + "@mui/icons-material": "6.5.0", + "@mui/material": "6.5.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { - "@docker/extension-api-client-types": "0.3.4", + "@docker/extension-api-client-types": "0.4.2", "@types/jest": "^29.5.5", "@types/node": "^20.8.4", "@types/react": "^18.2.27", @@ -30,37 +29,24 @@ "vite": "^6.4.1" } }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "dev": true, "license": "MIT", "engines": { @@ -68,22 +54,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", - "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.9", - "@babel/types": "^7.26.9", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -102,18 +88,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -121,14 +108,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -137,29 +124,38 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -169,9 +165,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -179,27 +175,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -207,26 +203,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -240,6 +236,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -252,6 +249,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -264,6 +262,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -271,11 +270,44 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -288,6 +320,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -296,12 +329,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -315,6 +349,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -327,6 +362,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -339,6 +375,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -351,6 +388,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -363,6 +401,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -375,6 +414,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -382,11 +422,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -398,12 +455,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -413,13 +471,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", - "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -429,13 +487,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", - "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -445,57 +503,54 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", - "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", - "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -505,41 +560,46 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@docker/docker-mui-theme": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@docker/docker-mui-theme/-/docker-mui-theme-0.0.11.tgz", - "integrity": "sha512-3A1axAPkmj9VPHqJYibojfron4cGl+jRMrHlzBqqRvvnH9jMGhv5lH4BPkXWQ2QPEWeqZ/yt7M9mN2ZZksBUwQ==", + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@docker/docker-mui-theme/-/docker-mui-theme-0.0.13.tgz", + "integrity": "sha512-ydWCcaSWwfpq/tfqiWT3DewGGt3xyR7mgTuhEc0QIlE+g7l1iDgpXU+Kso6nZDN7YhVsD4HS28P3NXpLygwzWg==", + "license": "Apache-2.0", "peerDependencies": { - "@mui/material": "^5.0.0", + "@mui/material": ">=5.x.x <=6.x.x", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" } }, "node_modules/@docker/extension-api-client": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@docker/extension-api-client/-/extension-api-client-0.3.4.tgz", - "integrity": "sha512-bPfMyIy/mf1ir8lhc7qeVWEV0VF/JZShr5UjFcDxG79JW3umCOGU4c17EtYqwk8EY1kF3exWM6G1ITHOETUmGw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@docker/extension-api-client/-/extension-api-client-0.4.2.tgz", + "integrity": "sha512-8iT3Wy1qqqTYgrI2YC/ShFC3D3oFbKVptgeh2VQ0hy+Ywb5pwD3Qi5Qr+L5lYakw5LUqMQSjQa3p7J9OxLF+0Q==", + "license": "Apache-2.0", "dependencies": { - "@docker/extension-api-client-types": "^0.3.4" + "@docker/extension-api-client-types": "^0.4.2" } }, "node_modules/@docker/extension-api-client-types": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@docker/extension-api-client-types/-/extension-api-client-types-0.3.4.tgz", - "integrity": "sha512-cDdD+dNSE0XCvQiw0R4j9aHpK+p6E7vi+z7RbKXfxwuQpfEMoeNCKFlp4W7K3XT78iWmoPz3DxQtZEAe4VJ1oQ==" + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@docker/extension-api-client-types/-/extension-api-client-types-0.4.2.tgz", + "integrity": "sha512-nsIFZ67B09q9rY0vOnWSTIM8+wcrxjVtKvDxu66WrEOiBQhrObPRUmyXDQuTyR+vSLlU0nunKexLLp2ge+d5vA==", + "license": "Apache-2.0" }, "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", @@ -549,47 +609,52 @@ } }, "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", - "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.1" + "@emotion/memoize": "^0.9.0" } }, "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" }, "node_modules/@emotion/react": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", - "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -602,33 +667,36 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" }, "node_modules/@emotion/styled": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", - "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.1", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -641,32 +709,36 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -681,9 +753,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -698,9 +770,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -715,9 +787,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -732,9 +804,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -749,9 +821,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -766,9 +838,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -783,9 +855,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -800,9 +872,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -817,9 +889,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -834,9 +906,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -851,9 +923,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -868,9 +940,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -885,9 +957,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -902,9 +974,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -919,9 +991,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -936,9 +1008,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -953,9 +1025,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -970,9 +1042,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -987,9 +1059,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -1004,9 +1076,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -1020,10 +1092,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -1038,9 +1127,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -1055,9 +1144,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -1072,9 +1161,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -1088,45 +1177,12 @@ "node": ">=18" } }, - "node_modules/@floating-ui/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", - "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", - "dependencies": { - "@floating-ui/utils": "^0.1.3" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", - "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", - "dependencies": { - "@floating-ui/dom": "^1.5.1" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", - "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -1143,6 +1199,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1152,6 +1209,7 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1161,6 +1219,7 @@ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -1173,81 +1232,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/core": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -1290,81 +1280,12 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -1380,6 +1301,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -1393,6 +1315,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, + "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3" }, @@ -1405,6 +1328,7 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -1422,6 +1346,7 @@ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -1437,6 +1362,7 @@ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -1475,81 +1401,12 @@ } } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -1562,6 +1419,7 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -1576,6 +1434,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -1591,6 +1450,7 @@ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -1606,6 +1466,7 @@ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -1627,87 +1488,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/@jest/transform/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -1720,180 +1513,81 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.18", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.18.tgz", - "integrity": "sha512-e9ZCy/ndhyt5MTshAS3qAUy/40UiO0jX+kAo6a+XirrPJE+rrQW+mKPSI0uyp+5z4Vh+z0pvNoJ2S2gSrNz3BQ==", - "dependencies": { - "@babel/runtime": "^7.23.1", - "@floating-ui/react-dom": "^2.0.2", - "@mui/types": "^7.2.5", - "@mui/utils": "^5.14.12", - "@popperjs/core": "^2.11.8", - "clsx": "^2.0.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.12", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.12.tgz", - "integrity": "sha512-WZhCkKqhrXaSVBzoC6LNcVkIawS000OOt7gmnp4g9HhyvN0PSclRXc/JrkC7EwfzUAZJh+hiK2LaVsbtOpNuOg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", + "integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==", + "license": "MIT", "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.14.12", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.12.tgz", - "integrity": "sha512-aFm6g/AIB3RQN9h/4MKoBoBybLZXeR3aDHWNx6KzemEpIlElUxv5uXRX5Qk1VC6v/YPkhbaPsiLLjsRSTiZF3w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.5.0.tgz", + "integrity": "sha512-VPuPqXqbBPlcVSA0BmnoE4knW4/xG6Thazo8vCLWkOKusko6DtwFV6B665MMWJ9j0KFohTIf3yx2zYtYacvG1g==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.1" + "@babel/runtime": "^7.26.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@mui/material": "^6.5.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1901,34 +1595,39 @@ } } }, - "node_modules/@mui/lab": { - "version": "5.0.0-alpha.147", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.147.tgz", - "integrity": "sha512-AZjDEl31/co9baYrOBHMlXI8BCrV9JTCGDE2+IswVm32HNPYL5V2gHL3wKqn+MIFeCEg+z2es+8CU/Bau0JNSQ==", + "node_modules/@mui/material": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz", + "integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.1", - "@mui/base": "5.0.0-beta.18", - "@mui/system": "^5.14.12", - "@mui/types": "^7.2.5", - "@mui/utils": "^5.14.12", - "@mui/x-tree-view": "6.0.0-alpha.1", - "clsx": "^2.0.0", - "prop-types": "^15.8.1" + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.5.0", + "@mui/system": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@mui/material-pigment-css": "^6.5.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1937,74 +1636,48 @@ "@emotion/styled": { "optional": true }, + "@mui/material-pigment-css": { + "optional": true + }, "@types/react": { "optional": true } } }, - "node_modules/@mui/material": { - "version": "5.14.12", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.12.tgz", - "integrity": "sha512-EelF2L46VcVqhg3KjzIGBBpOtcBgRh0MMy9Efuk6Do81QdcZsFC9RebCVAflo5jIdbHiBmxBs5/l5Q9NjONozg==", - "dependencies": { - "@babel/runtime": "^7.23.1", - "@mui/base": "5.0.0-beta.18", - "@mui/core-downloads-tracker": "^5.14.12", - "@mui/system": "^5.14.12", - "@mui/types": "^7.2.5", - "@mui/utils": "^5.14.12", - "@types/react-transition-group": "^4.4.6", - "clsx": "^2.0.0", - "csstype": "^3.1.2", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, + "node_modules/@mui/material/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, "@types/react": { "optional": true } } }, "node_modules/@mui/private-theming": { - "version": "5.14.12", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.12.tgz", - "integrity": "sha512-TWwm+9+BgHFpoR3w04FG+IqID4ALa74A27RuKq2CEaWgxliBZB24EVeI6djfjFt5t4FYmIb8BMw2ZJEir7YjLQ==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", + "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.1", - "@mui/utils": "^5.14.12", + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.9", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -2013,26 +1686,29 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.14.12", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.12.tgz", - "integrity": "sha512-bocxt1nDmXfB3gpLfCCmFCyJ7sVmscFs+PuheO210QagZwHVp47UIRT1AiswLDYSQo1ZqmVGn7KLEJEYK0d4Xw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz", + "integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.1", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -2044,31 +1720,32 @@ } }, "node_modules/@mui/system": { - "version": "5.14.12", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.12.tgz", - "integrity": "sha512-6DXfjjLhW0/ia5qU3Crke7j+MnfDbMBOHlLIrqbrEqNs0AuSBv8pXniEGb+kqO0H804NJreRTEJRjCngwOX5CA==", - "dependencies": { - "@babel/runtime": "^7.23.1", - "@mui/private-theming": "^5.14.12", - "@mui/styled-engine": "^5.14.12", - "@mui/types": "^7.2.5", - "@mui/utils": "^5.14.12", - "clsx": "^2.0.0", - "csstype": "^3.1.2", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz", + "integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.9", + "@mui/styled-engine": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "clsx": "^2.1.1", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -2082,12 +1759,13 @@ } } }, - "node_modules/@mui/types": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.5.tgz", - "integrity": "sha512-S2BwfNczr7VwS6ki8GoAXJyARoeSJDLuxOEPs3vEMyTALlf9PrdHv+sluX7kk3iKrCg/ML2mIWwapZvWbkMCQA==", + "node_modules/@mui/system/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -2096,25 +1774,28 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.12", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.12.tgz", - "integrity": "sha512-RFNXnhKQlzIkIUig6mmv0r5VbtjPdWoaBPYicq25LETdZux59HAqoRdWw15T7lp3c7gXOoE8y67+hTB8C64m2g==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.1", - "@types/prop-types": "^15.7.7", + "@babel/runtime": "^7.26.0", + "@mui/types": "~7.2.24", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "react-is": "^19.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -2122,48 +1803,41 @@ } } }, - "node_modules/@mui/x-tree-view": { - "version": "6.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.0.0-alpha.1.tgz", - "integrity": "sha512-JUG3HmBrmGEALbCFg1b+i7h726e1dWYZs4db3syO1j+Q++E3nbvE4Lehp5yGTFm+8esH0Tny50tuJaa4WX6VSA==", - "dependencies": { - "@babel/runtime": "^7.22.6", - "@mui/utils": "^5.14.3", - "@types/react-transition-group": "^4.4.6", - "clsx": "^2.0.0", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, + "node_modules/@mui/utils/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/base": "^5.0.0-alpha.87", - "@mui/material": "^5.8.6", - "@mui/system": "^5.8.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", - "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", + "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", "cpu": [ "arm" ], @@ -2175,9 +1849,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", - "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", + "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", "cpu": [ "arm64" ], @@ -2189,9 +1863,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", - "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", + "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", "cpu": [ "arm64" ], @@ -2203,9 +1877,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", - "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", + "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", "cpu": [ "x64" ], @@ -2217,9 +1891,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", - "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", + "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", "cpu": [ "arm64" ], @@ -2231,9 +1905,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", - "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", + "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", "cpu": [ "x64" ], @@ -2245,9 +1919,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", - "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", + "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", "cpu": [ "arm" ], @@ -2259,9 +1933,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", - "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", + "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", "cpu": [ "arm" ], @@ -2273,9 +1947,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", - "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", + "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", "cpu": [ "arm64" ], @@ -2287,9 +1961,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", - "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", + "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", "cpu": [ "arm64" ], @@ -2300,10 +1974,24 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", - "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", + "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", + "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", "cpu": [ "loong64" ], @@ -2314,10 +2002,24 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", - "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", + "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", + "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", "cpu": [ "ppc64" ], @@ -2329,9 +2031,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", - "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", + "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", + "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", "cpu": [ "riscv64" ], @@ -2343,9 +2059,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", - "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", + "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", "cpu": [ "s390x" ], @@ -2357,9 +2073,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", - "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", + "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", "cpu": [ "x64" ], @@ -2371,9 +2087,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", - "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", + "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", "cpu": [ "x64" ], @@ -2384,10 +2100,38 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", + "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", + "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", - "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", + "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", "cpu": [ "arm64" ], @@ -2399,9 +2143,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", - "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", + "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", "cpu": [ "ia32" ], @@ -2412,10 +2156,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", + "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", - "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", + "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", "cpu": [ "x64" ], @@ -2430,13 +2188,15 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } @@ -2446,6 +2206,7 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -2465,173 +2226,185 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "29.5.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz", - "integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==", + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "node_modules/@types/node": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", - "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~6.21.0" } }, "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.8", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", - "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==" + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" }, "node_modules/@types/react": { - "version": "18.2.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.27.tgz", - "integrity": "sha512-Wfv7B7FZiR2r3MIqbAlXoY1+tXm4bOqfz4oRr+nyXdBqapDBZ0l/IGcSlAfvxIHEEJjkPU0MYAc/BlFPOcrgLw==", + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "license": "MIT", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.12.tgz", - "integrity": "sha512-QWZuiA/7J/hPIGocXreCRbx7wyoeet9ooxfbSA+zbIWqyQEE7GMtRn4A37BdYyksnN+/NDnWgfxZH9UVGDw1hg==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, - "dependencies": { - "@types/react": "*" + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", - "dependencies": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" - }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", - "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.0", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/ansi-escapes": { @@ -2639,6 +2412,7 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -2654,8 +2428,25 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -2663,6 +2454,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2671,11 +2463,25 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -2685,6 +2491,7 @@ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -2701,81 +2508,12 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -2792,6 +2530,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -2808,6 +2547,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -2822,6 +2562,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -2833,26 +2574,30 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -2860,6 +2605,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, + "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -2875,7 +2621,18 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", + "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } }, "node_modules/brace-expansion": { "version": "1.1.12", @@ -2893,6 +2650,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -2901,9 +2659,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2921,10 +2679,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -2938,6 +2697,7 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } @@ -2946,12 +2706,14 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -2961,14 +2723,15 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001703", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz", - "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -2986,19 +2749,37 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -3006,21 +2787,24 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -3031,9 +2815,10 @@ } }, "node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -3043,32 +2828,57 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, + "license": "MIT", "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -3094,6 +2904,7 @@ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -3110,76 +2921,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/create-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/create-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/create-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/create-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/create-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3196,16 +2937,18 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3217,10 +2960,11 @@ } }, "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, + "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -3235,6 +2979,7 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3244,6 +2989,7 @@ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3253,6 +2999,7 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -3261,15 +3008,16 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "node_modules/electron-to-chromium": { - "version": "1.5.114", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz", - "integrity": "sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA==", + "version": "1.5.279", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", + "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", "dev": true, "license": "ISC" }, @@ -3278,6 +3026,7 @@ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3289,20 +3038,22 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3313,31 +3064,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -3354,6 +3106,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3366,6 +3119,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -3379,6 +3133,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -3411,6 +3166,7 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -3426,22 +3182,43 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3452,13 +3229,15 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3471,7 +3250,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -3479,6 +3259,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3488,15 +3269,20 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -3506,6 +3292,7 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -3515,6 +3302,7 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -3524,6 +3312,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -3535,7 +3324,9 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3551,36 +3342,40 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" } }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" } @@ -3588,27 +3383,31 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3621,10 +3420,11 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, + "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -3644,6 +3444,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -3652,7 +3453,9 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3662,19 +3465,25 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3685,6 +3494,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3694,6 +3504,7 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3703,6 +3514,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -3712,6 +3524,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -3723,26 +3536,29 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, @@ -3750,26 +3566,12 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -3777,17 +3579,12 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -3797,32 +3594,12 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -3837,15 +3614,17 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -3859,6 +3638,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -3885,6 +3665,7 @@ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -3899,6 +3680,7 @@ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -3925,81 +3707,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -4028,81 +3741,12 @@ } } }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-config": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -4143,84 +3787,113 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/jest-diff": { + "node_modules/jest-leak-detector": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" }, @@ -4228,980 +3901,262 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": ">=6" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each": { + "node_modules/jest-resolve": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/jest-environment-node": { + "node_modules/jest-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-validate": { @@ -5209,6 +4164,7 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -5221,175 +4177,37 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker": { @@ -5397,6 +4215,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -5407,20 +4226,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -5434,7 +4245,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/js-yaml": { "version": "3.14.2", @@ -5465,13 +4277,15 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -5484,6 +4298,7 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5493,6 +4308,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5500,13 +4316,15 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -5518,6 +4336,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -5540,6 +4359,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -5550,26 +4370,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-dir/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5577,17 +4383,12 @@ "node": ">=10" } }, - "node_modules/make-dir/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } @@ -5596,7 +4397,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/micromatch": { "version": "4.0.8", @@ -5612,11 +4414,25 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5626,6 +4442,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5634,14 +4451,15 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", - "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -5661,18 +4479,20 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -5681,6 +4501,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5690,6 +4511,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -5701,6 +4523,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5710,6 +4533,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -5719,6 +4543,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -5734,6 +4559,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -5749,6 +4575,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -5761,6 +4588,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -5776,6 +4604,7 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5784,6 +4613,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -5795,6 +4625,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -5813,6 +4644,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5822,6 +4654,7 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5831,6 +4664,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5838,12 +4672,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", "engines": { "node": ">=8" } @@ -5855,22 +4691,24 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -5880,6 +4718,7 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -5888,9 +4727,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -5908,7 +4747,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5921,6 +4760,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -5935,6 +4775,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5942,11 +4783,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "license": "MIT", "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -5959,6 +4808,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -5968,12 +4818,13 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -5984,12 +4835,14 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -5998,26 +4851,28 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", "engines": { @@ -6028,6 +4883,7 @@ "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -6039,32 +4895,32 @@ "react-dom": ">=16.6.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6074,6 +4930,7 @@ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, + "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, @@ -6086,6 +4943,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6094,27 +4952,29 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/rollup": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", - "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", + "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -6124,32 +4984,39 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.35.0", - "@rollup/rollup-android-arm64": "4.35.0", - "@rollup/rollup-darwin-arm64": "4.35.0", - "@rollup/rollup-darwin-x64": "4.35.0", - "@rollup/rollup-freebsd-arm64": "4.35.0", - "@rollup/rollup-freebsd-x64": "4.35.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", - "@rollup/rollup-linux-arm-musleabihf": "4.35.0", - "@rollup/rollup-linux-arm64-gnu": "4.35.0", - "@rollup/rollup-linux-arm64-musl": "4.35.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", - "@rollup/rollup-linux-riscv64-gnu": "4.35.0", - "@rollup/rollup-linux-s390x-gnu": "4.35.0", - "@rollup/rollup-linux-x64-gnu": "4.35.0", - "@rollup/rollup-linux-x64-musl": "4.35.0", - "@rollup/rollup-win32-arm64-msvc": "4.35.0", - "@rollup/rollup-win32-ia32-msvc": "4.35.0", - "@rollup/rollup-win32-x64-msvc": "4.35.0", + "@rollup/rollup-android-arm-eabi": "4.57.0", + "@rollup/rollup-android-arm64": "4.57.0", + "@rollup/rollup-darwin-arm64": "4.57.0", + "@rollup/rollup-darwin-x64": "4.57.0", + "@rollup/rollup-freebsd-arm64": "4.57.0", + "@rollup/rollup-freebsd-x64": "4.57.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", + "@rollup/rollup-linux-arm-musleabihf": "4.57.0", + "@rollup/rollup-linux-arm64-gnu": "4.57.0", + "@rollup/rollup-linux-arm64-musl": "4.57.0", + "@rollup/rollup-linux-loong64-gnu": "4.57.0", + "@rollup/rollup-linux-loong64-musl": "4.57.0", + "@rollup/rollup-linux-ppc64-gnu": "4.57.0", + "@rollup/rollup-linux-ppc64-musl": "4.57.0", + "@rollup/rollup-linux-riscv64-gnu": "4.57.0", + "@rollup/rollup-linux-riscv64-musl": "4.57.0", + "@rollup/rollup-linux-s390x-gnu": "4.57.0", + "@rollup/rollup-linux-x64-gnu": "4.57.0", + "@rollup/rollup-linux-x64-musl": "4.57.0", + "@rollup/rollup-openbsd-x64": "4.57.0", + "@rollup/rollup-openharmony-arm64": "4.57.0", + "@rollup/rollup-win32-arm64-msvc": "4.57.0", + "@rollup/rollup-win32-ia32-msvc": "4.57.0", + "@rollup/rollup-win32-x64-gnu": "4.57.0", + "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } @@ -6159,6 +5026,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -6168,6 +5036,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6180,6 +5049,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6188,19 +5058,22 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6209,6 +5082,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -6228,6 +5102,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -6238,6 +5113,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -6246,13 +5122,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -6265,6 +5143,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6274,6 +5153,7 @@ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -6287,6 +5167,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6301,6 +5182,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6313,6 +5195,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6322,6 +5205,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -6331,6 +5215,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -6341,12 +5226,27 @@ "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6359,6 +5259,7 @@ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -6369,14 +5270,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -6385,45 +5286,19 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -6436,6 +5311,7 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -6445,6 +5321,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -6453,10 +5330,11 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6466,15 +5344,16 @@ } }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", - "dev": true + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -6503,10 +5382,11 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -6520,7 +5400,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/vite": { "version": "6.4.1", @@ -6597,39 +5478,12 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } @@ -6639,6 +5493,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -6654,6 +5509,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6666,50 +5522,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -6723,6 +5548,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -6734,26 +5560,12 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -6772,6 +5584,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -6781,6 +5594,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/docker-extension/ui/package.json b/docker-extension/ui/package.json index 087eda3d9..0e20fc5ad 100644 --- a/docker-extension/ui/package.json +++ b/docker-extension/ui/package.json @@ -5,14 +5,13 @@ "type": "module", "dependencies": { "@docker/docker-mui-theme": "<0.1.0", - "@docker/extension-api-client": "0.3.4", - "@emotion/react": "^11.10.4", - "@emotion/styled": "^11.10.4", - "@mui/icons-material": "^5.14.12", - "@mui/lab": "^5.0.0-alpha.147", - "@mui/material": "^5.14.12", + "@docker/extension-api-client": "0.4.2", + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.1", + "@mui/material": "6.5.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "@mui/icons-material": "6.5.0" }, "scripts": { "dev": "vite", @@ -20,7 +19,7 @@ "test": "jest src" }, "devDependencies": { - "@docker/extension-api-client-types": "0.3.4", + "@docker/extension-api-client-types": "0.4.2", "@types/jest": "^29.5.5", "@types/node": "^20.8.4", "@types/react": "^18.2.27", diff --git a/docker-extension/ui/src/common/types.ts b/docker-extension/ui/src/common/types.ts index 664817a0a..e990bec56 100644 --- a/docker-extension/ui/src/common/types.ts +++ b/docker-extension/ui/src/common/types.ts @@ -25,3 +25,16 @@ export const NullWorkshop: Workshop = { source: "", status: "", }; + +// API response types +export type ListResponse = Workshop[]; + +export interface DeployResponse { + ok: boolean; + message?: string; +} + +export interface DeleteResponse { + ok: boolean; + message?: string; +} diff --git a/docker-extension/ui/src/components/WorkshopsTable/WorkshopsTable.tsx b/docker-extension/ui/src/components/WorkshopsTable/WorkshopsTable.tsx index b52f3f61c..a6e122c20 100644 --- a/docker-extension/ui/src/components/WorkshopsTable/WorkshopsTable.tsx +++ b/docker-extension/ui/src/components/WorkshopsTable/WorkshopsTable.tsx @@ -96,7 +96,12 @@ export default function WorkshopsTable({ rows, onStop, showPort }: WorkshopsTabl {showPort && ( - {row.url.split(":")[2]} + + {(() => { + const segments = row.url.split(":"); + return segments.length > 2 && segments[2] ? segments[2] : ""; + })()} + )} {row.source} diff --git a/docker-extension/ui/src/main.tsx b/docker-extension/ui/src/main.tsx index c730e6d27..d39b4cce3 100644 --- a/docker-extension/ui/src/main.tsx +++ b/docker-extension/ui/src/main.tsx @@ -1,15 +1,15 @@ import React from "react"; import ReactDOM from "react-dom/client"; import CssBaseline from "@mui/material/CssBaseline"; -import { DockerMuiThemeProvider } from "@docker/docker-mui-theme"; +import { DockerMuiV6ThemeProvider } from "@docker/docker-mui-theme"; import { App } from "./views/App"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + - + ); diff --git a/docker-extension/ui/src/views/App.tsx b/docker-extension/ui/src/views/App.tsx index 473f1f80f..df35278e0 100644 --- a/docker-extension/ui/src/views/App.tsx +++ b/docker-extension/ui/src/views/App.tsx @@ -4,10 +4,10 @@ import { Box, Grid, Link, TextField, Typography } from "@mui/material"; import BottomIntroPane from "../components/BottomIntroPane/BottomIntroPane"; import WorkshopsTable from "../components/WorkshopsTable/WorkshopsTable"; import { handleGoTo } from "../common/goto"; -import { Workshop } from "../common/types"; +import { Workshop, ListResponse, DeployResponse, DeleteResponse } from "../common/types"; import { isValidURL } from "../common/validations"; import OptionsPane from "../components/OptionsPane/OptionsPane"; -import { LoadingButton } from "@mui/lab"; +import Button from "@mui/material/Button"; const sampleWorkshopURL = "https://github.com/educates/lab-container-basics/releases/latest/download/workshop.yaml"; @@ -63,24 +63,20 @@ export function App() { }; useEffect(() => { - let interval: any = null; - if (interval != undefined) clearInterval(interval); - if (interval == undefined) { - interval = setInterval(() => { - list(); - }, 3000); - return () => clearInterval(interval); - } + const interval: ReturnType = setInterval(() => { + list(); + }, 3000); + return () => clearInterval(interval); }, []); const list = async () => { console.log("list"); ddClient.extension.vm?.service ?.get("/workshop/list") - .then((result: any) => { - setWorkshops(result); + .then((result: unknown) => { + setWorkshops(result as ListResponse); }) - .catch((err: any) => { + .catch((err: unknown) => { console.log(err); }); }; @@ -91,12 +87,13 @@ export function App() { setQueryingBackend(true); ddClient.extension.vm?.service ?.get("/workshop/deploy?url=" + encodeURIComponent(url) + "&port=" + port) - .then((result: any) => { + .then((result: unknown) => { + const _ = result as DeployResponse; setQueryingBackend(false); setUrl(""); list(); }) - .catch((err: any) => { + .catch((err: unknown) => { console.log(err); setQueryingBackend(false); }); @@ -109,10 +106,11 @@ export function App() { console.log("stop: " + name); ddClient.extension.vm?.service ?.get("/workshop/delete?name=" + name) - .then((result: any) => { + .then((result: unknown) => { + const _ = result as DeleteResponse; list(); }) - .catch((err: any) => { + .catch((err: unknown) => { console.log(err); }); }; @@ -173,9 +171,9 @@ export function App() { - + diff --git a/docker-extension/ui/tsconfig.json b/docker-extension/ui/tsconfig.json index 3d0a51a86..31d882746 100644 --- a/docker-extension/ui/tsconfig.json +++ b/docker-extension/ui/tsconfig.json @@ -8,6 +8,11 @@ "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node", diff --git a/go.work b/go.work index b095f6393..2af8d684d 100644 --- a/go.work +++ b/go.work @@ -1,3 +1,3 @@ -go 1.24.10 +go 1.25.6 use ./client-programs/ diff --git a/go.work.sum b/go.work.sum index c85627d58..1b0134b4f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -5,6 +5,8 @@ carvel.dev/ytt v0.47.0/go.mod h1:Xarf0th61vX6VY07l3KBSi3uaMCQ2UyPPiCPiaVpHME= cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= @@ -72,6 +74,8 @@ cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//u cloud.google.com/go/compute v1.27.4 h1:XM8ulx6crjdl09XBfji7viFgZOEQuIxBwKmjRH9Rtmc= cloud.google.com/go/compute v1.27.4/go.mod h1:7JZS+h21ERAGHOy5qb7+EPyXlQwzshzrx1x6L9JhTqU= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/contactcenterinsights v1.11.1/go.mod h1:FeNP3Kg8iteKM80lMwSk3zZZKVxr+PGnAId6soKuXwE= cloud.google.com/go/container v1.26.1/go.mod h1:5smONjPRUxeEpDG7bMKWfDL4sauswqEtnBK1/KKpR04= cloud.google.com/go/containeranalysis v0.11.1/go.mod h1:rYlUOM7nem1OJMKwE1SadufX0JP3wnXj844EtZAwWLY= @@ -171,9 +175,15 @@ cloud.google.com/go/vpcaccess v1.7.2/go.mod h1:mmg/MnRHv+3e8FJUjeSibVFvQF1cCy2Ms cloud.google.com/go/webrisk v1.9.2/go.mod h1:pY9kfDgAqxUpDBOrG4w8deLfhvJmejKB0qd/5uQIPBc= cloud.google.com/go/websecurityscanner v1.6.2/go.mod h1:7YgjuU5tun7Eg2kpKgGnDuEOXWIrh8x8lWrJT4zfmas= cloud.google.com/go/workflows v1.12.1/go.mod h1:5A95OhD/edtOhQd/O741NSfIMezNTbCwLM1P1tBRGHM= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v55.0.0+incompatible h1:L4/vUGbg1Xkw5L20LZD+hJI5I+ibWSytqQ68lTCfLwY= github.com/Azure/azure-sdk-for-go v55.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0/go.mod h1:PXe2h+LKcWTX9afWdZoHyODqR4fBa5boUM/8uJfZ0Jo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest/autorest v0.11.6/go.mod h1:V6p3pKZx1KKkJubbxnDWrzNhEIfOy/pTGasLqzHIPHs= @@ -189,21 +199,28 @@ github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW github.com/Azure/go-autorest/autorest/azure/cli v0.4.1 h1:jwcD1wURu0+hKceV04MubZmKLzwEYOCz6q4aOtVZ+Ng= github.com/Azure/go-autorest/autorest/azure/cli v0.4.1/go.mod h1:JfDgiIO1/RPu6z42AdQTyjOoCM2MFhLqSBDvMEkDgcg= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b/go.mod h1:FNj4KYEAAHfYu68kRYolGoxkaJn+6mdEsaM12VTwuI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.25/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hnslib v0.0.8/go.mod h1:EYveQJlhKh2obmEIRB3uKN6dBd9pj1frPsrTGFppKuk= github.com/Microsoft/hnslib v0.1.1/go.mod h1:DRQR4IjLae6WHYVhW7uqe44hmFUiNhmaWA+jwMbz5tM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= @@ -213,6 +230,8 @@ github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3 github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= 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/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -220,6 +239,7 @@ github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8q github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg= github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA= github.com/aws/aws-sdk-go-v2/config v1.29.6 h1:fqgqEKK5HaZVWLQoLiC9Q+xDlSp+1LYidp6ybGE2OGg= github.com/aws/aws-sdk-go-v2/config v1.29.6/go.mod h1:Ft+WLODzDQmCTHDvqAH1JfC2xxbZ0MxpZAcJqmE1LTQ= @@ -229,6 +249,7 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.59/go.mod h1:NM8fM6ovI3zak23UISdW github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0/go.mod h1:2LAuqPx1I6jNfaGDucWfA2zqQCYCOMCDHiCOciALyNw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 h1:KwsodFKVQTlI5EyhRSugALzsV6mG/SGrdjlMXSZSdso= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28/go.mod h1:EY3APf9MzygVhKuPXAc5H+MkGb8k/DOSQjWS0LgkKqI= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4= @@ -236,6 +257,7 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKd github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4/go.mod h1:SPBBhkJxjcrzJBc+qY85e83MQ2q3qdra8fghhkkyrJg= github.com/aws/aws-sdk-go-v2/service/ecr v1.4.1/go.mod h1:FglZcyeiBqcbvyinl+n14aT/EWC7S1MIH+Gan2iizt0= github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3 h1:a+210FCU/pR5hhKRaskRfX/ogcyyzFBrehcTk5DTAyU= github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3/go.mod h1:dtD3a4sjUjVL86e0NUvaqdGvds5ED6itUiZPDaT+Gh8= @@ -244,9 +266,12 @@ github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2 h1:E6/Myrj9HgLF22medmDrKm github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2/go.mod h1:OQ8NALFcchBJ/qruak6zKUQodovnTKKaReTuCkc5/9Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4/go.mod h1:b17At0o8inygF+c6FOD3rNyYZufPw62o9XJbSfQPgbo= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1/go.mod h1:zceowr5Z1Nh2WVP8bf/3ikB41IZW59E4yIYbg+pC6mw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4/go.mod h1:DnbBOv4FlIXHj2/xmrUQYtawRFC9L9ZmQPz+DBc6X5I= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1/go.mod h1:w5PC+6GHLkvMJKasYGVloB3TduOtROEMqm15HSuIbw4= github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM= github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 h1:/eE3DogBjYlvlbhd2ssWyeuovWunHLxfgw3s/OJa4GQ= github.com/aws/aws-sdk-go-v2/service/sso v1.24.15/go.mod h1:2PCJYpi7EKeA5SkStAmZlF6fi0uUABuhtF8ILHjGc3Y= @@ -264,15 +289,21 @@ github.com/bazelbuild/rules_go v0.34.0/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2 github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.2.1 h1:eetYiv8DDYOZcBADY+pRvRytf3Dlz1FhnpvL2FsClBc= github.com/bmatcuk/doublestar v1.2.1/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +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/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/checkpoint-restore/checkpointctl v1.4.0/go.mod h1:ynQ52zQBazgcTZuxpwTFzRinIcAf0haDTC1X1LA/FKA= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo= github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 h1:9Qh4lJ/KMr5iS1zfZ8I97+3MDpiKjl+0lZVUNBhdvRs= github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08/go.mod h1:MAuu1uDJNOS3T3ui0qmKdPUwm59+bO19BbTph2wZafE= @@ -280,6 +311,8 @@ github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwys github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= +github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -289,33 +322,54 @@ github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido6 github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/compose-spec/compose-go/v2 v2.9.1 h1:8UwI+ujNU+9Ffkf/YgAm/qM9/eU7Jn8nHzWG721W4rs= +github.com/compose-spec/compose-go/v2 v2.9.1/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE= +github.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU= +github.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg= github.com/container-storage-interface/spec v1.8.0/go.mod h1:ROLik+GhPslwwWRNFF1KasPzroNARibH2rfz1rkg4H0= github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0= +github.com/containerd/accelerated-container-image v1.3.0/go.mod h1:EvKVWor6ZQNUyYp0MZm5hw4k21ropuz7EegM+m/Jb/Q= +github.com/containerd/btrfs/v2 v2.0.0/go.mod h1:swkD/7j9HApWpzl8OHfrHNxppPd9l44DFZdF94BUj9k= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/fuse-overlayfs-snapshotter/v2 v2.1.6/go.mod h1:Mau9LZ7ZnyKCIgcNT7sMG5fjaZ9YCOHU5RuolUikhBQ= +github.com/containerd/go-cni v1.1.13/go.mod h1:nTieub0XDRmvCZ9VI/SBG6PyqT95N4FIhxsauF1vSBI= +github.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U= +github.com/containerd/imgcrypt/v2 v2.0.1/go.mod h1:/qIJL8nxzdzMA2n5iYyyuIY36KfoVQWmgTWdfVtyebM= +github.com/containerd/nri v0.10.0/go.mod h1:5VyvLa/4uL8FjyO8nis1UjbCutXDpngil17KvBSL6BU= +github.com/containerd/otelttrpc v0.1.0/go.mod h1:XhoA2VvaGPW1clB2ULwrBZfXVuEWuyOd2NUD1IM0yTg= +github.com/containerd/stargz-snapshotter v0.17.0 h1:djNS4KU8ztFhLdEDZ1bsfzOiYuVHT6TgSU5qwRk+cNc= +github.com/containerd/stargz-snapshotter v0.17.0/go.mod h1:ySEul1ck7jCE4jqsuFCo8FFLrHU20UWQeI9g7mdsanI= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/ttrpc v1.2.6/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.2/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/containerd/zfs/v2 v2.0.0-rc.0/go.mod h1:g36g/XCEGDRxUXIFdM3oWAEvmTvhfz/eKWElqg4Secw= +github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4= +github.com/containernetworking/plugins v1.9.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c= +github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= github.com/coredns/corefile-migration v1.0.21/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE= github.com/coredns/corefile-migration v1.0.24/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY= github.com/coredns/corefile-migration v1.0.26/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cppforlife/cobrautil v0.0.0-20221021151949-d60711905d65/go.mod h1:2w+qxVu2KSGW78Ex/XaIqfh/OvBgjEsmN53S4T8vEyA= github.com/cppforlife/go-cli-ui v0.0.0-20220425131040-94f26b16bc14/go.mod h1:AlgTssDlstr4mf92TR4DPITLfl5+7wEY4cKStCmeeto= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= @@ -327,6 +381,7 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2 github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -334,8 +389,11 @@ github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U= github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8= +github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -357,7 +415,9 @@ github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQ github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= @@ -373,12 +433,11 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= -github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getkin/kin-openapi v0.81.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= @@ -387,7 +446,9 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -402,6 +463,7 @@ github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -447,7 +509,6 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -455,6 +516,8 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +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/cadvisor v0.49.0/go.mod h1:s6Fqwb2KiWG6leCegVhw4KW40tf9f7m+SF1aXiE8Wsk= github.com/google/cadvisor v0.49.2/go.mod h1:s6Fqwb2KiWG6leCegVhw4KW40tf9f7m+SF1aXiE8Wsk= github.com/google/cadvisor v0.51.0/go.mod h1:czGE/c/P/i0QFpVNKTFrIEzord9Y10YfpwuaSWXELc0= @@ -466,6 +529,11 @@ github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= 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/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI= +github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/certtostore v1.0.6/go.mod h1:2N0ZPLkGvQWhYvXaiBGq02r71fnSLfq78VKIWQHr1wo= +github.com/google/deck v0.0.0-20230104221208-105ad94aa8ae/go.mod h1:DoDv8G58DuLNZF0KysYn0bA/6ZWhmRW3fZE2VnGEH0w= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -478,11 +546,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= +github.com/google/go-dap v0.12.0/go.mod h1:tNjCASCm5cqePi/RVXXWEVqtnNLV1KTWtYOqu6rZNzc= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -501,7 +569,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250315033105-103756e64e1d/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= @@ -525,20 +593,32 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/hanwen/go-fuse/v2 v2.8.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty-funcs v0.0.0-20250818135842-6aab67130928/go.mod h1:YC9ASYt9Z9sQEAtzCe+yaAzi3E7wcxfRphDXtwZoWC0= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/hiddeco/sshsig v0.2.0/go.mod h1:nJc98aGgiH6Yql2doqH4CTBVHexQA40Q+hMMLHP4EqE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/intel/goresctrl v0.10.0/go.mod h1:1S8GDqL46GuKb525bxNhIEEkhf4rhVcbSf9DuKhp7mw= github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/ishidawataru/sctp v0.0.0-20250521072954-ae8eb7fa7995/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -546,7 +626,6 @@ github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPG github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= -github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -556,6 +635,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k14s/semver/v4 v4.0.1-0.20210701191048-266d47ac6115/go.mod h1:mGrnmO5qnhJIaSiwMo05cvRL6Ww9ccYbTgNFcm6RHZQ= github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= @@ -565,13 +645,11 @@ github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/knqyf263/go-plugin v0.9.0/go.mod h1:2z5lCO1/pez6qGo8CvCxSlBFSEat4MEp1DrnA+f7w8Q= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= @@ -591,24 +669,29 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQth github.com/maxbrunsfeld/counterfeiter/v6 v6.7.0/go.mod h1:RVP6/F85JyxTrbJxWIdKU2vlSvK48iCMnMXRkSz7xtg= github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1/go.mod h1:eyp4DdUJAKkr9tvxR3jWhw2mDK7CWABMG5r9uyaKC7I= github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2/go.mod h1:VzB2VoMh1Y32/QqDfg9ZJYHj99oM4LiGtqPZydTiQSQ= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/ipvs v1.1.0/go.mod h1:4VJMWuf098bsUMmZEiD4Tjk/O7mOn3l1PTD3s4OoYAs= github.com/moby/moby v27.1.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/policy-helpers v0.0.0-20251105011237-bcaa71c99f14/go.mod h1:HJfK0E8dR+Jpk5anJ3oADg2dRSom1gJK17sqEiiMS7w= +github.com/moby/profiles/seccomp v0.1.0/go.mod h1:Kqk57vxH6/wuOc5bmqRiSXJ6iEz8Pvo3LQRkv0ytFWs= +github.com/moby/sys/mount v0.3.4/go.mod h1:KcQJMbQdJHPlq5lcYT+/CjatWM4PuxKe+XLSVS4J6Os= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/sys/reexec v0.1.0/go.mod h1:EqjBg8F3X7iZe5pU6nRZnYCMUTXoxsjiIfHup5wYIN8= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= @@ -624,13 +707,13 @@ github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9 github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/cgroups v0.0.1/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opencontainers/runc v1.2.1/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= github.com/opencontainers/runtime-spec v1.0.3-0.20220909204839-494a5a6aca78/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116/go.mod h1:DKDEfzxvRkoQ6n9TGhxQgg2IM1lY4aM0eaQP4e3oElw= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/openshift/build-machinery-go v0.0.0-20230824093055-6a18da01283c/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= @@ -645,11 +728,15 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= @@ -685,37 +772,42 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.0/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -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/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/tonistiigi/go-actions-cache v0.0.0-20250626083717-378c5ed1ddd9/go.mod h1:cD0SB2270BYw6HYKriFn4H6NRLhGj6ytf48YTpsm8LY= +github.com/tonistiigi/go-archvariant v1.0.0/go.mod h1:TxFmO5VS6vMq2kvs3ht04iPXtu2rUT/erOnGFYfk5Ho= +github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117/go.mod h1:3Ez1Paeg+0Ghu3KwpEGC1HgZ4CHDlg+Ez/5Baeomk54= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= +github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= +github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= @@ -728,26 +820,32 @@ github.com/vmware-tanzu/carvel-imgpkg v0.36.0 h1:ha5a3WUPaqpGlP+QRkKBA9WyT85vUPh github.com/vmware-tanzu/carvel-imgpkg v0.36.0/go.mod h1:8HeIt+froyx7iRjyZ/4py2wFMPXEFNyWUNUTQgAjD8M= github.com/vmware-tanzu/carvel-imgpkg v0.38.2/go.mod h1:v9BcO1qfXwwIQFw2zmksdUkx8eI1e+/a0Md3xG2BzDE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.8/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.8/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= go.etcd.io/etcd/client/v2 v2.305.16/go.mod h1:h9YxWCzcdvZENbfzBTFCnoNumr2ax3F19sKMqHFmXHE= go.etcd.io/etcd/client/v3 v3.5.8/go.mod h1:idZYIPVkttBJBiRigkB5EM0MmEyx8jcl18zCV3F5noc= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs= go.etcd.io/etcd/pkg/v3 v3.5.16/go.mod h1:+lutCZHG5MBBFI/U4eYT5yL7sJfnexsoM20Y0t2uNuY= @@ -763,16 +861,18 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.42.0/go.mod h1:XiglO+8SPMqM3Mqh5/rtxR1VHc63o8tb38QrU6tm4mU= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0/go.mod h1:uq8DrRaen3suIWTpdR/JNHCGpurSvMv9D5Nr5CU5TXc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= @@ -784,8 +884,9 @@ go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= @@ -796,35 +897,36 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0/go.mod h1:f3bYiqNqhoPxkvI2LrXqQVC546K7BuRDL/kKuxkujhA= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= @@ -836,11 +938,14 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -849,8 +954,8 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= @@ -880,9 +985,7 @@ golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -924,11 +1027,8 @@ golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -942,8 +1042,6 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -958,9 +1056,9 @@ golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -985,6 +1083,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -998,7 +1097,7 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1012,12 +1111,10 @@ golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= @@ -1035,11 +1132,6 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1088,6 +1180,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= @@ -1096,10 +1189,7 @@ golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= -golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= @@ -1183,11 +1273,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1: google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= -google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= @@ -1195,12 +1283,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1222,10 +1308,9 @@ google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9Y google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= 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.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= @@ -1243,10 +1328,6 @@ google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1266,6 +1347,8 @@ k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= +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.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.25.6/go.mod h1:IEp2B2/FvQ8GmdspscUoUS0iFF/GGc6NVrJ/cTM4OaA= k8s.io/apiserver v0.27.7/go.mod h1:OrLG9RwCOerutAlo8QJW5EHzUG9Dad7k6rgcDUNSO/w= @@ -1273,6 +1356,8 @@ k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak= k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.25.6/go.mod h1:aDxzxJynLKQkaa117y0FFcgZ5jG8+GobxZ2JUntmvKk= k8s.io/code-generator v0.27.7/go.mod h1:w1YF/xQcTg+d9Ag+04xuRqER+q8rDnJ70ynLql8/RLA= @@ -1282,7 +1367,12 @@ k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608y k8s.io/component-base v0.28.6 h1:G4T8VrcQ7xZou3by/fY5NU5mfxOBlWaivS2lPrEltAo= k8s.io/component-base v0.28.6/go.mod h1:Dg62OOG3ALu2P4nAG00UdsuHoNLQJ5VsUZKQlLDcS+E= k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= +k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= k8s.io/component-helpers v0.29.0/go.mod h1:j2coxVfmzTOXWSE6sta0MTgNSr572Dcx68F6DD+8fWc= +k8s.io/component-helpers v0.34.2 h1:RIUGDdU+QFzeVKLZ9f05sXTNAtJrRJ3bnbMLrogCrvM= +k8s.io/component-helpers v0.34.2/go.mod h1:pLi+GByuRTeFjjcezln8gHL7LcT6HImkwVQ3A2SQaEE= +k8s.io/cri-api v0.34.1/go.mod h1:4qVUjidMg7/Z9YGZpqIDygbkPWkg3mkS1PvOx/kpHTE= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -1309,6 +1399,7 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kubernetes v1.31.2 h1:VNSu4O7Xn5FFRsh9ePXyEPg6ucR21fOftarSdi053Gs= +k8s.io/kubernetes v1.31.7/go.mod h1:9xmT2buyTYj8TRKwRae7FcuY8k5+xlxv7VivvO0KKfs= k8s.io/metrics v0.29.0/go.mod h1:UCuTT4dC/x/x6ODSk87IWIZQnuAfcwxOjb1gjWJdjMA= k8s.io/metrics v0.30.3/go.mod h1:W06L2nXRhOwPkFYDJYWdEIS3u6JcJy3ebIPYbndRs6A= k8s.io/metrics v0.32.3/go.mod h1:9R1Wk5cb+qJpCQon9h52mgkVCcFeYxcY+YkumfwHVCU= @@ -1319,7 +1410,8 @@ k8s.io/system-validators v1.10.2/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0 k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.76/go.mod h1:7V2BQeHnVAQwhCnCPJ977giCeGDiywVewWF+8vkpPlc= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.76/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= @@ -1335,6 +1427,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0/go.mod h1:VHVDI/ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.13.1/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= sigs.k8s.io/controller-runtime v0.15.3/go.mod h1:kp4jckA4vTx281S/0Yk2LFEEQe67mjg+ev/yknv47Ds= sigs.k8s.io/controller-tools v0.7.0/go.mod h1:bpBAo0VcSDDLuWt47evLhMLPxRPxMDInTEH/YbdeMK0= @@ -1353,13 +1446,12 @@ sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRS sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 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/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +tags.cncf.io/container-device-interface/specs-go v1.1.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= diff --git a/project-docs/getting-started/local-environment.md b/project-docs/getting-started/local-environment.md index e82789917..6d3779acb 100644 --- a/project-docs/getting-started/local-environment.md +++ b/project-docs/getting-started/local-environment.md @@ -188,14 +188,14 @@ $ curl -v www.educates-local-dev.test > Host: www.educates-local-dev.test > User-Agent: curl/7.79.1 > Accept: */* -> +> * Mark bundle as not supporting multiuse < HTTP/1.1 404 Not Found < vary: Accept-Encoding < date: Fri, 25 Mar 2022 03:07:19 GMT < server: envoy < content-length: 0 -< +< * Connection #0 to host www.educates-local-dev.test left intact ``` @@ -287,10 +287,172 @@ To delete a local registry mirror, run: educates local mirror delete ghcr.io ``` -This will stop and remove the mirror container and clean up the configuration from the cluster. +This will stop and remove the mirror container and clean up the configuration from the cluster. As pointed out earlier, if the mirror configuration exists in the local cluster's configuration, if the Educates local cluster is recreated then the mirror will be recreated as well. +Multi-node clusters +------------------- + +By default, Educates creates a local Kind cluster with a single control-plane node. For testing scenarios that require multiple nodes, such as workload isolation, resource management, or simulating production-like environments, you can configure the cluster to include additional worker nodes. + +### Default behavior + +When no node configuration is provided, Educates automatically creates a cluster with: +- 1 control-plane node +- The `ingress-ready=true` label (required for ingress controller) +- Port mappings for HTTP (80) and HTTPS (443) + +### Configuring multiple nodes + +To create a multi-node cluster, add a `nodes` section to your local cluster configuration. You can do this by running `educates local config edit` and adding configuration like: + +```yaml +localKindCluster: + nodes: + - role: control-plane + labels: + environment: dev + node-type: control + - role: worker + labels: + tier: frontend + workload-type: web + - role: worker + labels: + tier: backend + workload-type: api +``` + +Or create a configuration file and use it when creating the cluster: + +``` +educates create-cluster --config multi-node-config.yaml +``` + +### Node configuration constraints + +When configuring nodes, the following constraints apply: + +- **Exactly 1 control-plane node** is required (if none is specified, one will be created automatically) +- **Maximum 5 worker nodes** are allowed +- Valid node roles are: `control-plane` or `worker` + +### Adding labels to nodes + +You can add custom labels to nodes to organize and identify them. Labels are key-value pairs that can be used with Kubernetes node selectors and affinity rules. + +```yaml +localKindCluster: + nodes: + - role: control-plane + labels: + environment: production + region: local + - role: worker + labels: + tier: frontend + disk-type: ssd + - role: worker + labels: + tier: backend + disk-type: hdd +``` + +The control-plane node will automatically have the `ingress-ready: true` label added in addition to any custom labels you specify. + +### Adding taints to nodes + +Taints allow you to mark nodes so that only pods with matching tolerations can be scheduled on them. This is useful for dedicating nodes to specific workloads. + +```yaml +localKindCluster: + nodes: + - role: control-plane + - role: worker + labels: + tier: frontend + - role: worker + labels: + tier: backend + taints: + - key: dedicated + value: backend + effect: NoSchedule + - role: worker + labels: + tier: database + taints: + - key: dedicated + value: database + effect: NoSchedule + - key: storage + value: "true" + effect: NoExecute +``` + +Valid taint effects are: +- `NoSchedule`: Pods will not be scheduled on the node unless they have a matching toleration +- `PreferNoSchedule`: Kubernetes will try to avoid scheduling pods on the node, but it's not required +- `NoExecute`: Existing pods without matching tolerations will be evicted from the node + +### Registry and mirror configuration + +When you create a multi-node cluster, the local image registry and any configured registry mirrors are automatically configured on **all nodes** (both control-plane and workers). This ensures that all nodes can pull images from the local registry and benefit from cached images in the mirrors. + +You don't need to do anything special - the registry configuration at `/etc/containerd/certs.d/` is automatically applied to every node in the cluster when the cluster is created. + +### Example: Complete multi-node configuration + +Here's a complete example showing a 4-node cluster with labels, taints, and registry mirrors: + +```yaml +localKindCluster: + nodes: + - role: control-plane + labels: + environment: dev + node-type: control + - role: worker + labels: + tier: frontend + workload-type: web + - role: worker + labels: + tier: backend + workload-type: api + taints: + - key: dedicated + value: backend + effect: NoSchedule + - role: worker + labels: + tier: database + workload-type: database + taints: + - key: dedicated + value: database + effect: NoSchedule + registryMirrors: + - mirror: ghcr.io + - mirror: docker.io + url: registry-1.docker.io +``` + +This configuration creates: +- 1 control-plane node with custom labels +- 1 frontend worker (no taints - accepts all workloads) +- 1 backend worker with a taint requiring pods to tolerate `dedicated=backend:NoSchedule` +- 1 database worker with a taint requiring pods to tolerate `dedicated=database:NoSchedule` +- Registry mirrors for GitHub Container Registry and Docker Hub configured on all nodes + +### Viewing node information + +After creating a multi-node cluster, you can view detailed information about the cluster status and all the nodes including their labels and taints by running: + +``` +educates local cluster status +``` Customize local pod and service CIDRs ------------------------------------- diff --git a/tunnel-manager/go.mod b/tunnel-manager/go.mod index 04fbb8780..7f738701e 100644 --- a/tunnel-manager/go.mod +++ b/tunnel-manager/go.mod @@ -1,6 +1,6 @@ module main -go 1.20 +go 1.25.6 require ( github.com/gorilla/websocket v1.5.0 // indirect diff --git a/workshop-images/base-environment/Dockerfile b/workshop-images/base-environment/Dockerfile index 2ba3bb309..81f48cbe0 100644 --- a/workshop-images/base-environment/Dockerfile +++ b/workshop-images/base-environment/Dockerfile @@ -78,7 +78,9 @@ WORKDIR /opt/helper RUN npm install && \ npm run vsce-package -FROM golang:1.19-buster as builder-image +FROM golang:1.25.6 as builder-image +ARG TARGETOS +ARG TARGETARCH WORKDIR /app @@ -87,7 +89,7 @@ echo "09cd14a34f17d88cd4f0d2b73e0bbd0bf56984be21bc947f416a7824a709011e /tmp/git- tar xvf /tmp/git-serve.tar.gz && \ cd git-serve-0.0.5 && \ go mod download && \ - go build -o git-serve cmd/git-serve/main.go + GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o git-serve cmd/git-serve/main.go FROM system-base AS scratch-image