package nodeagent import ( "bufio" "bytes" "context" "fmt" "os" "os/exec" "path/filepath" "strings" ) func writeAtomic(path string, data []byte, mode os.FileMode) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o700); err != nil { return fmt.Errorf("nodeagent: creating %s: %w", dir, err) } tmp, err := os.CreateTemp(dir, "."+filepath.Base(path)+".tmp-*") if err != nil { return fmt.Errorf("nodeagent: creating a temporary file beside %s: %w", path, err) } name := tmp.Name() keep := false defer func() { if !keep { os.Remove(name) } }() if _, err := tmp.Write(data); err != nil { tmp.Close() return fmt.Errorf("nodeagent: writing %s: %w", path, err) } if err := tmp.Chmod(mode); err != nil { tmp.Close() return fmt.Errorf("nodeagent: setting the mode of %s: %w", path, err) } if err := tmp.Sync(); err != nil { tmp.Close() return fmt.Errorf("nodeagent: syncing %s: %w", path, err) } if err := tmp.Close(); err != nil { return fmt.Errorf("nodeagent: closing %s: %w", path, err) } if err := os.Rename(name, path); err != nil { return fmt.Errorf("nodeagent: renaming %s into place: %w", path, err) } keep = true return nil } func readEnvFile(path string) (map[string]string, error) { file, err := os.Open(path) if err != nil { if os.IsNotExist(err) { return map[string]string{}, nil } return nil, err } defer file.Close() out := map[string]string{} scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() key, value, ok := strings.Cut(line, "=") if !ok { continue } if _, seen := out[key]; seen { continue } out[key] = value } if err := scanner.Err(); err != nil { return nil, err } return out, nil } func fileNonEmpty(path string) bool { st, err := os.Stat(path) return err == nil && st.Mode().IsRegular() && st.Size() > 0 } func ensureDir(path string, mode os.FileMode) error { if err := os.MkdirAll(path, mode); err != nil { return err } return os.Chmod(path, mode) } func executable(path string) bool { st, err := os.Stat(path) return err == nil && st.Mode().IsRegular() && st.Mode()&0o111 != 0 } func run(ctx context.Context, name string, args ...string) ([]byte, error) { cmd := exec.CommandContext(ctx, name, args...) var stderr bytes.Buffer cmd.Stderr = &stderr out, err := cmd.Output() if err != nil { msg := strings.TrimSpace(stderr.String()) if msg == "" { return out, fmt.Errorf("nodeagent: %s %s: %w", name, strings.Join(args, " "), err) } return out, fmt.Errorf("nodeagent: %s %s: %s", name, strings.Join(args, " "), msg) } return out, nil } func stripSpace(value string) string { return strings.Map(func(r rune) rune { switch r { case ' ', '\t', '\n', '\r', '\v', '\f': return -1 } return r }, value) } func firstValue(values map[string]any, key string) (string, bool) { value, ok := values[key] if !ok { return "", false } return valueString(value) }