diff --git a/.dockerignore b/.dockerignore index d225aed..2953945 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,7 @@ * !cmd !internal +!pkg !go.mod !go.sum !.golangci.yml diff --git a/.github/renovate.json b/.github/renovate.json index 445c57f..ddf1f32 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,31 +1,36 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "description": "THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.", + "prHeader": "Update Request | Renovate Bot", "extends": [ ":dependencyDashboard", ":gitSignOff", ":semanticCommitScopeDisabled", "schedule:earlyMondays" ], - "prHeader": "Update Request | Renovate Bot", "packageRules": [ { - "matchPackagePatterns": [ - "*" - ], - "matchDatasources": [ - "docker" - ], - "groupName": "container images" + "groupName": "dependencies", + "matchUpdateTypes": [ + "major", + "minor", + "patch", + "pin", + "digest" + ] }, { - "matchPackagePatterns": [ - "*" - ], - "matchDatasources": [ - "go", - "golang-version" - ], - "groupName": "go packages" + "enabled": false, + "matchFileNames": [ + "Dockerfile" + ] + }, + { + "enabled": false, + "matchFileNames": [ + ".github/workflows/*.yaml" + ] } - ] + ], + "separateMajorMinor": false } diff --git a/.github/workflows/slack-notify.yaml b/.github/workflows/slack-notify.yaml index 3809a5c..ec03a7e 100644 --- a/.github/workflows/slack-notify.yaml +++ b/.github/workflows/slack-notify.yaml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-03-28T12:14:24Z by kres 88d1199. +# Generated on 2025-02-10T19:53:43Z by kres 5e9dc91. name: slack-notify "on": @@ -24,11 +24,12 @@ jobs: run: | echo pull_request_number=$(gh pr view -R ${{ github.repository }} ${{ github.event.workflow_run.head_repository.owner.login }}:${{ github.event.workflow_run.head_branch }} --json number --jq .number) >> $GITHUB_OUTPUT - name: Slack Notify - uses: slackapi/slack-github-action@v1 + uses: slackapi/slack-github-action@v2 with: - channel-id: proj-talos-maintainers + method: chat.postMessage payload: | { + "channel": "proj-talos-maintainers", "attachments": [ { "color": "${{ github.event.workflow_run.conclusion == 'success' && '#2EB886' || github.event.workflow_run.conclusion == 'failure' && '#A30002' || '#FFCC00' }}", @@ -88,5 +89,4 @@ jobs: } ] } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + token: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml index 23ea937..3bded5e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-30T11:32:20Z by kres faf91e3. +# Generated on 2025-02-10T19:53:43Z by kres 5e9dc91. # options for analysis running run: @@ -17,7 +17,6 @@ output: path: stdout print-issued-lines: true print-linter-name: true - uniq-by-line: true path-prefix: "" # all available settings of specific linters @@ -116,7 +115,6 @@ linters: - gochecknoglobals - gochecknoinits - godox - - gomnd - gomoddirectives - gosec - inamedparam @@ -135,6 +133,7 @@ linters: - perfsprint # complains about us using fmt.Sprintf in non-performance critical code, updating just kres took too long - goimports # same as gci - musttag # seems to be broken - goes into imported libraries and reports issues there + - exportloopref # WARN The linter 'exportloopref' is deprecated (since v1.60.2) due to: Since Go1.22 (loopvar) this linter is no longer relevant. Replaced by copyloopvar. issues: exclude: [ ] @@ -144,6 +143,7 @@ issues: max-issues-per-linter: 10 max-same-issues: 3 new: false + uniq-by-line: true severity: default-severity: error diff --git a/.kres.yaml b/.kres.yaml index 436877d..7d01dbb 100644 --- a/.kres.yaml +++ b/.kres.yaml @@ -21,6 +21,8 @@ spec: copyFrom: - stage: extension entrypoint: /rootfs/usr/local/lib/containers/talos-vmtoolsd/talos-vmtoolsd + entrypointArgs: + - vmtoolsd --- kind: auto.CustomSteps spec: diff --git a/.license-header.go.txt b/.license-header.go.txt index 66e0819..449001b 100644 --- a/.license-header.go.txt +++ b/.license-header.go.txt @@ -1,3 +1,2 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 diff --git a/Dockerfile b/Dockerfile index 24ff63e..8973457 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -# syntax = docker/dockerfile-upstream:1.9.0-labs +# syntax = docker/dockerfile-upstream:1.12.1-labs # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-30T11:32:20Z by kres faf91e3. +# Generated on 2025-02-10T19:53:43Z by kres 5e9dc91. ARG TOOLCHAIN @@ -12,12 +12,12 @@ COPY manifest.yaml / COPY talos-vmtoolsd.yaml /rootfs/usr/local/etc/containers/talos-vmtoolsd.yaml # runs markdownlint -FROM docker.io/oven/bun:1.1.20-alpine AS lint-markdown +FROM docker.io/oven/bun:1.1.43-alpine AS lint-markdown WORKDIR /src -RUN bun i markdownlint-cli@0.41.0 sentences-per-line@0.2.1 +RUN bun i markdownlint-cli@0.43.0 sentences-per-line@0.3.0 COPY .markdownlint.json . COPY ./README.md ./README.md -RUN bunx markdownlint --ignore "CHANGELOG.md" --ignore "**/node_modules/**" --ignore '**/hack/chglog/**' --rules node_modules/sentences-per-line/index.js . +RUN bunx markdownlint --ignore "CHANGELOG.md" --ignore "**/node_modules/**" --ignore '**/hack/chglog/**' --rules sentences-per-line . # base toolchain image FROM --platform=${BUILDPLATFORM} ${TOOLCHAIN} AS toolchain @@ -55,6 +55,7 @@ RUN --mount=type=cache,target=/go/pkg go mod download RUN --mount=type=cache,target=/go/pkg go mod verify COPY ./cmd ./cmd COPY ./internal ./internal +COPY ./pkg ./pkg RUN --mount=type=cache,target=/go/pkg go list -mod=readonly all >/dev/null FROM tools AS embed-generate @@ -132,5 +133,5 @@ ARG TARGETARCH COPY --from=talos-vmtoolsd talos-vmtoolsd-linux-${TARGETARCH} /rootfs/usr/local/lib/containers/talos-vmtoolsd/talos-vmtoolsd COPY --from=extension / / LABEL org.opencontainers.image.source=https://github.com/siderolabs/talos-vmtoolsd -ENTRYPOINT ["/rootfs/usr/local/lib/containers/talos-vmtoolsd/talos-vmtoolsd"] +ENTRYPOINT ["/rootfs/usr/local/lib/containers/talos-vmtoolsd/talos-vmtoolsd","vmtoolsd"] diff --git a/Makefile b/Makefile index d377460..93b5353 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-07-30T11:32:20Z by kres faf91e3. +# Generated on 2025-02-10T19:53:43Z by kres 5e9dc91. # common variables @@ -17,15 +17,15 @@ WITH_RACE ?= false REGISTRY ?= ghcr.io USERNAME ?= siderolabs REGISTRY_AND_USERNAME ?= $(REGISTRY)/$(USERNAME) -PROTOBUF_GO_VERSION ?= 1.34.2 -GRPC_GO_VERSION ?= 1.4.0 -GRPC_GATEWAY_VERSION ?= 2.20.0 +PROTOBUF_GO_VERSION ?= 1.36.2 +GRPC_GO_VERSION ?= 1.5.1 +GRPC_GATEWAY_VERSION ?= 2.25.1 VTPROTOBUF_VERSION ?= 0.6.0 -GOIMPORTS_VERSION ?= 0.23.0 +GOIMPORTS_VERSION ?= 0.29.0 DEEPCOPY_VERSION ?= v0.5.6 -GOLANGCILINT_VERSION ?= v1.59.1 -GOFUMPT_VERSION ?= v0.6.0 -GO_VERSION ?= 1.22.5 +GOLANGCILINT_VERSION ?= v1.63.4 +GOFUMPT_VERSION ?= v0.7.0 +GO_VERSION ?= 1.23.6 GO_BUILDFLAGS ?= GO_LDFLAGS ?= CGO_ENABLED ?= 0 @@ -41,10 +41,12 @@ PLATFORM ?= linux/amd64 PROGRESS ?= auto PUSH ?= false CI_ARGS ?= +BUILDKIT_MULTI_PLATFORM ?= COMMON_ARGS = --file=Dockerfile COMMON_ARGS += --provenance=false COMMON_ARGS += --progress=$(PROGRESS) COMMON_ARGS += --platform=$(PLATFORM) +COMMON_ARGS += --build-arg=BUILDKIT_MULTI_PLATFORM=$(BUILDKIT_MULTI_PLATFORM) COMMON_ARGS += --push=$(PUSH) COMMON_ARGS += --build-arg=ARTIFACTS="$(ARTIFACTS)" COMMON_ARGS += --build-arg=SHA="$(SHA)" @@ -67,7 +69,7 @@ COMMON_ARGS += --build-arg=DEEPCOPY_VERSION="$(DEEPCOPY_VERSION)" COMMON_ARGS += --build-arg=GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)" COMMON_ARGS += --build-arg=GOFUMPT_VERSION="$(GOFUMPT_VERSION)" COMMON_ARGS += --build-arg=TESTPKGS="$(TESTPKGS)" -TOOLCHAIN ?= docker.io/golang:1.22-alpine +TOOLCHAIN ?= docker.io/golang:1.23-alpine # help menu @@ -143,8 +145,20 @@ clean: ## Cleans up all artifacts. target-%: ## Builds the specified target defined in the Dockerfile. The build result will only remain in the build cache. @$(BUILD) --target=$* $(COMMON_ARGS) $(TARGET_ARGS) $(CI_ARGS) . +registry-%: ## Builds the specified target defined in the Dockerfile and the output is an image. The image is pushed to the registry if PUSH=true. + @$(MAKE) target-$* TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/$(IMAGE_NAME):$(IMAGE_TAG)" BUILDKIT_MULTI_PLATFORM=1 + local-%: ## Builds the specified target defined in the Dockerfile using the local output type. The build result will be output to the specified local destination. @$(MAKE) target-$* TARGET_ARGS="--output=type=local,dest=$(DEST) $(TARGET_ARGS)" + @PLATFORM=$(PLATFORM) DEST=$(DEST) bash -c '\ + for platform in $$(tr "," "\n" <<< "$$PLATFORM"); do \ + directory="$${platform//\//_}"; \ + if [[ -d "$$DEST/$$directory" ]]; then \ + echo $$platform; \ + mv -f "$$DEST/$$directory/"* $$DEST; \ + rmdir "$$DEST/$$directory/"; \ + fi; \ + done' generate: ## Generate .proto definitions. @$(MAKE) local-$@ DEST=./ @@ -197,7 +211,7 @@ lint: lint-golangci-lint lint-gofumpt lint-govulncheck lint-markdown ## Run all .PHONY: image-talos-vmtoolsd image-talos-vmtoolsd: ## Builds image for talos-vmtoolsd. - @$(MAKE) target-$@ TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/talos-vmtoolsd:$(IMAGE_TAG)" + @$(MAKE) registry-$@ IMAGE_NAME="talos-vmtoolsd" .PHONY: rekres rekres: diff --git a/README.md b/README.md index fef563a..6b96d6a 100644 --- a/README.md +++ b/README.md @@ -48,20 +48,40 @@ kubectl --namespace kube-system \ rm vmtoolsd-secret.yaml ``` +If you craft your own manifests, please remember the note about `GRPC_ENFORCE_ALPN_ENABLED=false` below. + Install or upgrade `talos-vmtoolsd`: ```bash kubectl apply --filename https://raw.githubusercontent.com/siderolabs/talos-vmtoolsd/master/deploy/latest.yaml ``` +Remember + ## Talos Compatibility Matrix -| ⬇️ Tools \ Talos ➡️ | 0.7 - 0.10 | 0.11 - 0.13 | 0.14 - 1.4 | 1.4 | 1.5 | 1.6+ | -| ----------------- | ---------- | ----------- | ---------- | --- | --- | ---- | -| **0.5** (current) | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | -| **0.4** | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | -| **0.3** | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | -| **0.2** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +Please find an [older version of this matrix](https://github.com/siderolabs/talos-vmtoolsd/blob/0.4.0/README.md) +for compatibility with older Talos and vmtoolsd-verions. + +| ⬇️ Tools \ Talos ➡️ | 1.5 | 1.6 | 1.7 | 1.8 | 1.9 | +| ------------------ | --- | ----| --- | ---- | --- | +| **0.7** (current) | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | +| **0.6** | ✅ | ✅ | ✅ | ✅ | ⚠️ | +| **0.5** | ✅ | ✅ | | | | + +Talos 1.8+ carries gRPC >= 1.67, which [has issues with older gRPC](https://github.com/siderolabs/talos/issues/9463), +and causes gRPC errors like these: + +```text +rpc error: code = Unavailable desc = connection error: desc = \"transport: authentication handshake failed: credentials: cannot check peer: missing selected ALPN property\" +``` + +There are two workarounds: + +1. use older (< 0.7) `talos-vmtoolsd` on older (< 1.9) Talos versions +2. set `GRPC_ENFORCE_ALPN_ENABLED=false` and everything will be fine + +The latter option is used in the system extention and example manifests. ## Roadmap @@ -93,6 +113,7 @@ It simply translates between both interfaces and thereby seamlessly integrates t ## Attribution +This tool was originally written by Oliver Kuckertz, and was adopted by Equinix and Siderolabs. Talos-vmtoolsd is based on VMware's custom VIC toolbox of the govmomi project. I have reduced the toolbox's functionality to the bare minimum required by vSphere. Its main service has been refactored for plugin support. diff --git a/cmd/talos-vmtoolsd/main.go b/cmd/talos-vmtoolsd/main.go index c0a0021..aa2c213 100644 --- a/cmd/talos-vmtoolsd/main.go +++ b/cmd/talos-vmtoolsd/main.go @@ -1,166 +1,145 @@ -// Package main implements the main entry point for the Talos VMware Tools Daemon. +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +// Package main is the main package invoking the tool package main import ( "context" - "flag" + "errors" "fmt" + "log/slog" "os" - "os/signal" - "syscall" + "strings" - "github.com/sirupsen/logrus" - vmguestmsg "github.com/vmware/vmw-guestinfo/message" - "github.com/vmware/vmw-guestinfo/vmcheck" + "github.com/spf13/cobra" + "github.com/spf13/viper" - "github.com/siderolabs/talos-vmtoolsd/internal/nanotoolbox" - "github.com/siderolabs/talos-vmtoolsd/internal/talosapi" - "github.com/siderolabs/talos-vmtoolsd/internal/tboxcmds" + "github.com/siderolabs/talos-vmtoolsd/internal/talosconnection" + "github.com/siderolabs/talos-vmtoolsd/internal/util" "github.com/siderolabs/talos-vmtoolsd/internal/version" ) -// Debug flags. +const ( + flagUseMachined = "use-machined" + flagLogLevel = "log-level" + flagTalosConfig = "talos-config" + flagTalosNode = "talos-node" +) + +var rootCmd = &cobra.Command{ + Use: "talos-vmtoolsd", + Short: "toolset that glues talos to vmware hypervisors", + Long: "this is a tool like open-vm-tools, but for Talos Linux", + PersistentPreRunE: setup, + PersistentPostRunE: cleanup, +} + +var errTalosSetupFailed = errors.New("error setting up Talos connection") + var ( - talosTestQuery string - useMachinedSocket bool + logger *slog.Logger + api *talosconnection.TalosAPIConnection + ctx context.Context + ctxCancel context.CancelFunc ) -func main() { - l := logrus.StandardLogger() - l.SetFormatter(&logrus.JSONFormatter{ - DisableTimestamp: true, - DisableHTMLEscape: true, - }) - - flag.StringVar(&talosTestQuery, "test-apid-query", "", "query apid") - flag.BoolVar(&useMachinedSocket, "use-machined", false, "use machined unix socket") - flag.Parse() - - // Apply log level, default to "info" - if levelStr, ok := os.LookupEnv("LOG_LEVEL"); ok { - if level, err := logrus.ParseLevel(levelStr); err != nil { - l.WithError(err).Fatal("error parsing log level") - } else { - l.SetLevel(level) - } - } else { - l.SetLevel(logrus.InfoLevel) +func parseLevel(s string) (slog.Level, error) { + // slog does not support trace level logging by default, but is flexible + if strings.ToUpper(s) == "TRACE" { + return util.LogLevelTrace, nil } - l.Infof("talos-vmtoolsd version %v\n"+ - "Copyright 2020-2022 Oliver Kuckertz \n"+ - "This program is free software and available under the Apache 2.0 license.", - version.Tag) - - // Simplify deployment to mixed vSphere and non-vSphere clusters by detecting ESXi and stopping - // early for other platforms. Admins can avoid the overhead of this idle process by labeling - // all ESXi/vSphere nodes and editing talos-vmtoolsd's DaemonSet to run only on those nodes. - if !vmcheck.IsVirtualCPU() { - // NB: We cannot simply exit(0) because DaemonSets are always restarted. - l.Info("halting because the current node is not running under ESXi. fair winds!") - select {} + var level slog.Level + err := level.UnmarshalText([]byte(s)) + + return level, err +} + +func setup(cmd *cobra.Command, _ []string) error { + level, err := parseLevel(viper.GetString(flagLogLevel)) + if err != nil { + panic("error parsing log level") } - ctx, ctxCancel := context.WithCancel(context.Background()) + logOpts := &slog.HandlerOptions{ + Level: level, + } - var ( - api *talosapi.LocalClient - err error - ) + logger = slog.New(slog.NewTextHandler(os.Stdout, logOpts)).With("command", cmd.Name()) + ctx, ctxCancel = context.WithCancel(context.Background()) - if !useMachinedSocket { + if !viper.GetBool(flagUseMachined) { // Our spec file passes the secret path and K8s host IP via env vars. - configPath := os.Getenv("TALOS_CONFIG_PATH") + configPath := viper.GetString(flagTalosConfig) if len(configPath) == 0 { - l.Fatal("error: TALOS_CONFIG_PATH is a required path to a Talos configuration file") + logger.Error("a path to a Talos configuration file is required when not connecting to machined") + + return errTalosSetupFailed } - k8sHost := os.Getenv("TALOS_HOST") - if len(k8sHost) == 0 { - l.Fatal("error: TALOS_HOST is required to point to a node's internal IP") + talosNode := viper.GetString(flagTalosNode) + if len(talosNode) == 0 { + logger.Error("you need to specify a Talos node when not connecting to machined") + + return errTalosSetupFailed } // Connect to Talos apid - api, err = talosapi.NewLocalClient(ctx, l, configPath, k8sHost) + var err error + + api, err = talosconnection.RemoteApidConnection(ctx, logger.With("module", "talosconnection"), configPath, talosNode) if err != nil { - l.WithError(err).Fatal("could not connect to apid") + logger.Error("could not connect to apid", "err", err) + + return errTalosSetupFailed } } else { // Connect to Talos machined - api, err = talosapi.NewLocalSocketClient(ctx, l) + var err error + + api, err = talosconnection.MachinedConnection(ctx, logger.With("module", "talosconnection")) if err != nil { - l.WithError(err).Fatal("could not connect to machined socket") + logger.Error("could not connect to machined socket", "err", err) + + return errTalosSetupFailed } } - defer func() { - if err := api.Close(); err != nil { - l.WithError(err).Warn("failed to close API client during process shutdown") - } - }() + hello := fmt.Sprintf("%s © 2020-2025 Oliver Kuckertz, Equinix and Siderolabs", version.Name) + logger.Info(hello, "version", version.Tag) - // Manual test query mode for Talos apid client - if talosTestQuery != "" { - if err := testQuery(api, talosTestQuery); err != nil { - l.WithField("test_query", talosTestQuery).WithError(err).Fatal("test query failed") + return nil +} - os.Exit(1) //nolint:gocritic - } +func cleanup(_ *cobra.Command, _ []string) error { + if err := api.Close(); err != nil { + logger.Warn("failed to close API client during process shutdown", "err", err) - os.Exit(0) + return err } - // Wires up VMware Toolbox commands to Talos apid. - vmguestmsg.DefaultLogger = l.WithField("module", "vmware-guestinfo") - rpcIn, rpcOut := nanotoolbox.NewHypervisorChannelPair() - svc := nanotoolbox.NewService(l, rpcIn, rpcOut) - tboxcmds.RegisterGuestInfoCommands(svc, api) - tboxcmds.RegisterPowerDelegate(svc, api) - tboxcmds.RegisterVixCommand(svc, api) - - // The toolbox service runs and response to RPC requests in the background. - if err := svc.Start(); err != nil { - l.WithError(err).Fatal("error starting service") - } - - // Graceful shutdown on SIGINT/SIGTERM - sig := make(chan os.Signal, 1) - signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) - - go func() { - l.Debugf("signal: %s", <-sig) - ctxCancel() - svc.Stop() - }() - svc.Wait() - l.Info("graceful shutdown done, fair winds!") + return nil } -func testQuery(api *talosapi.LocalClient, query string) error { - w := os.Stdout - - switch query { - case "net-interfaces": - for idx, intf := range api.NetInterfaces() { - for _, addr := range intf.Addrs { - _, _ = fmt.Fprintf(w, "%d: name=%s mac=%s addr=%s\n", idx, intf.Name, intf.MAC, addr) //nolint:errcheck - } - } - - return nil - case "hostname": - _, _ = fmt.Fprintln(w, api.Hostname()) //nolint:errcheck +func init() { + viper.AutomaticEnv() + viper.SetEnvKeyReplacer(strings.NewReplacer(`-`, `_`)) + viper.SetEnvPrefix("vmtoolsd") - return nil - case "os-version": - _, _ = fmt.Fprintln(w, api.OSVersion()) //nolint:errcheck + pf := rootCmd.PersistentFlags() + pf.Bool(flagUseMachined, false, "use machined unix socket instead of TCP") + pf.String(flagTalosConfig, "", "path to talos config file") + pf.String(flagTalosNode, "", "talos node to operate on") + pf.String(flagLogLevel, "info", "log level (error, warning, info, debug, trace)") - return nil - case "os-version-short": - _, _ = fmt.Fprintln(w, api.OSVersionShort()) //nolint:errcheck + if err := viper.BindPFlags(pf); err != nil { + panic(err) + } +} - return nil - default: - return fmt.Errorf("unknown test query %q", query) +func main() { + if err := rootCmd.Execute(); err != nil { + panic(err) } } diff --git a/cmd/talos-vmtoolsd/talosquery.go b/cmd/talos-vmtoolsd/talosquery.go new file mode 100644 index 0000000..9cf1b6c --- /dev/null +++ b/cmd/talos-vmtoolsd/talosquery.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "time" + + "github.com/spf13/cobra" +) + +var talosqueryCmd = &cobra.Command{ + Use: "talosquery", + Short: "query the talos API", + Long: "this can be used to test the connection with talos api for development of this tool", + Run: talosquery, +} + +func init() { + rootCmd.AddCommand(talosqueryCmd) +} + +func talosquery(_ *cobra.Command, _ []string) { + logger.Info("hostname", "dnsname", api.Hostname()) + logger.Info("os information", "version", api.OSVersion(), "short", api.OSVersionShort()) + u := api.Uptime() + logger.Info("uptime", "seconds", u, "duration", time.Duration(u)) + + for idx, nic := range api.NetInterfaces() { + logger.Info("interface", "idx", idx, "name", nic.Name, "mac", nic.Mac) + + for _, addr := range nic.Addrs { + logger.Info("with address", "addr", addr) + } + } +} diff --git a/cmd/talos-vmtoolsd/vmtoolsd.go b/cmd/talos-vmtoolsd/vmtoolsd.go new file mode 100644 index 0000000..da5b17a --- /dev/null +++ b/cmd/talos-vmtoolsd/vmtoolsd.go @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "errors" + "os" + "os/signal" + "syscall" + + "github.com/spf13/cobra" + vmguestmsg "github.com/vmware/vmw-guestinfo/message" + "github.com/vmware/vmw-guestinfo/vmcheck" + + "github.com/siderolabs/talos-vmtoolsd/internal/integration" + "github.com/siderolabs/talos-vmtoolsd/internal/vmwlogger" + "github.com/siderolabs/talos-vmtoolsd/pkg/nanotoolbox" +) + +var vmtoolsdCmd = &cobra.Command{ + Use: "vmtoolsd", + Short: "a daemon that responds to requests from vmware", + Long: "this daemon listens to vmware and implements stuff like guestinfo, poweroff, etc", + RunE: vmtoolsd, +} + +var errVMToolsdStartFailed = errors.New("error starting vmtoolsd") + +func init() { + rootCmd.AddCommand(vmtoolsdCmd) +} + +func vmtoolsd(_ *cobra.Command, _ []string) error { + // Simplify deployment to mixed vSphere and non-vSphere clusters by detecting ESXi and stopping + // early for other platforms. Admins can avoid the overhead of this idle process by labeling + // all ESXi/vSphere nodes and editing talos-vmtoolsd's DaemonSet to run only on those nodes. + if !vmcheck.IsVirtualCPU() { + // NB: We cannot simply exit(0) because DaemonSets are always restarted. TODO: or should we? Restarts get noticed, select{} won't + logger.Error("halting because the current node is not running under ESXi. fair winds!") + select {} + } + + // Wires up VMware Toolbox commands to Talos apid. + vmlog := vmwlogger.New(logger.With("module", "vmw-guestinfo")) + vmguestmsg.DefaultLogger = vmlog + + rpcIn, rpcOut := nanotoolbox.NewHypervisorChannelPair(logger.With("module", "nanotoolbox.channel")) + svc := nanotoolbox.NewService(logger.With("module", "nanotoolbox.service"), rpcIn, rpcOut) + + integrations := []integration.Integration{ + integration.NewPower(logger.With("integration", "power"), api, svc), + integration.NewGuestInfo(logger.With("integration", "guestinfo"), api, svc), + integration.NewVIX(logger.With("integration", "vix"), api, svc), + } + + for _, i := range integrations { + i.Register() + } + + // The toolbox service runs and response to RPC requests in the background. + if err := svc.Start(); err != nil { + logger.Error("error starting service", "err", err) + + return errVMToolsdStartFailed + } + + // Graceful shutdown on SIGINT/SIGTERM + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + + go func() { + logger.Debug("signal received", "signal", <-sig) + ctxCancel() + svc.Stop() + }() + svc.Wait() + logger.Info("graceful shutdown done, fair winds!") + + return nil +} diff --git a/deploy/latest.yaml b/deploy/latest.yaml index 3a899a1..d9fbcb9 100644 --- a/deploy/latest.yaml +++ b/deploy/latest.yaml @@ -37,6 +37,8 @@ spec: mountPath: /etc/talos-vmtoolsd readOnly: true env: + - name: GRPC_ENFORCE_ALPN_ENABLED + value: "false" - name: TALOS_CONFIG_PATH value: /etc/talos-vmtoolsd/talosconfig - name: TALOS_HOST diff --git a/go.mod b/go.mod index 0ed5055..1749d69 100644 --- a/go.mod +++ b/go.mod @@ -1,60 +1,87 @@ module github.com/siderolabs/talos-vmtoolsd -go 1.22.1 +go 1.23.3 + +toolchain go1.23.4 require ( - github.com/cosi-project/runtime v0.3.20 + github.com/cosi-project/runtime v0.9.2 github.com/golang/protobuf v1.5.4 - github.com/siderolabs/talos v1.6.7 - github.com/siderolabs/talos/pkg/machinery v1.6.7 - github.com/sirupsen/logrus v1.9.3 - github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 - github.com/vmware/govmomi v0.36.2 + github.com/siderolabs/talos v1.9.3 + github.com/siderolabs/talos/pkg/machinery v1.9.3 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 + github.com/vmware/govmomi v0.48.0 github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 - google.golang.org/grpc v1.62.1 + google.golang.org/grpc v1.70.0 ) require ( - github.com/ProtonMail/go-crypto v1.0.0 // indirect + cel.dev/expr v0.19.2 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect - github.com/ProtonMail/gopenpgp/v2 v2.7.5 // indirect - github.com/adrg/xdg v0.4.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/containerd/go-cni v1.1.9 // indirect - github.com/containernetworking/cni v1.1.2 // indirect + github.com/ProtonMail/gopenpgp/v2 v2.8.2 // indirect + github.com/adrg/xdg v0.5.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cloudflare/circl v1.5.0 // indirect + github.com/containerd/go-cni v1.1.12 // indirect + github.com/containernetworking/cni v1.2.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gertd/go-pluralize v0.2.1 // indirect + github.com/google/cel-go v0.23.1 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/jsimonetti/rtnetlink v1.4.1 // indirect - github.com/mdlayher/ethtool v0.1.0 // indirect + github.com/jsimonetti/rtnetlink/v2 v2.0.3-0.20241216183107-2d6e9f8ad3f2 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mdlayher/ethtool v0.2.0 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/petermattis/goid v0.0.0-20250121172306-05bcfb9a85dc // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2 // indirect + github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/siderolabs/crypto v0.4.4 // indirect - github.com/siderolabs/gen v0.4.8 // indirect - github.com/siderolabs/go-api-signature v0.3.2 // indirect - github.com/siderolabs/go-blockdevice v0.4.7 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/siderolabs/crypto v0.5.1 // indirect + github.com/siderolabs/gen v0.8.0 // indirect + github.com/siderolabs/go-api-signature v0.3.6 // indirect + github.com/siderolabs/go-blockdevice/v2 v2.0.13 // indirect github.com/siderolabs/go-pointer v1.0.0 // indirect github.com/siderolabs/net v0.4.0 // indirect - github.com/siderolabs/protoenc v0.2.1 // indirect + github.com/siderolabs/protoenc v0.2.2 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect + google.golang.org/protobuf v1.36.4 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 916f2b7..d2eb299 100644 --- a/go.sum +++ b/go.sum @@ -1,341 +1,261 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4= +cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= -github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA= -github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/ProtonMail/gopenpgp/v2 v2.8.2 h1:fe/XagfxkHRCr+cLFMcoF7XwaASRGSmK/fmcmK8yo6o= +github.com/ProtonMail/gopenpgp/v2 v2.8.2/go.mod h1:pPWZyRQWpQ7g8NWsdZmUynNZ1R09k4MdbSHvm+KooqM= +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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/brianvoe/gofakeit/v6 v6.24.0 h1:74yq7RRz/noddscZHRS2T84oHZisW9muwbb8sRnU52A= -github.com/brianvoe/gofakeit/v6 v6.24.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= -github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/go-cni v1.1.9 h1:ORi7P1dYzCwVM6XPN4n3CbkuOx/NZ2DOqy+SHRdo9rU= -github.com/containerd/go-cni v1.1.9/go.mod h1:XYrZJ1d5W6E2VOvjffL3IZq0Dz6bsVlERHbekNK90PM= -github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= -github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= -github.com/cosi-project/runtime v0.3.20 h1:pl8mwbHMFIRFYV8v0Glxw2ruhTXn/5ij7TSlO9nApi4= -github.com/cosi-project/runtime v0.3.20/go.mod h1:3DQsIr7zF/bmWfHOnpHmOQ9mDukFGi8AMoHx2rNsi+s= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +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/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= +github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= +github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= +github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/containerd/go-cni v1.1.12 h1:wm/5VD/i255hjM4uIZjBRiEQ7y98W9ACy/mHeLi4+94= +github.com/containerd/go-cni v1.1.12/go.mod h1:+jaqRBdtW5faJxj2Qwg1Of7GsV66xcvnCx4mSJtUlxU= +github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= +github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= +github.com/cosi-project/runtime v0.9.2 h1:l0kugKjZUFrmBFwkza2lt2vA1u8PBuWbrbB2HvPmkTs= +github.com/cosi-project/runtime v0.9.2/go.mod h1:dzAY2bdrPv0y39FyJOOURMkx3Ae7erMVoxX/N0OAZQU= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA= github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/cel-go v0.23.1 h1:91ThhEZlBcE5rB2adBVXqvDoqdL8BG2oyhd0bK1I/r4= +github.com/google/cel-go v0.23.1/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +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/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jsimonetti/rtnetlink v1.4.1 h1:JfD4jthWBqZMEffc5RjgmlzpYttAVw1sdnmiNaPO3hE= -github.com/jsimonetti/rtnetlink v1.4.1/go.mod h1:xJjT7t59UIZ62GLZbv6PLLo8VFrostJMPBAheR6OM8w= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/jsimonetti/rtnetlink/v2 v2.0.3-0.20241216183107-2d6e9f8ad3f2 h1:4pspWog/mjnfv+B3rjEUfCoFL80T7J8ojK9ay8ApPCM= +github.com/jsimonetti/rtnetlink/v2 v2.0.3-0.20241216183107-2d6e9f8ad3f2/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mdlayher/ethtool v0.1.0 h1:XAWHsmKhyPOo42qq/yTPb0eFBGUKKTR1rE0dVrWVQ0Y= -github.com/mdlayher/ethtool v0.1.0/go.mod h1:fBMLn2UhfRGtcH5ZFjr+6GUiHEjZsItFD7fSn7jbZVQ= +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/mdlayher/ethtool v0.2.0 h1:akcA4WZVWozzirPASeMq8qgLkxpF3ykftVXwnrMKrhY= +github.com/mdlayher/ethtool v0.2.0/go.mod h1:W0pIBrNPK1TslIN4Z9wt1EVbay66Kbvek2z2f29VBfw= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.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.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= -github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +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/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20250121172306-05bcfb9a85dc h1:Xz/LkK9AJRY5QTkA1uE1faB8yeqRFjeKgwDtI13ogcY= +github.com/petermattis/goid v0.0.0-20250121172306-05bcfb9a85dc/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2 h1:1sLMdKq4gNANTj0dUibycTLzpIEKVnLnbaEkxws78nw= +github.com/planetscale/vtprotobuf v0.6.1-0.20241121165744-79df5c4772f2/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +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/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 h1:UVArwN/wkKjMVhh2EQGC0tEc1+FqiLlvYXY5mQ2f8Wg= +github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/siderolabs/crypto v0.4.4 h1:Q6EDBMR2Ub2oAZW5Xl8lrKB27bM3Sn8Gkfw3rngco5U= -github.com/siderolabs/crypto v0.4.4/go.mod h1:hsR3tJ3aaeuhCChsLF4dBd9vlJVPvmhg4vvx2ez4aD4= -github.com/siderolabs/gen v0.4.8 h1:VNpbmDLhkXp7qcSEkKk1Ee7vU2afs3xvHrWLGR2UuiY= -github.com/siderolabs/gen v0.4.8/go.mod h1:7ROKVHHB68R3Amrd4a1ZXz/oMrXWF3Mg3lSEgnkJY5c= -github.com/siderolabs/go-api-signature v0.3.2 h1:blqrZF1GM7TWgq7mY7CsR+yQ93u6az0Kf0mfsw+hvf0= -github.com/siderolabs/go-api-signature v0.3.2/go.mod h1:punhUOaXa7LELYBRCUhfgUGH6ieVz68GrP98apCKXj8= -github.com/siderolabs/go-blockdevice v0.4.7 h1:2bk4WpEEflGxjrNwp57ye24Pr+cYgAiAeNMWiQOuWbQ= -github.com/siderolabs/go-blockdevice v0.4.7/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/siderolabs/crypto v0.5.1 h1:aZEUTZBoP8rH+0TqQAlUgazriPh89MrXf4R+th+m6ps= +github.com/siderolabs/crypto v0.5.1/go.mod h1:7RHC7eUKBx6RLS2lDaNXrQ83zY9iPH/aQSTxk1I4/j4= +github.com/siderolabs/gen v0.8.0 h1:Pj93+hexkk5hQ7izjJ6YXnEWc8vlzOmDwFz13/VzS7o= +github.com/siderolabs/gen v0.8.0/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= +github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU= +github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U= +github.com/siderolabs/go-blockdevice/v2 v2.0.13 h1:N94eK+EFwnD+2kdNT38910Qlu+5+Z0WDODKbX7NXvPs= +github.com/siderolabs/go-blockdevice/v2 v2.0.13/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= github.com/siderolabs/go-pointer v1.0.0 h1:6TshPKep2doDQJAAtHUuHWXbca8ZfyRySjSBT/4GsMU= github.com/siderolabs/go-pointer v1.0.0/go.mod h1:HTRFUNYa3R+k0FFKNv11zgkaCLzEkWVzoYZ433P3kHc= github.com/siderolabs/go-retry v0.3.3 h1:zKV+S1vumtO72E6sYsLlmIdV/G/GcYSBLiEx/c9oCEg= github.com/siderolabs/go-retry v0.3.3/go.mod h1:Ff/VGc7v7un4uQg3DybgrmOWHEmJ8BzZds/XNn/BqMI= github.com/siderolabs/net v0.4.0 h1:1bOgVay/ijPkJz4qct98nHsiB/ysLQU0KLoBC4qLm7I= github.com/siderolabs/net v0.4.0/go.mod h1:/ibG+Hm9HU27agp5r9Q3eZicEfjquzNzQNux5uEk0kM= -github.com/siderolabs/protoenc v0.2.1 h1:BqxEmeWQeMpNP3R6WrPqDatX8sM/r4t97OP8mFmg6GA= -github.com/siderolabs/protoenc v0.2.1/go.mod h1:StTHxjet1g11GpNAWiATgc8K0HMKiFSEVVFOa/H0otc= -github.com/siderolabs/talos v1.6.7 h1:PGN5qQesMzxSEbW5qLLc1hxdniXbrWlGE9LCwJyza4E= -github.com/siderolabs/talos v1.6.7/go.mod h1:IftqCYzeNglswkKVjL8g3uE8BL6XO193KKdnbjmOgP8= -github.com/siderolabs/talos/pkg/machinery v1.6.7 h1:L/IV0+w+0n6Jsxa1LEFOxPaNNluGrZqn3YRvAXk/xzA= -github.com/siderolabs/talos/pkg/machinery v1.6.7/go.mod h1:X1rb+ZA0tRjclzuH4Kurune4DK6kr58B/vorQ88KwpE= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 h1:OzCVd0SV5qE3ZcDeSFCmOWLZfEWZ3Oe8KtmSOYKEVWE= -github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= +github.com/siderolabs/protoenc v0.2.2 h1:vVQDrTjV+QSOiroWTca6h2Sn5XWYk7VSUPav5J0Qp54= +github.com/siderolabs/protoenc v0.2.2/go.mod h1:gtkHkjSCFEceXUHUzKDpnuvXu1mab9D3pVxTnQN+z+o= +github.com/siderolabs/talos v1.9.3 h1:WSF0Bt2ZoTCaFlR9elOCYu92tpRyqIGH+dmZaRqXu9I= +github.com/siderolabs/talos v1.9.3/go.mod h1:8Cbumft8sW99t3cLNmwpJYVTmYVvQ0Lj8mtW9duGay4= +github.com/siderolabs/talos/pkg/machinery v1.9.3 h1:P3fb4UsmF3UEV2CDf100L7fVW+CBL5Yk6sEDis7uZr4= +github.com/siderolabs/talos/pkg/machinery v1.9.3/go.mod h1:G4swVKn4vK3455ssgcPUrikPTypx6n+uaqv7GyBWXy4= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vmware/govmomi v0.36.2 h1:fFTicZmjwPCiBJGyKLZ5Ty9JbTgBPVSXJDv1Duw0r7c= -github.com/vmware/govmomi v0.36.2/go.mod h1:mtGWtM+YhTADHlCgJBiskSRPOZRsN9MSjPzaZLte/oQ= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vmware/govmomi v0.48.0 h1:CP5bCvkDNGkmn29UlcJKTWMLwDg3iusP8anrZnedWrg= +github.com/vmware/govmomi v0.48.0/go.mod h1:bYwUHpGpisE4AOlDl5eph90T+cjJMIcKx/kaa5v5rQM= github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 h1:v6jG/tdl4O07LNVp74Nt7/OyL+1JsIW1M2f/nSvQheY= github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3/go.mod h1:CSBTxrhePCm0cmXNKDGeu+6bOQzpaEklfCqEpn89JWk= -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.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= 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.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.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 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-20190308221718-c2843e01d9a2/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= -golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -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/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20211025201205-69cdffdb9359/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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa h1:Jt1XW5PaLXF1/ePZrznsh/aAUvI7Adfc3LY1dAKlzRs= -google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:K4kfzHtI0kqWA79gecJarFtDn/Mls+GxQcg3Zox91Ac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa h1:RBgMaUMP+6soRkik4VoN8ojR2nex2TqZwjSSogic+eo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 h1:A2ni10G3UlplFrWdCDJTl7D7mJ7GSRm37S+PDimaKRw= +google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hack/release.sh b/hack/release.sh index 92f0848..421a67b 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/usr/bin/env bash # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-03-28T12:14:24Z by kres 88d1199. +# Generated on 2025-02-10T19:53:43Z by kres 5e9dc91. set -e @@ -44,9 +44,92 @@ function commit { exit 1 fi + if is_on_main_branch; then + update_license_files + fi + git commit -s -m "release($1): prepare release" -m "This is the official $1 release." } +function is_on_main_branch { + main_remotes=("upstream" "origin") + branch_names=("main" "master") + current_branch=$(git rev-parse --abbrev-ref HEAD) + + echo "Check current branch: $current_branch" + + for remote in "${main_remotes[@]}"; do + echo "Fetch remote $remote..." + + if ! git fetch --quiet "$remote" &>/dev/null; then + echo "Failed to fetch $remote, skip..." + + continue + fi + + for branch_name in "${branch_names[@]}"; do + if ! git rev-parse --verify "$branch_name" &>/dev/null; then + echo "Branch $branch_name does not exist, skip..." + + continue + fi + + echo "Branch $remote/$branch_name exists, comparing..." + + merge_base=$(git merge-base "$current_branch" "$remote/$branch_name") + latest_main=$(git rev-parse "$remote/$branch_name") + + if [ "$merge_base" = "$latest_main" ]; then + echo "Current branch is up-to-date with $remote/$branch_name" + + return 0 + else + echo "Current branch is not on $remote/$branch_name" + + return 1 + fi + done + done + + echo "No main or master branch found on any remote" + + return 1 +} + +function update_license_files { + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + parent_dir="$(dirname "$script_dir")" + current_year=$(date +"%Y") + change_date=$(date -v+4y +"%Y-%m-%d" 2>/dev/null || date -d "+4 years" +"%Y-%m-%d" 2>/dev/null || date --date="+4 years" +"%Y-%m-%d") + + # Find LICENSE and .kres.yaml files recursively in the parent directory (project root) + find "$parent_dir" \( -name "LICENSE" -o -name ".kres.yaml" \) -type f | while read -r file; do + temp_file="${file}.tmp" + + if [[ $file == *"LICENSE" ]]; then + if grep -q "^Business Source License" "$file"; then + sed -e "s/The Licensed Work is (c) [0-9]\{4\}/The Licensed Work is (c) $current_year/" \ + -e "s/Change Date: [0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}/Change Date: $change_date/" \ + "$file" >"$temp_file" + else + continue # Not a Business Source License file + fi + elif [[ $file == *".kres.yaml" ]]; then + sed -E 's/^([[:space:]]*)ChangeDate:.*$/\1ChangeDate: "'"$change_date"'"/' "$file" >"$temp_file" + fi + + # Check if the file has changed + if ! cmp -s "$file" "$temp_file"; then + mv "$temp_file" "$file" + echo "Updated: $file" + git add "$file" + else + echo "No changes: $file" + rm "$temp_file" + fi + done +} + if declare -f "$1" > /dev/null then cmd="$1" @@ -55,7 +138,7 @@ then else cat <= maxNICs { + g.logger.Warn("maximum number of NICs reached", "max", maxNICs) + + break + } + } + + if hostname := g.talos.Hostname(); hostname != "" { + info.V3.DNSConfigInfo = &toolbox.DNSConfigInfo{HostName: &hostname} + } + + infoXDR, err := toolbox.EncodeXDR(info) + if err != nil { + g.logger.Error("error encoding NIC info to XDR", "err", err) + + return + } + + g.logger.Debug("setting info about nics", "len", len(info.V3.Nics)) + + g.setGuestInfo(guestInfoIPAddressV3, infoXDR) +} + +// primaryIP using very complex logic to carefully calculate the primary IP address using AI, weighted logic and kittens. +func (g *GuestInfo) primaryIP() string { + // find the first interface with an address and return it. Assume that that's the "primary IP" + for _, nic := range g.talos.NetInterfaces() { + for _, addr := range nic.Addrs { + return addr.Addr().String() + } + } + + g.logger.Warn("no IP addresses found from Talos API") + + return "unknown" +} + +// Register registers all handlers, options and capabilities. +func (g *GuestInfo) Register() { + g.logger.Debug("registering") + // reset handlers + g.service.RegisterResetHandler(func() { + g.setDNSName() + g.setOSNameFull() + g.setOSName() + g.setUptime() + g.setIPAddressV3() + }) + + // option handlers + g.service.RegisterOptionHandler("broadcastIP", func(string, string) { + g.infoSet("guestinfo.ip", g.primaryIP()) + g.setIPAddressV3() + }) + + // ping handlers + g.service.RegisterCommandHandler("ping", func([]byte) ([]byte, error) { + g.setDNSName() + g.setOSNameFull() + g.setOSName() + g.setUptime() + + return nil, nil + }) + + // As stated in guestInfoServer.c, VMX expects uptime information in response + // to the capabilities request. + g.service.AddCapability(fmt.Sprintf("SetGuestInfo %d %d", guestInfoUptime, g.talos.Uptime())) +} diff --git a/internal/integration/integration.go b/internal/integration/integration.go new file mode 100644 index 0000000..6bf9d21 --- /dev/null +++ b/internal/integration/integration.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +// Package integration packages all the integrations between Talos Linux and VMWare +package integration + +// Integration is the interface every integration should implement. +type Integration interface { + Register() +} diff --git a/internal/integration/power.go b/internal/integration/power.go new file mode 100644 index 0000000..c785d23 --- /dev/null +++ b/internal/integration/power.go @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +package integration + +import ( + "fmt" + "log/slog" + + "github.com/siderolabs/talos-vmtoolsd/internal/talosconnection" + "github.com/siderolabs/talos-vmtoolsd/pkg/nanotoolbox" +) + +// Power represents the power integration (shutdown/reboot/etc.) +type Power struct { + talos *talosconnection.TalosAPIConnection + logger *slog.Logger + service *nanotoolbox.Service +} + +type powerState int + +// vmware/guestrpc/powerops.h. +const ( + _ powerState = iota + pwrHaltID + pwrRebootID + pwrPowerOnID + pwrResumeID + pwrSuspendID +) + +var pwrStates = map[powerState]string{ + pwrHaltID: "OS_Halt", + pwrRebootID: "OS_Reboot", + pwrPowerOnID: "OS_PowerOn", + pwrResumeID: "OS_Resume", + pwrSuspendID: "OS_Suspend", +} + +func (p powerState) Name() string { + return pwrStates[p] +} + +// powerHandler is the type of the function that handles power operations. +type powerHandler func() error + +// no-op power operation. +func noop() error { + return nil +} + +// NewPower creates a new power integration. +func NewPower(logger *slog.Logger, talos *talosconnection.TalosAPIConnection, service *nanotoolbox.Service) *Power { + logger.Debug("initializing") + + p := &Power{ + logger: logger, + talos: talos, + service: service, + } + + return p +} + +// template function that "creates" a power operation. It basically wraps powerFn. +func (p *Power) makePowerHandler(ps powerState, powerFn powerHandler) (string, nanotoolbox.CommandHandler) { + return ps.Name(), func([]byte) ([]byte, error) { + l := p.logger.With("power_op", ps.Name()) + l.Debug("handling power operation") + + rc := nanotoolbox.RpciOK + + if err := powerFn(); err != nil { + l.Error("error handling power operation", "err", err) + + rc = nanotoolbox.RpciERR + } + + msg := fmt.Sprintf("tools.os.statechange.status %s%d\x00", rc, int(ps)) + if _, err := p.service.Request([]byte(msg)); err != nil { + return nil, fmt.Errorf("error sending %q: %w", msg, err) + } + + return nil, nil + } +} + +// Register registers the power integration into the service. +func (p *Power) Register() { + p.logger.Debug("registering") + p.service.AddCapability("tools.capability.statechange") + p.service.AddCapability("tools.capability.softpowerop_retry") + p.service.RegisterCommandHandler(p.makePowerHandler(pwrHaltID, p.talos.Shutdown)) + p.service.RegisterCommandHandler(p.makePowerHandler(pwrRebootID, p.talos.Reboot)) + p.service.RegisterCommandHandler(p.makePowerHandler(pwrPowerOnID, noop)) + p.service.RegisterCommandHandler(p.makePowerHandler(pwrSuspendID, noop)) + p.service.RegisterCommandHandler(p.makePowerHandler(pwrResumeID, noop)) +} diff --git a/internal/integration/vix.go b/internal/integration/vix.go new file mode 100644 index 0000000..2a0c0e1 --- /dev/null +++ b/internal/integration/vix.go @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +package integration + +import ( + "encoding/base64" + "log/slog" + + "github.com/vmware/govmomi/toolbox/vix" + + "github.com/siderolabs/talos-vmtoolsd/internal/talosconnection" + "github.com/siderolabs/talos-vmtoolsd/internal/version" + "github.com/siderolabs/talos-vmtoolsd/pkg/nanotoolbox" + "github.com/siderolabs/talos-vmtoolsd/pkg/vixserver" +) + +const ( + vixCommand = "Vix_1_Relayed_Command" +) + +const ( + vixToolsFeatureSupportGetHandleState = 1 +) + +const ( + vixGuestOfFamilyLinux = 1 +) + +// VIX represents the VIX integration. +type VIX struct { + talos *talosconnection.TalosAPIConnection + logger *slog.Logger + service *nanotoolbox.Service + vixserver *vixserver.VIXCommandServer +} + +// NewVIX constructs the VIX integration. +func NewVIX(logger *slog.Logger, talos *talosconnection.TalosAPIConnection, service *nanotoolbox.Service) *VIX { + vs := vixserver.New(logger.With("module", "vixserver"), talos) + + logger.Debug("initializing") + + vi := &VIX{ + logger: logger, + talos: talos, + service: service, + vixserver: vs, + } + + return vi +} + +func (v *VIX) handleVIXCommand(data []byte) ([]byte, error) { + res, err := v.vixserver.Dispatch(data) + if err != nil { + v.logger.Error("error dispatching VIX command", "err", err) + } + + return res, err +} + +// Register registers the VIX integration into the service. +func (v *VIX) Register() { + v.logger.Debug("registering") + v.service.RegisterCommandHandler(vixCommand, v.handleVIXCommand) + v.vixserver.RegisterCommand(vixToolsFeatureSupportGetHandleState, v.getToolState) +} + +func (v *VIX) getToolState(_ vix.CommandRequestHeader, _ []byte) ([]byte, error) { + osVersion := v.talos.OSVersion() + osVersionShort := v.talos.OSVersionShort() + hostname := v.talos.Hostname() + + v.logger.Debug("sending tool state", "version", osVersion, "short", osVersionShort, "hostname", hostname) + + props := vix.PropertyList{ + vix.NewStringProperty(vix.PropertyGuestOsVersion, osVersion), + vix.NewStringProperty(vix.PropertyGuestOsVersionShort, osVersionShort), + vix.NewStringProperty(vix.PropertyGuestToolsProductNam, version.Name), + vix.NewStringProperty(vix.PropertyGuestToolsVersion, version.Tag), + vix.NewStringProperty(vix.PropertyGuestName, hostname), + vix.NewInt32Property(vix.PropertyGuestToolsAPIOptions, vixToolsFeatureSupportGetHandleState), + vix.NewInt32Property(vix.PropertyGuestOsFamily, vixGuestOfFamilyLinux), + } + + bin, err := props.MarshalBinary() + if err != nil { + v.logger.Warn("error encoding vix props to binary", "err", err) + + return nil, err + } + + encoder := base64.StdEncoding + res := make([]byte, encoder.EncodedLen(len(bin))) + encoder.Encode(res, bin) + + return res, nil +} diff --git a/internal/talosapi/client.go b/internal/talosapi/client.go deleted file mode 100644 index 0c0af4b..0000000 --- a/internal/talosapi/client.go +++ /dev/null @@ -1,209 +0,0 @@ -// Package talosapi represents the Talos API client. -package talosapi - -import ( - "context" - "fmt" - - "github.com/cosi-project/runtime/pkg/safe" - "github.com/golang/protobuf/ptypes/empty" - "github.com/siderolabs/talos/pkg/grpc/middleware/authz" - "github.com/siderolabs/talos/pkg/machinery/api/machine" - talosclient "github.com/siderolabs/talos/pkg/machinery/client" - talosconfig "github.com/siderolabs/talos/pkg/machinery/client/config" - talosconstants "github.com/siderolabs/talos/pkg/machinery/constants" - "github.com/siderolabs/talos/pkg/machinery/resources/network" - talosrole "github.com/siderolabs/talos/pkg/machinery/role" - "github.com/sirupsen/logrus" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/metadata" - - "github.com/siderolabs/talos-vmtoolsd/internal/tboxcmds" -) - -// LocalClient represents the Talos API client. -type LocalClient struct { - ctx context.Context //nolint:containedctx - log logrus.FieldLogger - api *talosclient.Client -} - -// Close closes the client. -func (c *LocalClient) Close() error { - return c.api.Close() -} - -func (c *LocalClient) connectToApid(configPath string, k8sHost string) (*talosclient.Client, error) { - cfg, err := talosconfig.Open(configPath) - if err != nil { - return nil, fmt.Errorf("failed to open config file %q: %w", configPath, err) - } - - opts := []talosclient.OptionFunc{ - talosclient.WithConfig(cfg), - talosclient.WithEndpoints(k8sHost), - } - - api, err := talosclient.New(c.ctx, opts...) - if err != nil { - return nil, fmt.Errorf("failed to construct client: %w", err) - } - - return api, nil -} - -func (c *LocalClient) connectToMachined() (*talosclient.Client, error) { - opts := []talosclient.OptionFunc{ - talosclient.WithUnixSocket(talosconstants.MachineSocketPath), - talosclient.WithGRPCDialOptions(grpc.WithTransportCredentials(insecure.NewCredentials())), - } - md := metadata.New(nil) - authz.SetMetadata(md, talosrole.MakeSet(talosrole.Admin)) - c.ctx = metadata.NewOutgoingContext(c.ctx, md) - - api, err := talosclient.New(c.ctx, opts...) - if err != nil { - return nil, fmt.Errorf("failed to construct client: %w", err) - } - - return api, nil -} - -// Shutdown shuts down the machine. -func (c *LocalClient) Shutdown() error { - return c.api.Shutdown(c.ctx) -} - -// Reboot reboots the machine. -func (c *LocalClient) Reboot() error { - return c.api.Reboot(c.ctx) -} - -func (c *LocalClient) osVersionInfo() (*machine.VersionInfo, error) { - resp, err := c.api.Version(c.ctx) - if err != nil || len(resp.Messages) == 0 { - return nil, err - } - - return resp.Messages[0].Version, nil -} - -// OSVersion returns the OS version. -func (c *LocalClient) OSVersion() string { - v, err := c.osVersionInfo() - if err != nil { - c.log.WithError(err).Error("error retrieving OS version information") - - return "Talos" - } - - return fmt.Sprintf("Talos %s-%s", v.Tag, v.Sha) -} - -// OSVersionShort returns the short OS version. -func (c *LocalClient) OSVersionShort() string { - v, err := c.osVersionInfo() - if err != nil { - c.log.WithError(err).Error("error retrieving OS version information") - - return "Talos" - } - - return fmt.Sprintf("Talos %s", v.Tag) -} - -// Hostname returns the hostname. -func (c *LocalClient) Hostname() string { - resp, err := c.api.MachineClient.Hostname(c.ctx, &empty.Empty{}) - if err != nil || len(resp.Messages) == 0 { - c.log.WithError(err).Error("error retrieving hostname") - - return "" - } - - return resp.Messages[0].Hostname -} - -// NetInterfaces returns the network interfaces. -func (c *LocalClient) NetInterfaces() (result []tboxcmds.NetInterface) { - addrMap := make(map[string][]*network.AddressStatusSpec) - - networkAddresses, err := safe.StateListAll[*network.AddressStatus](c.ctx, c.api.COSI) - if err != nil { - c.log.WithError(err).Error("error listing address status resources") - - return nil - } - - iter := networkAddresses.Iterator() - - for iter.Next() { - linkName := iter.Value().TypedSpec().LinkName - addrMap[linkName] = append(addrMap[linkName], iter.Value().TypedSpec()) - } - - linkStatuses, err := safe.StateListAll[*network.LinkStatus](c.ctx, c.api.COSI) - if err != nil { - c.log.WithError(err).Error("error listing link status resources") - - return nil - } - - linksIter := linkStatuses.Iterator() - - for linksIter.Next() { - link := linksIter.Value().TypedSpec() - - if !link.Physical() { - continue - } - - intf := tboxcmds.NetInterface{ - Name: linksIter.Value().Metadata().ID(), - MAC: link.HardwareAddr.String(), - } - - for _, addr := range addrMap[intf.Name] { - intf.Addrs = append(intf.Addrs, addr.Address) - } - - result = append(result, intf) - } - - return result -} - -// NewLocalClient creates a new Talos API client. -func NewLocalClient(ctx context.Context, log logrus.FieldLogger, configPath string, k8sHost string) (*LocalClient, error) { - var err error - - c := &LocalClient{ - ctx: ctx, - log: log.WithField("module", "talosapi"), - } - - c.api, err = c.connectToApid(configPath, k8sHost) - if err != nil { - return nil, fmt.Errorf("failed to connect to apid: %w", err) - } - - return c, nil -} - -// NewLocalSocketClient creates a new Talos API client using a local socket. -func NewLocalSocketClient(ctx context.Context, log logrus.FieldLogger) (*LocalClient, error) { - var err error - - c := &LocalClient{ - ctx: ctx, - log: log.WithField("module", "talosapi"), - } - - c.api, err = c.connectToMachined() - if err != nil { - return nil, fmt.Errorf("failed to connect to machined: %w", err) - } - - return c, nil -} diff --git a/internal/talosconnection/client.go b/internal/talosconnection/client.go new file mode 100644 index 0000000..6b2a1f8 --- /dev/null +++ b/internal/talosconnection/client.go @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +// Package talosconnection represents the connection to the Talos API. +package talosconnection + +import ( + "context" + "fmt" + "log/slog" + + "github.com/siderolabs/talos/pkg/grpc/middleware/authz" + talosclient "github.com/siderolabs/talos/pkg/machinery/client" + talosconfig "github.com/siderolabs/talos/pkg/machinery/client/config" + talosconstants "github.com/siderolabs/talos/pkg/machinery/constants" + talosrole "github.com/siderolabs/talos/pkg/machinery/role" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" +) + +// TalosAPIConnection represents the Talos API client. +type TalosAPIConnection struct { + ctx context.Context //nolint:containedctx + log *slog.Logger + client *talosclient.Client +} + +// Close closes the client. +func (c *TalosAPIConnection) Close() error { + return c.client.Close() +} + +// RemoteApidConnection is used for using a TCP/gRPC connection to apid. +func RemoteApidConnection(ctx context.Context, logger *slog.Logger, configPath string, node string) (*TalosAPIConnection, error) { + cfg, err := talosconfig.Open(configPath) + if err != nil { + return nil, fmt.Errorf("failed to open config file %q: %w", configPath, err) + } + + logger.Debug("setting up talos connection to apid", "configfile", configPath, "node", node) + + client, err := talosclient.New(ctx, + talosclient.WithConfig(cfg), + talosclient.WithEndpoints(node), + ) + if err != nil { + logger.Error("could not setup connection", "err", err) + } + + conn := &TalosAPIConnection{ + ctx: ctx, + log: logger, + client: client, + } + + return conn, nil +} + +// MachinedConnection is used for using a connection to machined using local UNIX socket. +func MachinedConnection(ctx context.Context, logger *slog.Logger) (*TalosAPIConnection, error) { + logger.Debug("setting up talos connection to machined", "socket", talosconstants.MachineSocketPath) + + md := metadata.Pairs() + authz.SetMetadata(md, talosrole.MakeSet(talosrole.Admin)) + adminCtx := metadata.NewOutgoingContext(ctx, md) + + client, err := talosclient.New(adminCtx, + talosclient.WithUnixSocket(talosconstants.MachineSocketPath), + talosclient.WithGRPCDialOptions(grpc.WithTransportCredentials(insecure.NewCredentials())), + ) + if err != nil { + logger.Error("could not setup connection", "err", err) + } + + conn := &TalosAPIConnection{ + ctx: adminCtx, + log: logger, + client: client, + } + + return conn, nil +} diff --git a/internal/talosconnection/getters.go b/internal/talosconnection/getters.go new file mode 100644 index 0000000..1b29072 --- /dev/null +++ b/internal/talosconnection/getters.go @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +package talosconnection + +import ( + "fmt" + "net/netip" + "time" + + "github.com/cosi-project/runtime/pkg/safe" + "github.com/golang/protobuf/ptypes/empty" + "github.com/siderolabs/talos/pkg/machinery/api/machine" + "github.com/siderolabs/talos/pkg/machinery/resources/network" +) + +func (c *TalosAPIConnection) osVersionInfo() (*machine.VersionInfo, error) { + resp, err := c.client.Version(c.ctx) + if err != nil || len(resp.Messages) == 0 { + return nil, err + } + + return resp.Messages[0].Version, nil +} + +// OSVersion returns the OS version. +func (c *TalosAPIConnection) OSVersion() string { + v, err := c.osVersionInfo() + if err != nil { + c.log.Error("error retrieving OS version information", "err", err) + + return "Talos unknown" + } + + return fmt.Sprintf("Talos %s-%s", v.Tag, v.Sha) +} + +// OSVersionShort returns the short OS version. +func (c *TalosAPIConnection) OSVersionShort() string { + v, err := c.osVersionInfo() + if err != nil { + c.log.Error("error retrieving OS version information", "err", err) + + return "Talos" + } + + return fmt.Sprintf("Talos %s", v.Tag) +} + +// Hostname returns the hostname. +func (c *TalosAPIConnection) Hostname() string { + resp, err := c.client.MachineClient.Hostname(c.ctx, &empty.Empty{}) + if err != nil || len(resp.Messages) == 0 { + c.log.Error("error retrieving hostname", "err", err) + + return "" + } + + return resp.Messages[0].Hostname +} + +// Uptime returns the uptime according to Talos in seconds. +func (c *TalosAPIConnection) Uptime() int { + resp, err := c.client.MachineClient.SystemStat(c.ctx, &empty.Empty{}) + if err != nil || len(resp.Messages) == 0 { + c.log.Error("error retrieving system stats", "err", err) + + return 0 + } + + return int(time.Since(time.Unix(int64(resp.Messages[0].GetBootTime()), 0)).Round(time.Second).Seconds()) +} + +// NetInterface represents a network interface. +type NetInterface struct { + Name string + Mac string + Addrs []netip.Prefix +} + +// NetInterfaces returns the network interfaces. +func (c *TalosAPIConnection) NetInterfaces() (result []NetInterface) { + addrMap := make(map[string][]*network.AddressStatusSpec) + + networkAddresses, err := safe.StateListAll[*network.AddressStatus](c.ctx, c.client.COSI) + if err != nil { + c.log.Error("error listing address status resources", "err", err) + + return nil + } + + for addr := range networkAddresses.All() { + linkName := addr.TypedSpec().LinkName + addrMap[linkName] = append(addrMap[linkName], addr.TypedSpec()) + } + + linkStatuses, err := safe.StateListAll[*network.LinkStatus](c.ctx, c.client.COSI) + if err != nil { + c.log.Error("error listing link status resources", "err", err) + + return nil + } + + for link := range linkStatuses.All() { + if !link.TypedSpec().Physical() { + continue + } + + intf := NetInterface{ + Name: link.Metadata().ID(), + Mac: link.TypedSpec().HardwareAddr.String(), + } + + for _, addr := range addrMap[intf.Name] { + intf.Addrs = append(intf.Addrs, addr.Address) + } + + result = append(result, intf) + } + + return result +} diff --git a/internal/talosconnection/ops.go b/internal/talosconnection/ops.go new file mode 100644 index 0000000..b3975c7 --- /dev/null +++ b/internal/talosconnection/ops.go @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +package talosconnection + +// Shutdown shuts down the machine. +func (c *TalosAPIConnection) Shutdown() error { + return c.client.Shutdown(c.ctx) +} + +// Reboot reboots the machine. +func (c *TalosAPIConnection) Reboot() error { + return c.client.Reboot(c.ctx) +} diff --git a/internal/tboxcmds/guestinfo.go b/internal/tboxcmds/guestinfo.go deleted file mode 100644 index 0bd14a2..0000000 --- a/internal/tboxcmds/guestinfo.go +++ /dev/null @@ -1,238 +0,0 @@ -package tboxcmds - -import ( - "bytes" - "fmt" - "net/netip" - "os" - "strconv" - - "github.com/sirupsen/logrus" - xdr "github.com/stellar/go-xdr/xdr3" - - "github.com/siderolabs/talos-vmtoolsd/internal/nanotoolbox" -) - -const ( - _ = iota - // GuestInfoDNSName is the guest info kind for the DNS name. - GuestInfoDNSName - _ // IP v1 - _ // free disk space - _ // build number - // GuestInfoOSNameFull is the guest info kind for the full OS name. - GuestInfoOSNameFull - // GuestInfoOSName is the guest info kind for the OS name. - GuestInfoOSName - // GuestInfoUptime is the guest uptime in 100s of seconds. - GuestInfoUptime - _ // memory - _ // IP v2 - // GuestInfoIPAddressV3 is the guest info kind for the IP address. - GuestInfoIPAddressV3 - _ // OS detailed -) - -const ( - unknownIP = "unknown" - maxNICs = 16 -) - -// NetInterface represents a network interface. -type NetInterface struct { - Name string - MAC string // xx:xx:xx:xx:xx:xx - Addrs []netip.Prefix -} - -// NicDelegate is the interface that must be implemented by the delegate. -type NicDelegate interface { - NetInterfaces() []NetInterface - Hostname() string - OSVersion() string - OSVersionShort() string -} - -// GuestInfoCommands provides a set of commands for the vmx. -type GuestInfoCommands struct { - log logrus.FieldLogger - out *nanotoolbox.ChannelOut - delegate NicDelegate -} - -// PrimaryIP returns the primary IP address. -func (cmd *GuestInfoCommands) PrimaryIP() string { - ifs := cmd.delegate.NetInterfaces() - if len(ifs) < 1 { - cmd.log.Warn("not sending primary IP: no interfaces received from upstream") - - return unknownIP - } - - addrs := ifs[0].Addrs - if len(addrs) < 1 { - cmd.log.Warn("not sending primary IP: first upstream adapter has no addresses") - - return unknownIP - } - - return addrs[0].String() -} - -// GuestNicInfo represents the guest NIC info. -func (cmd *GuestInfoCommands) GuestNicInfo() *GuestNicInfo { - // NB: this is polled by vSphere roughly every 30s - info := NewGuestNicInfo() - ifs := cmd.delegate.NetInterfaces() - - for _, nic := range ifs { - nicDesc := GuestNicV3{MacAddress: nic.MAC} - for _, addr := range nic.Addrs { - nicDesc.AddIP(addr) - cmd.log.Debugf("GuestNicInfo: adding name=%v mac=%v ip=%v", nic.Name, nic.MAC, addr) - } - - info.V3.Nics = append(info.V3.Nics, nicDesc) - if len(info.V3.Nics) >= maxNICs { - cmd.log.Debugf("GuestNicInfo: truncating NIC list to %v NICs", maxNICs) - - break - } - } - - if hostname := cmd.delegate.Hostname(); hostname != "" { - info.V3.DNSConfigInfo = &DNSConfigInfo{HostName: &hostname} - } - - return info -} - -// SendGuestInfo sends the guest info. -func (cmd *GuestInfoCommands) SendGuestInfo(kind int, buf []byte) { - // NB: intentionally using two spaces as separator to match open-vm-tools - msg := append([]byte(fmt.Sprintf("SetGuestInfo %d ", kind)), buf...) - if _, err := cmd.out.Request(msg); err != nil { - cmd.log.WithError(err).WithField("guest_info_kind", kind).Error("error sending guest info") - } -} - -// SendGuestInfoString sends the guest info string. -func (cmd *GuestInfoCommands) SendGuestInfoString(kind int, str string) { - cmd.SendGuestInfo(kind, []byte(str)) -} - -// SendGuestInfoXDR sends the guest info XDR. -func (cmd *GuestInfoCommands) SendGuestInfoXDR(kind int, v interface{}) { - var buf bytes.Buffer - - _, err := xdr.Marshal(&buf, v) - if err != nil { - cmd.log.WithError(err).WithField("guest_info_kind", kind).Error("error encoding guest info") - - return - } - - cmd.SendGuestInfo(kind, buf.Bytes()) -} - -// SendGuestInfoDNSName sends the guest info DNS name. -func (cmd *GuestInfoCommands) SendGuestInfoDNSName() { - if hostname := cmd.delegate.Hostname(); hostname != "" { - cmd.log.Debugf("sending hostname: %v", hostname) - cmd.SendGuestInfoString(GuestInfoDNSName, hostname) - } -} - -// SendGuestInfoOSNameFull sends the guest info OS full name. -func (cmd *GuestInfoCommands) SendGuestInfoOSNameFull() { - if name := cmd.delegate.OSVersion(); name != "" { - cmd.log.Debugf("sending OS full name: %v", name) - cmd.SendGuestInfoString(GuestInfoOSNameFull, name) - } -} - -// SendGuestInfoOSName sends the guest info OS name. -func (cmd *GuestInfoCommands) SendGuestInfoOSName() { - if name := cmd.delegate.OSVersionShort(); name != "" { - cmd.log.Debugf("sending OS short name: %v", name) - cmd.SendGuestInfoString(GuestInfoOSName, name) - } -} - -// GuestUptime represents the system uptime. -func (cmd *GuestInfoCommands) GuestUptime() int64 { - u, err := os.ReadFile("/proc/uptime") - if err != nil { - cmd.log.WithError(err).Error("error getting uptime") - - return -1 - } - - field := bytes.Fields(u)[0] - - uptime, err := strconv.ParseFloat(string(field), 64) - if err != nil { - cmd.log.WithError(err).Error("error getting uptime") - - return -1 - } - - return int64(uptime * 100) -} - -// SendGuestInfoUptime sends the guest uptime. -func (cmd *GuestInfoCommands) SendGuestInfoUptime() { - uptime := cmd.GuestUptime() - cmd.log.Debugf("sending uptime: %v", uptime) - cmd.SendGuestInfoString(GuestInfoUptime, fmt.Sprintf("%d", uptime)) -} - -// SendGuestInfoNIC sends the guest info NIC. -func (cmd *GuestInfoCommands) SendGuestInfoNIC() { - cmd.SendGuestInfoXDR(GuestInfoIPAddressV3, cmd.GuestNicInfo()) -} - -// BroadcastIPOptionHandler handles the broadcast IP option. -func (cmd *GuestInfoCommands) BroadcastIPOptionHandler(string, string) { - msg := fmt.Sprintf("info-set guestinfo.ip %s", cmd.PrimaryIP()) - if _, err := cmd.out.Request([]byte(msg)); err != nil { - cmd.log.WithError(err).Error("error sending IP message") - } - - cmd.SendGuestInfoNIC() -} - -// PushGuestInfo pushes the guest info. -func (cmd *GuestInfoCommands) PushGuestInfo() { - cmd.SendGuestInfoDNSName() - cmd.SendGuestInfoOSNameFull() - cmd.SendGuestInfoOSName() - cmd.SendGuestInfoUptime() - cmd.SendGuestInfoNIC() -} - -// PingHandler pushes updated guest info on ping request. -func (cmd *GuestInfoCommands) PingHandler([]byte) ([]byte, error) { - cmd.SendGuestInfoDNSName() - cmd.SendGuestInfoOSNameFull() - cmd.SendGuestInfoOSName() - cmd.SendGuestInfoUptime() - - return nil, nil -} - -// RegisterGuestInfoCommands registers the guest info commands. -func RegisterGuestInfoCommands(svc *nanotoolbox.Service, delegate NicDelegate) { - cmd := &GuestInfoCommands{ - log: svc.Log.WithField("module", "tboxcmds"), - out: svc.Out, - delegate: delegate, - } - svc.RegisterResetHandler(cmd.PushGuestInfo) - svc.RegisterOptionHandler("broadcastIP", cmd.BroadcastIPOptionHandler) - svc.RegisterCommandHandler("ping", cmd.PingHandler) - - // As stated in guestInfoServer.c, VMX expects uptime information in response - // to the capabilities request. - svc.AddCapability(fmt.Sprintf("SetGuestInfo %d %d", GuestInfoUptime, cmd.GuestUptime())) -} diff --git a/internal/tboxcmds/nicinfo.go b/internal/tboxcmds/nicinfo.go deleted file mode 100644 index 01dde2a..0000000 --- a/internal/tboxcmds/nicinfo.go +++ /dev/null @@ -1,125 +0,0 @@ -// This file was copied from govmomi/toolbox's guest_info.go. -// The original copyright notice follows. - -/* -Copyright (c) 2017 VMware, Inc. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Defs from: open-vm-tools/lib/guestRpc/nicinfo.x - -package tboxcmds - -import "net/netip" - -// TypedIPAddress represents an IP address with a type. -type TypedIPAddress struct { //nolint:govet - Type int32 - Address []byte -} - -// IPAddressEntry represents an IP address with prefix length, origin, and status. -type IPAddressEntry struct { //nolint:govet - Address TypedIPAddress - PrefixLength uint32 - Origin *int32 `xdr:"optional"` - Status *int32 `xdr:"optional"` -} - -// InetCidrRouteEntry represents a route entry. -type InetCidrRouteEntry struct { //nolint:govet - Dest TypedIPAddress - PrefixLength uint32 - NextHop *TypedIPAddress `xdr:"optional"` - IfIndex uint32 - Type int32 - Metric uint32 -} - -// DNSConfigInfo represents DNS configuration. -type DNSConfigInfo struct { //nolint:govet - HostName *string `xdr:"optional"` - DomainName *string `xdr:"optional"` - Servers []TypedIPAddress - Search *string `xdr:"optional"` -} - -// WinsConfigInfo represents WINS configuration. -type WinsConfigInfo struct { - Primary TypedIPAddress - Secondary TypedIPAddress -} - -// DhcpConfigInfo represents DHCP configuration. -type DhcpConfigInfo struct { //nolint:govet - Enabled bool - Settings string -} - -// GuestNicV3 represents NIC information. -type GuestNicV3 struct { //nolint:govet - MacAddress string - IPs []IPAddressEntry - DNSConfigInfo *DNSConfigInfo `xdr:"optional"` - WinsConfigInfo *WinsConfigInfo `xdr:"optional"` - DhcpConfigInfov4 *DhcpConfigInfo `xdr:"optional"` - DhcpConfigInfov6 *DhcpConfigInfo `xdr:"optional"` -} - -// GuestNicInfo represents NIC information. -type GuestNicInfo struct { //nolint:govet - Version int32 - V3 *NicInfoV3 `xdr:"optional"` -} - -// AddIP adds an IP address to the NIC. -func (nic *GuestNicV3) AddIP(prefix netip.Prefix) { - addr := prefix.Addr() - kind := int32(1) // IAT_IPV4 - - if addr.Is6() { - kind = 2 // IAT_IPV6 - } else if addr.Is4In6() { - addr = netip.AddrFrom4(addr.As4()) // convert to 4-byte representation - } - - // nicinfo.x defines enum IpAddressStatus, but vmtoolsd only uses IAS_PREFERRED - var status int32 = 1 // IAS_PREFERRED - - e := IPAddressEntry{ - Address: TypedIPAddress{ - Type: kind, - Address: addr.AsSlice(), - }, - PrefixLength: uint32(prefix.Bits()), - Status: &status, - } - - nic.IPs = append(nic.IPs, e) -} - -// NicInfoV3 contains NIC information. -type NicInfoV3 struct { //nolint:govet - Nics []GuestNicV3 - Routes []InetCidrRouteEntry - DNSConfigInfo *DNSConfigInfo `xdr:"optional"` - WinsConfigInfo *WinsConfigInfo `xdr:"optional"` - DhcpConfigInfov4 *DhcpConfigInfo `xdr:"optional"` - DhcpConfigInfov6 *DhcpConfigInfo `xdr:"optional"` -} - -// NewGuestNicInfo creates a new GuestNicInfo. -func NewGuestNicInfo() *GuestNicInfo { - return &GuestNicInfo{ - Version: 3, - V3: &NicInfoV3{}, - } -} diff --git a/internal/tboxcmds/power.go b/internal/tboxcmds/power.go deleted file mode 100644 index d36f768..0000000 --- a/internal/tboxcmds/power.go +++ /dev/null @@ -1,91 +0,0 @@ -package tboxcmds - -import ( - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/siderolabs/talos-vmtoolsd/internal/nanotoolbox" -) - -// vmware/guestrpc/powerops.h. -const ( - _ = iota - PowerStateHalt - PowerStateReboot - PowerStatePowerOn - PowerStateResume - PowerStateSuspend -) - -var powerCmdName = map[int]string{ - PowerStateHalt: "OS_Halt", - PowerStateReboot: "OS_Reboot", - PowerStatePowerOn: "OS_PowerOn", - PowerStateResume: "OS_Resume", - PowerStateSuspend: "OS_Suspend", -} - -// PowerDelegate is the interface that must be implemented by the delegate. -type PowerDelegate interface { - Shutdown() error - Reboot() error -} - -// PowerHandler is the type of the function that handles power operations. -type PowerHandler func() error - -type powerOp struct { //nolint:govet - log logrus.FieldLogger - out *nanotoolbox.ChannelOut - state int - handler PowerHandler -} - -func (op powerOp) Name() string { - return powerCmdName[op.state] -} - -func (op powerOp) HandleCommand([]byte) ([]byte, error) { - l := op.log.WithField("power_operation", op.Name()) - l.Debug("handling power operation") - - rc := nanotoolbox.RpciOK - - if op.handler != nil { - if err := op.handler(); err != nil { - l.WithError(err).Error("error handling power operation") - - rc = nanotoolbox.RpciERR - } - } - - msg := fmt.Sprintf("tools.os.statechange.status %s%d\x00", rc, op.state) - if _, err := op.out.Request([]byte(msg)); err != nil { - return nil, fmt.Errorf("error sending %q: %w", msg, err) - } - - return nil, nil -} - -func powerOpHandler(svc *nanotoolbox.Service, state int, handler PowerHandler) (string, nanotoolbox.CommandHandler) { - op := powerOp{ - log: svc.Log.WithField("command", "power"), - out: svc.Out, - state: state, - handler: handler, - } - - return op.Name(), op.HandleCommand -} - -// RegisterPowerDelegate registers the power operations with the service. -func RegisterPowerDelegate(svc *nanotoolbox.Service, delegate PowerDelegate) { - svc.AddCapability("tools.capability.statechange") - svc.AddCapability("tools.capability.softpowerop_retry") - svc.RegisterCommandHandler(powerOpHandler(svc, PowerStateHalt, delegate.Shutdown)) - svc.RegisterCommandHandler(powerOpHandler(svc, PowerStateReboot, delegate.Reboot)) - svc.RegisterCommandHandler(powerOpHandler(svc, PowerStatePowerOn, nil)) - svc.RegisterCommandHandler(powerOpHandler(svc, PowerStateSuspend, nil)) - svc.RegisterCommandHandler(powerOpHandler(svc, PowerStateResume, nil)) -} diff --git a/internal/tboxcmds/vix.go b/internal/tboxcmds/vix.go deleted file mode 100644 index d38a651..0000000 --- a/internal/tboxcmds/vix.go +++ /dev/null @@ -1,182 +0,0 @@ -// This file was adapted from govmomi/toolbox's command.go. -// The original copyright notice follows. - -/* -Copyright (c) 2017 VMware, Inc. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package tboxcmds provides a set of commands for the vmx. -package tboxcmds - -import ( - "bytes" - "encoding/base64" - "encoding/binary" - "fmt" - - "github.com/sirupsen/logrus" - "github.com/vmware/govmomi/toolbox/vix" - - "github.com/siderolabs/talos-vmtoolsd/internal/nanotoolbox" - "github.com/siderolabs/talos-vmtoolsd/internal/version" -) - -const ( - // VixToolsFeatureSupportGetHandleState defines the VixToolsFeatureSupportGetHandleState feature. - VixToolsFeatureSupportGetHandleState = 1 - // VixGuestOfFamilyLinux defines the VixGuestOfFamilyLinux family. - VixGuestOfFamilyLinux = 1 -) - -// VixCommandHandler is a function that handles a Vix command. -type VixCommandHandler func(vix.CommandRequestHeader, []byte) ([]byte, error) - -// VixDelegate is the interface that must be implemented by the delegate. -type VixDelegate interface { - OSVersion() string - OSVersionShort() string - Hostname() string -} - -// VixCommandServer provides a set of commands for the vmx. -type VixCommandServer struct { - log logrus.FieldLogger - out *nanotoolbox.ChannelOut - delegate VixDelegate - handlers map[uint32]VixCommandHandler -} - -// RegisterVixCommand registers the Vix command. -func RegisterVixCommand(svc *nanotoolbox.Service, delegate VixDelegate) { - svr := &VixCommandServer{ - log: svc.Log.WithField("command", "vix"), - out: svc.Out, - delegate: delegate, - } - svr.handlers = map[uint32]VixCommandHandler{vix.CommandGetToolsState: svr.GetToolsState} - svc.RegisterCommandHandler("Vix_1_Relayed_Command", svr.Dispatch) -} - -func commandResult(header vix.CommandRequestHeader, rc int, err error, response []byte) []byte { - // All Foundry tools commands return results that start with a foundry error - // and a guest-OS-specific error (e.g. errno) - errno := 0 - - if err != nil { - response = []byte(err.Error()) - } - - buf := bytes.NewBufferString(fmt.Sprintf("%d %d ", rc, errno)) - - if header.CommonFlags&vix.CommandGuestReturnsBinary != 0 { - // '#' delimits end of ascii and the start of the binary data (see ToolsDaemonTcloReceiveVixCommand) - _ = buf.WriteByte('#') - } - - _, _ = buf.Write(response) - - if header.CommonFlags&vix.CommandGuestReturnsBinary == 0 { - // this is not binary data, so it should be a NULL terminated string (see ToolsDaemonTcloReceiveVixCommand) - _ = buf.WriteByte(0) - } - - return buf.Bytes() -} - -// Dispatch dispatches the Vix command. -func (c *VixCommandServer) Dispatch(data []byte) ([]byte, error) { - // See ToolsDaemonTcloGetQuotedString - if data[0] == '"' { - data = data[1:] - } - - name := "" - - ix := bytes.IndexByte(data, '"') - if ix > 0 { - name = string(data[:ix]) - data = data[ix+1:] - } - - if data[0] == 0 { - data = data[1:] - } - - l := c.log.WithField("command_name", name) - - var header vix.CommandRequestHeader - - buf := bytes.NewBuffer(data) - - err := binary.Read(buf, binary.LittleEndian, &header) - if err != nil { - l.WithError(err).Print("decoding command failed") - - return nil, err - } - - if header.Magic != vix.CommandMagicWord { - l.Print("invalid magic header for command") - - return commandResult(header, vix.InvalidMessageHeader, nil, nil), nil - } - - handler, ok := c.handlers[header.OpCode] - if !ok { - l.Warn("unhandled command") - - return commandResult(header, vix.UnrecognizedCommandInGuest, nil, nil), nil - } - - rc := vix.OK - - response, err := handler(header, buf.Bytes()) - if err != nil { - l.WithError(err).Error("command handler failed") - rc = vix.ErrorCode(err) - } - - return commandResult(header, rc, err, response), nil -} - -// RegisterHandler registers the Vix command handler. -func (c *VixCommandServer) RegisterHandler(op uint32, handler VixCommandHandler) { - c.handlers[op] = handler -} - -// GetToolsState returns the tools state. -func (c *VixCommandServer) GetToolsState(_ vix.CommandRequestHeader, _ []byte) ([]byte, error) { - osVersion := c.delegate.OSVersion() - versionShort := c.delegate.OSVersionShort() - hostname := c.delegate.Hostname() - c.log.Debugf("sending tools state version=%q versionShort=%q hostname=%q", - osVersion, versionShort, hostname) - - props := vix.PropertyList{ - vix.NewStringProperty(vix.PropertyGuestOsVersion, osVersion), - vix.NewStringProperty(vix.PropertyGuestOsVersionShort, versionShort), - vix.NewStringProperty(vix.PropertyGuestToolsProductNam, "Talos Tools"), - vix.NewStringProperty(vix.PropertyGuestToolsVersion, version.Tag), - vix.NewStringProperty(vix.PropertyGuestName, hostname), - vix.NewInt32Property(vix.PropertyGuestToolsAPIOptions, VixToolsFeatureSupportGetHandleState), - vix.NewInt32Property(vix.PropertyGuestOsFamily, VixGuestOfFamilyLinux), - } - src, _ := props.MarshalBinary() //nolint:errcheck - enc := base64.StdEncoding - buf := make([]byte, enc.EncodedLen(len(src))) - enc.Encode(buf, src) - - return buf, nil -} diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 0000000..1905a74 --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +// Package util packages various utilities. +package util + +import ( + "context" + "log/slog" +) + +// log/slog does not implement trace logging by default, but is flexible. +const ( + LogLevelTrace = slog.Level(-8) +) + +// TraceLog sends trace-level logging to log/slog.Logger. +func TraceLog(l *slog.Logger, msg string, args ...any) { + l.Log(context.Background(), LogLevelTrace, msg, args...) +} diff --git a/internal/version/version.go b/internal/version/version.go index a3e8433..12672ac 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,10 +1,5 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. -// -// Generated on 2024-03-28T13:15:25Z by kres latest. +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 // Package version contains variables such as project name, tag and sha. It's a proper alternative to using // -ldflags '-X ...'. @@ -31,8 +26,14 @@ var ( } // Check if siderolabs project - if strings.HasPrefix(info.Path, "github.com/siderolabs/") { - return info.Path[strings.LastIndex(info.Path, "/")+1:] + prefix := "github.com/siderolabs/" + if strings.HasPrefix(info.Path, prefix) { + tail := info.Path[len(prefix):] + + before, _, found := strings.Cut(tail, "/") + if found { + return before + } } // We could return a proper full path here, but it could be seen as a privacy violation. diff --git a/internal/vmwlogger/logger.go b/internal/vmwlogger/logger.go new file mode 100644 index 0000000..f09ba38 --- /dev/null +++ b/internal/vmwlogger/logger.go @@ -0,0 +1,48 @@ +// This file was adapted from govmomi/toolbox's channel.go and backdoor.go. +// The original copyright notice follows. + +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +// Package vmwlogger is a wrapper to plug into github.com/vmware/vmw-guestinfo/message +package vmwlogger + +import ( + "fmt" + "log/slog" + + "github.com/siderolabs/talos-vmtoolsd/internal/util" +) + +// VMWareLogger is wrapper to make log/slog fit into https://pkg.go.dev/github.com/vmware/vmw-guestinfo@v0.0.0-20220317130741-510905f0efa3/message. +type VMWareLogger struct { + logger *slog.Logger +} + +// New initializes the wrapper around slog.Logger. +func New(logger *slog.Logger) *VMWareLogger { + l := &VMWareLogger{ + logger: logger, + } + + return l +} + +// Errorf logs an error. +func (v VMWareLogger) Errorf(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + v.logger.Error(msg) +} + +// Debugf logs debugging (acually trace info). +// We'll send debug statements from vmw-message to slog trace level. +func (v VMWareLogger) Debugf(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + util.TraceLog(v.logger, msg) +} + +// Infof logs informational messages. +func (v VMWareLogger) Infof(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + v.logger.Info(msg) +} diff --git a/internal/nanotoolbox/channel.go b/pkg/nanotoolbox/channel.go similarity index 74% rename from internal/nanotoolbox/channel.go rename to pkg/nanotoolbox/channel.go index 065ed0e..be2bc29 100644 --- a/internal/nanotoolbox/channel.go +++ b/pkg/nanotoolbox/channel.go @@ -1,21 +1,8 @@ // This file was adapted from govmomi/toolbox's channel.go and backdoor.go. // The original copyright notice follows. -/* -Copyright (c) 2017 VMware, Inc. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 // Package nanotoolbox provides a minimal set of tools for communicating with the vmx. package nanotoolbox @@ -24,6 +11,7 @@ import ( "bytes" "errors" "fmt" + "log/slog" "github.com/vmware/vmw-guestinfo/message" "github.com/vmware/vmw-guestinfo/vmcheck" @@ -81,11 +69,14 @@ func (c *ChannelOut) Request(request []byte) ([]byte, error) { type hypervisorChannel struct { //nolint:govet protocol uint32 + logger *slog.Logger *message.Channel } func (b *hypervisorChannel) Start() error { + b.logger.Debug("starting") + if !vmcheck.IsVirtualCPU() { return ErrNotVirtualWorld } @@ -101,6 +92,8 @@ func (b *hypervisorChannel) Start() error { } func (b *hypervisorChannel) Stop() error { + b.logger.Debug("stopping") + if b.Channel == nil { return nil } @@ -113,9 +106,15 @@ func (b *hypervisorChannel) Stop() error { } // NewHypervisorChannelPair returns a pair of channels for communicating with the vmx. -func NewHypervisorChannelPair() (Channel, Channel) { - in := &hypervisorChannel{protocol: tcloProtocol} - out := &hypervisorChannel{protocol: rpciProtocol} +func NewHypervisorChannelPair(logger *slog.Logger) (Channel, Channel) { + in := &hypervisorChannel{ + protocol: tcloProtocol, + logger: logger.With("dir", "in"), + } + out := &hypervisorChannel{ + protocol: rpciProtocol, + logger: logger.With("dir", "out"), + } return in, out } diff --git a/internal/nanotoolbox/service.go b/pkg/nanotoolbox/service.go similarity index 79% rename from internal/nanotoolbox/service.go rename to pkg/nanotoolbox/service.go index 1cca6df..761b136 100644 --- a/internal/nanotoolbox/service.go +++ b/pkg/nanotoolbox/service.go @@ -1,31 +1,19 @@ // This file was adapted from govmomi/toolbox's service.go. // The original copyright notice follows. -/* -Copyright (c) 2017 VMware, Inc. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 package nanotoolbox import ( "bytes" "fmt" + "log/slog" "sync" "time" - "github.com/sirupsen/logrus" + "github.com/siderolabs/talos-vmtoolsd/internal/util" ) const ( @@ -42,8 +30,8 @@ const ( // Service receives and dispatches incoming RPC requests from the vmx. type Service struct { //nolint:govet - Log logrus.FieldLogger - Out *ChannelOut + logger *slog.Logger + out *ChannelOut in Channel name string @@ -59,11 +47,11 @@ type Service struct { //nolint:govet } // NewService initializes a Service instance. -func NewService(log logrus.FieldLogger, rpcIn Channel, rpcOut Channel) *Service { +func NewService(log *slog.Logger, rpcIn Channel, rpcOut Channel) *Service { s := &Service{ - Log: log.WithField("module", "nanotoolbox"), - Out: &ChannelOut{rpcOut}, - in: rpcIn, + logger: log, + out: &ChannelOut{rpcOut}, + in: rpcIn, name: "toolbox", // same name used by vmtoolsd wg: new(sync.WaitGroup), @@ -84,6 +72,13 @@ func NewService(log logrus.FieldLogger, rpcIn Channel, rpcOut Channel) *Service return s } +// Request wraps ChannelOut.Request for demarcation/protection. +func (s *Service) Request(request []byte) ([]byte, error) { + util.TraceLog(s.logger, "requesting", "request", request) + + return s.out.Request(request) +} + // backoff exponentially increases the RPC poll delay up to maxDelay. func (s *Service) backoff() { if s.delay < maxDelay { @@ -102,7 +97,7 @@ func (s *Service) backoff() { func (s *Service) stopChannel() { s.in.Stop() //nolint:errcheck - s.Out.Stop() //nolint:errcheck + s.out.Stop() //nolint:errcheck } func (s *Service) startChannel() error { @@ -111,15 +106,17 @@ func (s *Service) startChannel() error { return err } - return s.Out.Start() + return s.out.Start() } func (s *Service) checkReset() error { if s.rpcError { + s.logger.Warn("resetting because of rpc error", "err", s.rpcError) s.stopChannel() err := s.startChannel() if err != nil { + s.logger.Error("error restarting channel", "err", err) s.delay = resetDelay return err @@ -162,9 +159,11 @@ func (s *Service) Start() error { } err = s.in.Send(response) + util.TraceLog(s.logger, "send", "err", err, "response", string(response)) response = nil if err != nil { + s.logger.Warn("send failed") s.delay = resetDelay s.rpcError = true @@ -172,11 +171,14 @@ func (s *Service) Start() error { } request, _ := s.in.Receive() //nolint:errcheck + util.TraceLog(s.logger, "received request", "request", string(request)) if len(request) > 0 { response = s.Dispatch(request) + util.TraceLog(s.logger, "response from dispatch", "request", string(request), "response", string(response)) s.delay = 0 } else { + util.TraceLog(s.logger, "backing off") s.backoff() } } @@ -204,39 +206,42 @@ type OptionHandler func(key, value string) // AddCapability adds a capability to the Service. func (s *Service) AddCapability(name string) { + s.logger.Debug("registering capability", "capability", name) s.capabilities = append(s.capabilities, name) } // RegisterCommandHandler adds a CommandHandler to the Service. func (s *Service) RegisterCommandHandler(name string, handler CommandHandler) { + s.logger.Debug("registering command handler", "command", name) s.commandHandlers[name] = handler } // RegisterOptionHandler adds an OptionHandler to the Service. func (s *Service) RegisterOptionHandler(key string, handler OptionHandler) { + s.logger.Debug("registering command handler", "option", key) s.optionHandlers[key] = handler } // RegisterResetHandler adds a function to be called when the Service is reset. func (s *Service) RegisterResetHandler(f func()) { + s.logger.Debug("registering a reset handler") s.resetHandlers = append(s.resetHandlers, f) } // Dispatch an incoming RPC request to a CommandHandler. func (s *Service) Dispatch(request []byte) []byte { + s.logger.Debug("dispatching", "request", string(request)) msg := bytes.SplitN(request, []byte{' '}, 2) name := msg[0] // Trim NULL byte terminator name = bytes.TrimRight(name, "\x00") - l := s.Log.WithField("handler_name", string(name)) - - l.Debug("incoming RPC request") + l := s.logger.With("handler_kind", string(name)) handler, ok := s.commandHandlers[string(name)] if !ok { - l.Debug("unknown command") + l.Debug("unknown command kind") return []byte("ERROR Unknown Command") } @@ -250,7 +255,7 @@ func (s *Service) Dispatch(request []byte) []byte { if err == nil { response = append([]byte("OK "), response...) } else { - l.WithError(err).Warn("error calling handler") + l.Warn("error calling handler", "err", err) response = append([]byte("ERROR "), response...) } @@ -288,7 +293,7 @@ func (s *Service) HandleSetOption(args []byte) ([]byte, error) { // HandleCapabilitiesRegister sends the Service's capabilities to the vmx. func (s *Service) HandleCapabilitiesRegister([]byte) ([]byte, error) { for _, capability := range s.capabilities { - _, err := s.Out.Request([]byte(capability)) + _, err := s.Request([]byte(capability)) if err != nil { return nil, fmt.Errorf("error sending %q: %w", capability, err) } diff --git a/pkg/vixserver/server.go b/pkg/vixserver/server.go new file mode 100644 index 0000000..a668a22 --- /dev/null +++ b/pkg/vixserver/server.go @@ -0,0 +1,129 @@ +// This file was adapted from govmomi/toolbox's command.go. +// The original copyright notice follows. + +// SPDX-FileCopyrightText: Copyright (c) 2020 Oliver Kuckertz, Siderolabs and Equinix +// SPDX-License-Identifier: Apache-2.0 + +// Package vixserver is a "VIX Command Server", it dispatches so called vix commands. It was adapted from govmomi's command.go. +package vixserver + +import ( + "bytes" + "encoding/binary" + "fmt" + "log/slog" + + "github.com/vmware/govmomi/toolbox/vix" + + "github.com/siderolabs/talos-vmtoolsd/internal/talosconnection" +) + +type vixCommandHandler func(vix.CommandRequestHeader, []byte) ([]byte, error) + +// VIXCommandServer is the command server itself. +type VIXCommandServer struct { + logger *slog.Logger + talos *talosconnection.TalosAPIConnection + registry map[uint32]vixCommandHandler +} + +// New constructs a fresh command server. +func New(logger *slog.Logger, talos *talosconnection.TalosAPIConnection) *VIXCommandServer { + v := &VIXCommandServer{ + logger: logger, + talos: talos, + registry: make(map[uint32]vixCommandHandler), + } + + return v +} + +// createVIXCommandResult is a convinience function that builds a VIX command result. +// it is adapted from govmomi's commandResult(). +func createVIXCommandResult(header vix.CommandRequestHeader, rc int, err error, response []byte) []byte { + // All Foundry tools commands return results that start with a foundry error + // and a guest-OS-specific error (e.g. errno) + errno := 0 + + if err != nil { + response = []byte(err.Error()) + } + + buf := bytes.NewBufferString(fmt.Sprintf("%d %d ", rc, errno)) + + if header.CommonFlags&vix.CommandGuestReturnsBinary != 0 { + // '#' delimits end of ascii and the start of the binary data (see ToolsDaemonTcloReceiveVixCommand) + _ = buf.WriteByte('#') + } + + _, _ = buf.Write(response) + + if header.CommonFlags&vix.CommandGuestReturnsBinary == 0 { + // this is not binary data, so it should be a NULL terminated string (see ToolsDaemonTcloReceiveVixCommand) + _ = buf.WriteByte(0) + } + + return buf.Bytes() +} + +// Dispatch dispatches a VIX command from toolbox. It is copied verbatim from govmomi. +func (v *VIXCommandServer) Dispatch(data []byte) ([]byte, error) { + // See ToolsDaemonTcloGetQuotedString + if data[0] == '"' { + data = data[1:] + } + + name := "" + + ix := bytes.IndexByte(data, '"') + if ix > 0 { + name = string(data[:ix]) + data = data[ix+1:] + } + + if data[0] == 0 { + data = data[1:] + } + + l := v.logger.With("command_name", name) + + var header vix.CommandRequestHeader + + buf := bytes.NewBuffer(data) + + err := binary.Read(buf, binary.LittleEndian, &header) + if err != nil { + l.Error("decoding command failed", "err", err) + + return nil, err + } + + if header.Magic != vix.CommandMagicWord { + l.Error("invalid magic header for command", "magic", header.Magic) + + return createVIXCommandResult(header, vix.InvalidMessageHeader, nil, nil), nil + } + + handler, ok := v.registry[header.OpCode] + if !ok { + l.Debug("unhandled command") // debug level, as ESX issues way more commands than we care for + + return createVIXCommandResult(header, vix.UnrecognizedCommandInGuest, nil, nil), nil + } + + rc := vix.OK + + response, err := handler(header, buf.Bytes()) + if err != nil { + l.Error("command handler failed", "err", err) + rc = vix.ErrorCode(err) + } + + return createVIXCommandResult(header, rc, err, response), nil +} + +// RegisterCommand registers a VIX command. +func (v *VIXCommandServer) RegisterCommand(command uint32, handler vixCommandHandler) { + v.logger.Info("registering vix command", "command", command) + v.registry[command] = handler +} diff --git a/talos-vmtoolsd.yaml b/talos-vmtoolsd.yaml index 31334d5..0159160 100644 --- a/talos-vmtoolsd.yaml +++ b/talos-vmtoolsd.yaml @@ -2,7 +2,10 @@ name: talos-vmtoolsd container: entrypoint: ./talos-vmtoolsd args: + - vmtoolsd - --use-machined + - --log-level + - debug mounts: - source: /system/run/machined/machine.sock destination: /system/run/machined/machine.sock