fix: support specifying patch file without '@' symbol

Try to be more smart while parsing `--config-patch` (and similar) flags:

* we still support inline patches
* if the flag value doesn't look like a patch, try to use it as a
  filename directly

This avoids common confusion with `--config-patch=patch.yaml` returning
an error "expected a mapping node".

Also clarify/updated documentation for `talosctl edit` and `talosctl
patch`, as they only work for the machineconfig, there is no other
usecase now.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2025-11-27 19:19:08 +04:00
parent e202b1f9e8
commit e2ee39b8ac
No known key found for this signature in database
GPG Key ID: 322C6F63F594CE7C
6 changed files with 48 additions and 18 deletions

View File

@ -178,11 +178,11 @@ func addEditingComment(in string) string {
// editCmd represents the edit command.
var editCmd = &cobra.Command{
Use: "edit <type> [<id>]",
Short: "Edit a resource from the default editor.",
Use: "edit machineconfig",
Short: "Edit Talos node machine configuration with the default editor.",
Args: cobra.RangeArgs(1, 2),
Long: `The edit command allows you to directly edit any API resource
you can retrieve via the command line tools.
Long: `The edit command allows you to directly edit the machine configuration
of a Talos node using your preferred text editor.
It will open the editor defined by your TALOS_EDITOR,
or EDITOR environment variables, or fall back to 'vi' for Linux

View File

@ -114,8 +114,8 @@ func patchFn(c *client.Client, patches []configpatcher.Patch) func(context.Conte
// patchCmd represents the edit command.
var patchCmd = &cobra.Command{
Use: "patch <type> [<id>]",
Short: "Update field(s) of a resource using a JSON patch.",
Use: "patch machineconfig",
Short: "Patch machine configuration of a Talos node with a local patch.",
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
return WithClient(func(ctx context.Context, c *client.Client) error {

View File

@ -52,7 +52,7 @@ func (suite *ApplyConfigSuite) TestApplyWithPatch() {
patchPath := filepath.Join(tmpDir, "patch.yaml")
suite.Require().NoError(os.WriteFile(patchPath, dummyAPPatch, 0o777))
suite.RunCLI([]string{"apply-config", "--nodes", node, "--config-patch", "@" + patchPath, "-f", configPath},
suite.RunCLI([]string{"apply-config", "--nodes", node, "--config-patch", patchPath, "-f", configPath},
base.StdoutEmpty(),
base.StderrNotEmpty(),
base.StderrShouldMatch(regexp.MustCompile("Applied configuration without a reboot")),
@ -71,12 +71,15 @@ func (suite *ApplyConfigSuite) TestApplyWithPatch() {
suite.Require().NoError(os.WriteFile(patchPath, deleteDummyAPPatch, 0o777))
suite.RunCLI([]string{"apply-config", "--nodes", node, "--config-patch", "@" + patchPath, "-f", configPath},
suite.RunCLI([]string{"apply-config", "--nodes", node, "--config-patch", patchPath, "-f", configPath},
base.StdoutEmpty(),
base.StderrNotEmpty(),
base.StderrShouldMatch(regexp.MustCompile("Applied configuration without a reboot")),
)
// sleep a bit to let the config propagate
time.Sleep(1 * time.Second)
suite.RunCLI([]string{"get", "--nodes", node, "links"},
base.StdoutShouldNotMatch(regexp.MustCompile("dummy-ap-patch")),
base.WithRetry(retry.Constant(15*time.Second, retry.WithUnits(time.Second))),

View File

@ -71,6 +71,10 @@ func LoadPatch(in []byte) (Patch, error) {
}
// LoadPatches loads the JSON patch either from value literal or from a file if the patch starts with '@'.
//
// It also tries to guess if the filename was given without '@' prefix.
//
//nolint:gocyclo
func LoadPatches(in []string) ([]Patch, error) {
var result []Patch
@ -81,14 +85,24 @@ func LoadPatches(in []string) ([]Patch, error) {
err error
)
if strings.HasPrefix(patchString, "@") {
switch {
case strings.HasPrefix(patchString, "@"):
filename := patchString[1:]
contents, err = os.ReadFile(filename)
if err != nil {
return result, err
}
} else {
case !strings.ContainsAny(patchString, "\n ") &&
!strings.HasPrefix(patchString, "[") &&
!strings.HasPrefix(patchString, "{"):
// any valid patch supplied inline should contain either '\n' or space, or start with '[' or '{'
// so if none of this is true, assume it's a filename, but without '@' prefix
contents, err = os.ReadFile(patchString)
if err != nil {
return result, err
}
default:
contents = []byte(patchString)
}

View File

@ -112,3 +112,16 @@ func TestLoadMixedPatches(t *testing.T) {
assert.Implements(t, (*configpatcher.StrategicMergePatch)(nil), patchList[1])
assert.IsType(t, jsonpatch.Patch{}, patchList[2])
}
func TestLoadStraightFilename(t *testing.T) {
patchList, err := configpatcher.LoadPatches([]string{
"testdata/strategic.yaml",
`[{"op":"replace","path":"/some","value": []}]`,
})
require.NoError(t, err)
require.Len(t, patchList, 2)
assert.Implements(t, (*configpatcher.StrategicMergePatch)(nil), patchList[0])
assert.IsType(t, jsonpatch.Patch{}, patchList[1])
}

View File

@ -940,19 +940,19 @@ talosctl dmesg [flags]
## talosctl edit
Edit a resource from the default editor.
Edit Talos node machine configuration with the default editor.
### Synopsis
The edit command allows you to directly edit any API resource
you can retrieve via the command line tools.
The edit command allows you to directly edit the machine configuration
of a Talos node using your preferred text editor.
It will open the editor defined by your TALOS_EDITOR,
or EDITOR environment variables, or fall back to 'vi' for Linux
or 'notepad' for Windows.
```
talosctl edit <type> [<id>] [flags]
talosctl edit machineconfig [flags]
```
### Options
@ -2572,10 +2572,10 @@ talosctl netstat [flags]
## talosctl patch
Update field(s) of a resource using a JSON patch.
Patch machine configuration of a Talos node with a local patch.
```
talosctl patch <type> [<id>] [flags]
talosctl patch machineconfig [flags]
```
### Options
@ -3248,7 +3248,7 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos
* [talosctl copy](#talosctl-copy) - Copy data out from the node
* [talosctl dashboard](#talosctl-dashboard) - Cluster dashboard with node overview, logs and real-time metrics
* [talosctl dmesg](#talosctl-dmesg) - Retrieve kernel logs
* [talosctl edit](#talosctl-edit) - Edit a resource from the default editor.
* [talosctl edit](#talosctl-edit) - Edit Talos node machine configuration with the default editor.
* [talosctl etcd](#talosctl-etcd) - Manage etcd
* [talosctl events](#talosctl-events) - Stream runtime events
* [talosctl gen](#talosctl-gen) - Generate CAs, certificates, and private keys
@ -3265,7 +3265,7 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos
* [talosctl meta](#talosctl-meta) - Write and delete keys in the META partition
* [talosctl mounts](#talosctl-mounts) - List mounts
* [talosctl netstat](#talosctl-netstat) - Show network connections and sockets
* [talosctl patch](#talosctl-patch) - Update field(s) of a resource using a JSON patch.
* [talosctl patch](#talosctl-patch) - Patch machine configuration of a Talos node with a local patch.
* [talosctl pcap](#talosctl-pcap) - Capture the network packets from the node.
* [talosctl processes](#talosctl-processes) - List running processes
* [talosctl read](#talosctl-read) - Read a file on the machine