1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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)
}