Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Talos metadata writer tool
# Talos metadata tool

Tool for writing network metadata in the Talos META partition.
Tool for reading and writing network metadata in the Talos META partition.

Doc: https://docs.siderolabs.com/talos/v1.13/platform-specific-installations/bare-metal-platforms/metal-network-configuration

Expand All @@ -12,6 +12,9 @@ GOOS=linux GOARCH=amd64 go build -o talos-meta-tool .

Usage:
```bash
# read current config
talos-meta-tool -device /dev/sda -read

# write config from file
talos-meta-tool -device /dev/sda -config config.yaml

Expand Down
39 changes: 35 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ func writeConfig(dev interface{ io.ReaderAt; io.WriterAt }, configData []byte) e
return nil
}

func readConfig(dev io.ReaderAt) ([]byte, error) {
adv, err := talos.NewADV(io.NewSectionReader(dev, 0, int64(talos.Size)))
if err != nil {
return nil, fmt.Errorf("loading ADV: %w", err)
}
data, ok := adv.ReadTagBytes(FixedTag)
if !ok {
return nil, fmt.Errorf("tag %#x not found", FixedTag)
}
return data, nil
}

func loadConfig(path, envVar string, b64 bool) ([]byte, error) {
if envVar != "" {
val, ok := os.LookupEnv(envVar)
Expand Down Expand Up @@ -119,18 +131,23 @@ func loadConfig(path, envVar string, b64 bool) ([]byte, error) {

func main() {
devicePath := flag.String("device", "", "Path to the disk device (e.g., /dev/sda)")
read := flag.Bool("read", false, "Read and print the current configuration from the META partition")
configPath := flag.String("config", "", "Path to the configuration file (e.g., config.yaml)")
configEnv := flag.String("config-env", "", "Name of the environment variable containing the configuration")
configEnvBase64 := flag.Bool("config-env-base64", false, "Decode the -config-env value as base64 before use")
skipValidation := flag.Bool("skip-validation", false, "Skip schema validation of the configuration file")
flag.Parse()

if *devicePath == "" {
fmt.Fprintln(os.Stderr, "Usage: talos-meta-tool -device <disk-device> (-config <file> | -config-env <VAR> [-config-env-base64])")
fmt.Fprintln(os.Stderr, "Usage: talos-meta-tool -device <disk-device> (-read | -config <file> | -config-env <VAR> [-config-env-base64])")
os.Exit(1)
}
Comment on lines 141 to 144

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Validating command-line flags immediately after parsing is a best practice. Currently, if a user provides invalid flags (e.g., missing both -read and -config), the tool will still attempt to open the device with os.O_RDWR and read the GPT partition table before validating the flags and exiting. This can cause unnecessary permission errors or side-effects.

Additionally, this ensures that -read is explicitly validated as mutually exclusive with -config and -config-env rather than silently ignoring them.

	if *devicePath == "" {
		fmt.Fprintln(os.Stderr, "Usage: talos-meta-tool -device <disk-device> (-read | -config <file> | -config-env <VAR> [-config-env-base64])")
		os.Exit(1)
	}
	if !*read && *configPath == "" && *configEnv == "" {
		fmt.Fprintln(os.Stderr, "Usage: talos-meta-tool -device <disk-device> (-read | -config <file> | -config-env <VAR> [-config-env-base64])")
		os.Exit(1)
	}
	if *read && (*configPath != "" || *configEnv != "") {
		fmt.Fprintln(os.Stderr, "Error: -read is mutually exclusive with -config and -config-env")
		os.Exit(1)
	}
	if *configPath != "" && *configEnv != "" {
		fmt.Fprintln(os.Stderr, "Error: -config and -config-env are mutually exclusive")
		os.Exit(1)
	}
	if *configEnvBase64 && *configEnv == "" {
		fmt.Fprintln(os.Stderr, "Error: -config-env-base64 requires -config-env")
		os.Exit(1)
	}

if *configPath == "" && *configEnv == "" {
fmt.Fprintln(os.Stderr, "Usage: talos-meta-tool -device <disk-device> (-config <file> | -config-env <VAR> [-config-env-base64])")
if !*read && *configPath == "" && *configEnv == "" {
fmt.Fprintln(os.Stderr, "Usage: talos-meta-tool -device <disk-device> (-read | -config <file> | -config-env <VAR> [-config-env-base64])")
os.Exit(1)
}
if *read && (*configPath != "" || *configEnv != "") {
fmt.Fprintln(os.Stderr, "Error: -read is mutually exclusive with -config and -config-env")
os.Exit(1)
}
if *configPath != "" && *configEnv != "" {
Expand All @@ -142,7 +159,12 @@ func main() {
os.Exit(1)
}

device, err := os.OpenFile(*devicePath, os.O_RDWR, 0)
openMode := os.O_RDWR
if *read {
openMode = os.O_RDONLY
}

device, err := os.OpenFile(*devicePath, openMode, 0)
if err != nil {
log.Fatalf("Error opening device: %v", err)
}
Expand All @@ -153,6 +175,15 @@ func main() {
log.Fatalf("Error: %v", err)
}

if *read {
data, err := readConfig(meta)
if err != nil {
log.Fatalf("Error reading config: %v", err)
}
Comment on lines +178 to +182
fmt.Print(string(data))
return
}

configData, err := loadConfig(*configPath, *configEnv, *configEnvBase64)
if err != nil {
log.Fatalf("loading configuration: %v", err)
Expand Down
33 changes: 33 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,36 @@ func TestWriteConfigFullDisk(t *testing.T) {
t.Fatalf("tag value: got %q, want %q", got, payload)
}
}

func TestReadConfigRoundTrip(t *testing.T) {
f := newTestFile(t)

payload := []byte("key: value\n")

if err := writeConfig(f, payload); err != nil {
t.Fatalf("writeConfig: %v", err)
}

got, err := readConfig(f)
if err != nil {
t.Fatalf("readConfig: %v", err)
}

if !bytes.Equal(got, payload) {
t.Fatalf("readConfig: got %q, want %q", got, payload)
}
}

func TestReadConfigEmpty(t *testing.T) {
f := newTestFile(t)

if _, err := readConfig(f); err == nil {
t.Fatal("expected error reading from empty ADV, got nil")
}
}

func TestReadConfigBadDevice(t *testing.T) {
if _, err := readConfig(errDevice{}); err == nil {
t.Fatal("expected error for bad device, got nil")
}
}
Loading