carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "errors"
7 "fmt"
8 "log"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "strings"
13 "testing"
14
15 "github.com/carlosframework/platform/internal/rootstate"
16)
17
18func TestRunAdoptDisabledClaimsArmsSystemUnitsBeforeRegistryOpenFailure(t *testing.T) {
19 if os.Getenv("CARLOS_TEST_ADOPT_ORDER_HELPER") == "1" {
20 os.Unsetenv("CARLOS_HOST_CLAIM")
21 probe := func(args ...string) (string, error) {
22 switch args[0] {
23 case "is-active":
24 return "inactive\n", errors.New("exit status 3")
25 case "is-enabled":
26 return "disabled\n", errors.New("exit status 1")
27 }
28 return "", nil
29 }
30 systemctl := func(args ...string) (string, error) {
31 f, err := os.OpenFile(os.Getenv("CARLOS_TEST_ADOPT_SYSTEMCTL_CALLS"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
32 if err != nil {
33 return "", err
34 }
35 defer f.Close()
36 _, err = fmt.Fprintln(f, strings.Join(args, " "))
37 return "", err
38 }
39 ensure := func() {
40 ensureSystemUnits(
41 os.Getenv("CARLOS_TEST_ADOPT_ETC"),
42 os.Getenv("CARLOS_TEST_ADOPT_SYSTEM_LOCK"),
43 systemctl,
44 probe,
45 log.Printf,
46 )
47 }
48 runAdoptWithEnsureSystemUnits([]string{"-registry", os.Getenv("CARLOS_TEST_ADOPT_REGISTRY")}, nil, ensure,
49 noCensusAdoptSource(func() (rootstate.Store, error) { return &plan14ReleaseStore{}, nil }))
50 return
51 }
52
53 root := t.TempDir()
54 if err := os.MkdirAll(filepath.Join(root, "data"), 0o755); err != nil {
55 t.Fatal(err)
56 }
57 blocker := filepath.Join(root, "not-a-directory")
58 if err := os.WriteFile(blocker, []byte("block registry parent"), 0o600); err != nil {
59 t.Fatal(err)
60 }
61 registryPath := filepath.Join(blocker, "registry.db")
62 etc := filepath.Join(root, "etc")
63 callsPath := filepath.Join(root, "systemctl.calls")
64 cmd := exec.Command(os.Args[0], "-test.run=^TestRunAdoptDisabledClaimsArmsSystemUnitsBeforeRegistryOpenFailure$", "-test.v")
65 cmd.Dir = root
66 cmd.Env = isolatedAdoptOrderEnv(t,
67 "CARLOS_HOST_CLAIM=",
68 "CARLOS_DATA_DIR="+filepath.Join(root, "data"),
69 "CARLOS_OPT_DIR="+filepath.Join(root, "opt"),
70 "CARLOS_TEST_ADOPT_ORDER_HELPER=1",
71 "CARLOS_TEST_ADOPT_REGISTRY="+registryPath,
72 "CARLOS_TEST_ADOPT_ETC="+etc,
73 "CARLOS_TEST_ADOPT_SYSTEM_LOCK="+filepath.Join(root, "system.lock"),
74 "CARLOS_TEST_ADOPT_SYSTEMCTL_CALLS="+callsPath,
75 )
76 out, err := cmd.CombinedOutput()
77 if err == nil {
78 t.Fatalf("helper succeeded; want fatal registry-open failure:\n%s", out)
79 }
80 got := string(out)
81 unitErr := "adopt: self-update units installed and armed"
82 registryErr := "adopt: open registry " + registryPath + ":"
83 unitAt, registryAt := strings.Index(got, unitErr), strings.Index(got, registryErr)
84 if unitAt < 0 {
85 t.Fatalf("unit arming was not reported before exit:\n%s", got)
86 }
87 if registryAt < 0 {
88 t.Fatalf("registry error does not match the adopt error contract:\n%s", got)
89 }
90 if unitAt > registryAt {
91 t.Fatalf("registry failed before unit repair ran:\n%s", got)
92 }
93 for _, unit := range systemUnitFiles {
94 if _, err := os.Stat(filepath.Join(etc, "systemd", "system", unit)); err != nil {
95 t.Errorf("unit repair did not install %s before registry failure: %v", unit, err)
96 }
97 }
98 calls, err := os.ReadFile(callsPath)
99 if err != nil {
100 t.Fatalf("read systemctl calls: %v", err)
101 }
102 called := string(calls)
103 for _, want := range []string{
104 "daemon-reload",
105 "enable carlos-system-update.path carlos-system-recover.service carlos-instance-control.socket",
106 "start carlos-system-update.path carlos-instance-control.socket",
107 } {
108 if !strings.Contains(called, want) {
109 t.Errorf("systemctl calls lack %q:\n%s", want, called)
110 }
111 }
112}
113
114func isolatedAdoptOrderEnv(t *testing.T, extra ...string) []string {
115 t.Helper()
116 home := t.TempDir()
117 drop := map[string]bool{
118 "CARLOS_SERVICE_TOKEN": true,
119 "CARLOS_CREDENTIALS": true,
120 "CARLOS_CONSOLE": true,
121 "CARLOS_DESTINATION": true,
122 "HOME": true,
123 }
124 for _, kv := range extra {
125 if key, _, ok := strings.Cut(kv, "="); ok {
126 drop[key] = true
127 }
128 }
129 var env []string
130 for _, kv := range os.Environ() {
131 key, _, _ := strings.Cut(kv, "=")
132 if !drop[key] {
133 env = append(env, kv)
134 }
135 }
136 env = append(env,
137 "CARLOS_CREDENTIALS="+filepath.Join(home, "credentials"),
138 "CARLOS_CONSOLE=",
139 "CARLOS_DESTINATION=",
140 "HOME="+home,
141 )
142 return append(env, extra...)
143}
144