diff --git a/cmd/mcp/mcp.go b/cmd/mcp/mcp.go new file mode 100644 index 0000000..ba72c7d --- /dev/null +++ b/cmd/mcp/mcp.go @@ -0,0 +1,100 @@ +package mcp + +import ( + "fmt" + + "github.com/metal-stack/api/go/metalstack/admin/v2/adminv2mcp" + "github.com/metal-stack/api/go/metalstack/api/v2/apiv2mcp" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/v" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime" + "github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime/gosdk" + + "github.com/spf13/cobra" +) + +var long = fmt.Sprintf(` +Use %q to serve as mcp server. You must configure your coding agent to make use of the mcp server. + +Example opencode.json: + +"mcp": { + "metal": { + "type": "local", + "command": [ + %q, + "mcp" + ], + "enabled": true, + } +} + +Then login with %q and start you coding agent and ask questions like: + +- list all metal partitions +- give me all available metal sizes and images +- create a ip address, ask me questions +`, config.BinaryName, config.BinaryName, config.BinaryName) + +func NewMCPCmd(c *config.Config) *cobra.Command { + + cmd := &cobra.Command{ + Use: "mcp", + Short: "start mcp server", + Long: long, + RunE: func(cmd *cobra.Command, args []string) error { + raw, mcps := gosdk.NewServer("metal-stack.io mcp server", v.V.String()) + + forwardToClients(c, mcps) + + if err := raw.Run(cmd.Context(), &mcp.StdioTransport{}); err != nil { + return err + } + return nil + }, + } + return cmd +} + +func forwardToClients(c *config.Config, mcps runtime.MCPServer) { + // Be prepared for later opts. + var opts []runtime.Option + // API Clients + apiv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Apiv2().Audit(), opts...) + apiv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Apiv2().Filesystem(), opts...) + apiv2mcp.ForwardToHealthServiceClient(mcps, c.Client.Apiv2().Health(), opts...) + apiv2mcp.ForwardToImageServiceClient(mcps, c.Client.Apiv2().Image(), opts...) + apiv2mcp.ForwardToIPServiceClient(mcps, c.Client.Apiv2().IP(), opts...) + apiv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Apiv2().Machine(), opts...) + apiv2mcp.ForwardToMethodServiceClient(mcps, c.Client.Apiv2().Method(), opts...) + apiv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Apiv2().Network(), opts...) + apiv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Apiv2().Partition(), opts...) + apiv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Apiv2().Project(), opts...) + apiv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Apiv2().SizeImageConstraint(), opts...) + apiv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Apiv2().SizeReservation(), opts...) + apiv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Apiv2().Size(), opts...) + apiv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Apiv2().Tenant(), opts...) + apiv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Apiv2().Token(), opts...) + apiv2mcp.ForwardToUserServiceClient(mcps, c.Client.Apiv2().User(), opts...) + apiv2mcp.ForwardToVersionServiceClient(mcps, c.Client.Apiv2().Version(), opts...) + + // Admin Clients + adminv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Adminv2().Audit(), opts...) + adminv2mcp.ForwardToComponentServiceClient(mcps, c.Client.Adminv2().Component(), opts...) + adminv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Adminv2().Filesystem(), opts...) + adminv2mcp.ForwardToImageServiceClient(mcps, c.Client.Adminv2().Image(), opts...) + adminv2mcp.ForwardToIPServiceClient(mcps, c.Client.Adminv2().IP(), opts...) + adminv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Adminv2().Machine(), opts...) + adminv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Adminv2().Network(), opts...) + adminv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Adminv2().Partition(), opts...) + adminv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Adminv2().Project(), opts...) + adminv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Adminv2().SizeImageConstraint(), opts...) + adminv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Adminv2().SizeReservation(), opts...) + adminv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Adminv2().Size(), opts...) + adminv2mcp.ForwardToSwitchServiceClient(mcps, c.Client.Adminv2().Switch(), opts...) + adminv2mcp.ForwardToTaskServiceClient(mcps, c.Client.Adminv2().Task(), opts...) + adminv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Adminv2().Tenant(), opts...) + adminv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Adminv2().Token(), opts...) + adminv2mcp.ForwardToVPNServiceClient(mcps, c.Client.Adminv2().VPN(), opts...) +} diff --git a/cmd/mcp/mcp_test.go b/cmd/mcp/mcp_test.go new file mode 100644 index 0000000..144ce8e --- /dev/null +++ b/cmd/mcp/mcp_test.go @@ -0,0 +1,47 @@ +package mcp + +import ( + "log" + "os/exec" + "testing" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/require" +) + +func TestMCPServer(t *testing.T) { + t.SkipNow() + ctx := t.Context() + // Create a new client, with no features. + client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil) + + // Connect to a server over stdin/stdout. + transport := &mcp.CommandTransport{Command: exec.Command("/home/stefan/bin/metalctlv2", "mcp")} + session, err := client.Connect(ctx, transport, nil) + require.NoError(t, err) + defer func() { + _ = session.Close() + }() + + tools, err := session.ListTools(ctx, &mcp.ListToolsParams{}) + require.NoError(t, err) + for _, tool := range tools.Tools { + t.Logf("%s", tool.Name) + t.Logf("%#v", tool) + } + + // Call a tool on the server. + params := &mcp.CallToolParams{ + Name: "metalstack_api_v2_PartitionService_List", + Arguments: map[string]any{}, + } + res, err := session.CallTool(ctx, params) + require.NoError(t, err) + if res.IsError { + t.Logf("tool failed %v", res.Content) + } + for _, c := range res.Content { + log.Print(c.(*mcp.TextContent).Text) + } + t.Fail() +} diff --git a/cmd/root.go b/cmd/root.go index 821a391..a0c1ffc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,6 +9,7 @@ import ( adminv2 "github.com/metal-stack/cli/cmd/admin/v2" apiv2 "github.com/metal-stack/cli/cmd/api/v2" + "github.com/metal-stack/cli/cmd/mcp" "github.com/metal-stack/cli/cmd/completion" "github.com/metal-stack/cli/cmd/config" @@ -98,7 +99,7 @@ func NewRootCmd(c *config.Config) *cobra.Command { }, } - rootCmd.AddCommand(newContextCmd(c), markdownCmd, newLoginCmd(c), newLogoutCmd(c)) + rootCmd.AddCommand(newContextCmd(c), markdownCmd, newLoginCmd(c), newLogoutCmd(c), mcp.NewMCPCmd(c)) adminv2.AddCmds(rootCmd, c) apiv2.AddCmds(rootCmd, c) diff --git a/docs/metalctlv2.md b/docs/metalctlv2.md index a4524aa..91c7774 100644 --- a/docs/metalctlv2.md +++ b/docs/metalctlv2.md @@ -29,6 +29,7 @@ cli for managing entities in metal-stack * [metalctlv2 logout](metalctlv2_logout.md) - logout * [metalctlv2 machine](metalctlv2_machine.md) - manage machine entities * [metalctlv2 markdown](metalctlv2_markdown.md) - create markdown documentation +* [metalctlv2 mcp](metalctlv2_mcp.md) - start mcp server * [metalctlv2 network](metalctlv2_network.md) - manage network entities * [metalctlv2 partition](metalctlv2_partition.md) - manage partition entities * [metalctlv2 project](metalctlv2_project.md) - manage project entities diff --git a/docs/metalctlv2_mcp.md b/docs/metalctlv2_mcp.md new file mode 100644 index 0000000..34a5b91 --- /dev/null +++ b/docs/metalctlv2_mcp.md @@ -0,0 +1,56 @@ +## metalctlv2 mcp + +start mcp server + +### Synopsis + + +Use "metalctlv2" to serve as mcp server. You must configure your coding agent to make use of the mcp server. + +Example opencode.json: + +"mcp": { + "metal": { + "type": "local", + "command": [ + "metalctlv2", + "mcp" + ], + "enabled": true, + } +} + +Then login with "metalctlv2" and start you coding agent and ask questions like: + +- list all metal partitions +- give me all available metal sizes and images +- create a ip address, ask me questions + + +``` +metalctlv2 mcp [flags] +``` + +### Options + +``` + -h, --help help for mcp +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2](metalctlv2.md) - cli for managing entities in metal-stack + diff --git a/go.mod b/go.mod index eb2fabe..9ad3bcc 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,11 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.4.4 + github.com/metal-stack/api v0.4.5-0.20260817075741-42bd06831657 github.com/metal-stack/metal-lib v0.26.3 github.com/metal-stack/v v1.0.3 + github.com/modelcontextprotocol/go-sdk v1.7.0 + github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 @@ -23,6 +25,7 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260709200747-435963d16310.1 // indirect + buf.build/gen/go/redpandadata/common/protocolbuffers/go v1.36.12-20260323171043-6e06f84ad823.1 // indirect buf.build/go/protovalidate v1.3.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect cel.dev/expr v0.25.3 // indirect @@ -54,6 +57,7 @@ require ( github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.31.0 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -75,20 +79,24 @@ require ( github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pires/go-proxyproto v0.15.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/redpanda-data/common-go/api v0.0.0-20260805234448-c124c21fdb6b // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/safchain/ethtool v0.7.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/segmentio/encoding v0.5.4 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect - github.com/tailscale/hujson v0.0.0-20260727124030-b80ff77dac4f // indirect + github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect - github.com/tailscale/web-client-prebuilt v0.0.0-20251127225136-f19339b67368 // indirect + github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect @@ -103,7 +111,7 @@ require ( golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - golang.zx2c4.com/wireguard/windows v1.0.1 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260810153831-ec0a7760b754 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 11b6c18..ff08a20 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260709200747-435963d16310.1 h1:6nlcxMOui23ZRVAfJM451duu79P1npA5JRdZqMilrrQ= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260709200747-435963d16310.1/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/gen/go/redpandadata/common/protocolbuffers/go v1.36.12-20260323171043-6e06f84ad823.1 h1:QU5JhJgMt4e6soYJ1owLNL0b5l6uWLLujetFOmgA214= +buf.build/gen/go/redpandadata/common/protocolbuffers/go v1.36.12-20260323171043-6e06f84ad823.1/go.mod h1:1O+pTOUPSosYIhh9BbmQqfL455/rzSEtTjGyrZiDKdc= buf.build/go/protovalidate v1.3.0 h1:8ITcnZGkAHx6TyhZvro+iET/AyqU8gEWQJK2WsT62ms= buf.build/go/protovalidate v1.3.0/go.mod h1:82s5g+rFRj1CZPiLv6OTA31jBu2fpq7mLXHwa9mZfEs= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= @@ -147,6 +149,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I= github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -193,8 +197,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= -github.com/metal-stack/api v0.4.4 h1:NwJKCFHnbmsU7DjHLtBmpdPuPniXc2wDabHjdpXk3qA= -github.com/metal-stack/api v0.4.4/go.mod h1:E7f2GkKNSr4vBxhQwWrr/Mjnf4ctPqCX2dpDtXBqkBk= +github.com/metal-stack/api v0.4.5-0.20260817075741-42bd06831657 h1:vMGuCEIueV/NjSDLTrS/tmgZJKKJENkxjpHJpxVfiE8= +github.com/metal-stack/api v0.4.5-0.20260817075741-42bd06831657/go.mod h1:avT8GxSRpujhE+xEr9sTdN0+QbwpDuOOaZ/JKkVsBkA= github.com/metal-stack/metal-lib v0.26.3 h1:K5gLoD65m6p3l6qCPrfavIdvNdWfmF2QdXrvU2URaZs= github.com/metal-stack/metal-lib v0.26.3/go.mod h1:cNXjPBs8SFnjqfBobuSbm5mDk6E/jS8PVeIOrV/7POE= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -205,6 +209,8 @@ github.com/minio/minlz v1.2.0 h1:6IOBuiHg04QxvbFfgFLT/9sMaO/UhL7S+ApW1mK8q5A= github.com/minio/minlz v1.2.0/go.mod h1:Ls9H7nlkASeCcdl5thjVD5Eraj6z+zGa7xtq57jIKD4= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44= +github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= @@ -219,6 +225,8 @@ github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8= github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw= github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= @@ -234,6 +242,10 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.69.0 h1:OA85nJQS/T/MaYh/Q2CcgDKSGWqNIgrBDvDH85CuiNk= github.com/prometheus/common v0.69.0/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= +github.com/redpanda-data/common-go/api v0.0.0-20260805234448-c124c21fdb6b h1:37u906bF7SsdlHuFwkDjFY/NAe6vKkMJUMdGNB9FBdY= +github.com/redpanda-data/common-go/api v0.0.0-20260805234448-c124c21fdb6b/go.mod h1:X4iTnBNz6LJ7KwyOkW/7xh+pBS8tuqFWUwECPkVDjIo= +github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6 h1:ThmYtRYkopbcf1vhKlsE6OEMQWHl1VrIxk9ZgLqJpgw= +github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6/go.mod h1:qCv9zvs6wyK9+EyY0auYApUdOEgi6TVM6aWQ7Byahuc= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -244,6 +256,10 @@ github.com/safchain/ethtool v0.7.0 h1:rlJzfDetsVvT61uz8x1YIcFn12akMfuPulHtZjtb7I github.com/safchain/ethtool v0.7.0/go.mod h1:MenQKEjXdfkjD3mp2QdCk8B/hwvkrlOTm/FD4gTpFxQ= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -271,14 +287,14 @@ github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8 github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= github.com/tailscale/golang-x-crypto v0.0.0-20260720153645-2ba0bf7866ed h1:uyvHhX1FQada0vVk8CSHa4tJT96EEAkTypaYz8Tq5Nc= github.com/tailscale/golang-x-crypto v0.0.0-20260720153645-2ba0bf7866ed/go.mod h1:NC3xRCu4UR+m4n6ix8b6oLLbHa820Y0StbOQEdWTDo0= -github.com/tailscale/hujson v0.0.0-20260727124030-b80ff77dac4f h1:9hiVElpCmKzsBKQHkBqZ8LGzt82iLfM8egxr4sew+Ys= -github.com/tailscale/hujson v0.0.0-20260727124030-b80ff77dac4f/go.mod h1:8/zr1Tv0+cKpVtGCEB/7YfRXr2TszsMxMXLaT8YuBgU= +github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA= +github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20251127225136-f19339b67368 h1:0tpDdAj9sSfSZg4gMwNTdqMP592sBrq2Sm0w6ipnh7k= -github.com/tailscale/web-client-prebuilt v0.0.0-20251127225136-f19339b67368/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0 h1:CnIEL2n7Xql6Ux1k+Vu5S5ubDHCT/kxFgkKCY8FjefU= @@ -295,6 +311,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= @@ -335,8 +353,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446 h1:cqHQ3AycTHvM2R7ikgyX57D+XvtcSnGylsLkOVhta/w= golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= -golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8= -golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= google.golang.org/genproto/googleapis/api v0.0.0-20260810153831-ec0a7760b754 h1:dWeMvEJ3JhYgqSCAHUZZJgMUyfniiiCvDc72x5EqJP0= google.golang.org/genproto/googleapis/api v0.0.0-20260810153831-ec0a7760b754/go.mod h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks= google.golang.org/genproto/googleapis/rpc v0.0.0-20260810153831-ec0a7760b754 h1:k5CJw9e5ONCcA/u0webKt092npXuY+KeGh3Q8NAVf0g= diff --git a/pkg/helpers/machine.go b/pkg/helpers/machine.go index 1cd547d..e54b6ed 100644 --- a/pkg/helpers/machine.go +++ b/pkg/helpers/machine.go @@ -153,6 +153,10 @@ func MachineCreateRequestFromCLI(c *config.Config) (*apiv2.MachineServiceCreateR if err != nil { return nil, err } + placementLabels, err := LabelsFromSlice(viper.GetStringSlice("placement-tags")) + if err != nil { + return nil, err + } var filesystemlayout *string if viper.IsSet("filesystem-layout") { @@ -190,7 +194,7 @@ func MachineCreateRequestFromCLI(c *config.Config) (*apiv2.MachineServiceCreateR DnsServers: dnsServers, NtpServers: ntpServers, FilesystemLayout: filesystemlayout, - PlacementTags: viper.GetStringSlice("placement-tags"), + PlacementLabels: placementLabels, AllocationType: allocationType, FirewallSpec: firewallSpec, }, nil diff --git a/tests/e2e/admin/machine_test.go b/tests/e2e/admin/machine_test.go index efa33e3..d6c4787 100644 --- a/tests/e2e/admin/machine_test.go +++ b/tests/e2e/admin/machine_test.go @@ -208,7 +208,9 @@ func Test_MachineCmd_Create(t *testing.T) { return nws }(), - PlacementTags: []string{"cluster-id=cluster-uuid"}, + PlacementLabels: &apiv2.Labels{ + Labels: map[string]string{"cluster-id": "cluster-uuid"}, + }, DnsServers: []*apiv2.DNSServer{{Ip: "1.1.1.1"}}, NtpServers: []*apiv2.NTPServer{{Address: "2.2.2.2"}, {Address: "3.3.3.3"}}, AllocationType: apiv2.MachineAllocationType_MACHINE_ALLOCATION_TYPE_MACHINE, diff --git a/tests/e2e/api/machine_test.go b/tests/e2e/api/machine_test.go index 079a713..7a7ad0d 100644 --- a/tests/e2e/api/machine_test.go +++ b/tests/e2e/api/machine_test.go @@ -182,7 +182,9 @@ func Test_MachineCmd_Create(t *testing.T) { return nws }(), - PlacementTags: []string{"cluster-id=cluster-uuid"}, + PlacementLabels: &apiv2.Labels{ + Labels: map[string]string{"cluster-id": "cluster-uuid"}, + }, DnsServers: []*apiv2.DNSServer{{Ip: "1.1.1.1"}}, NtpServers: []*apiv2.NTPServer{{Address: "2.2.2.2"}, {Address: "3.3.3.3"}}, AllocationType: apiv2.MachineAllocationType_MACHINE_ALLOCATION_TYPE_MACHINE,