Skip to content

Commit a1a82e2

Browse files
committed
internal/exec/stages/files: add tests for LUKS ostree soft-reboot
Add tests verifying x-initrd.attach option is added to crypttab entries on ostree systems. This prevents systemd-cryptsetup-generator from tearing down LUKS devices during soft-reboot. Related: #2216
1 parent 7e288ed commit a1a82e2

1 file changed

Lines changed: 187 additions & 0 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright 2026 CoreOS, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package files
16+
17+
import (
18+
"os"
19+
"path/filepath"
20+
"testing"
21+
)
22+
23+
// TestIsOstreeSystem tests the ostree system detection function
24+
func TestIsOstreeSystem(t *testing.T) {
25+
tests := []struct {
26+
name string
27+
setupFunc func(string) error
28+
expected bool
29+
description string
30+
}{
31+
{
32+
name: "ostree system",
33+
setupFunc: func(tmpDir string) error {
34+
ostreeMarker := filepath.Join(tmpDir, "run", "ostree-booted")
35+
if err := os.MkdirAll(filepath.Dir(ostreeMarker), 0755); err != nil {
36+
return err
37+
}
38+
return os.WriteFile(ostreeMarker, []byte(""), 0644)
39+
},
40+
expected: true,
41+
description: "Should detect ostree system when /run/ostree-booted exists",
42+
},
43+
{
44+
name: "non-ostree system",
45+
setupFunc: func(tmpDir string) error {
46+
// Just create the run directory without the ostree-booted file
47+
return os.MkdirAll(filepath.Join(tmpDir, "run"), 0755)
48+
},
49+
expected: false,
50+
description: "Should not detect ostree when /run/ostree-booted is missing",
51+
},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
tmpDir, err := os.MkdirTemp("", "ignition-test-")
57+
if err != nil {
58+
t.Fatalf("failed to create temp dir: %v", err)
59+
}
60+
defer func() {
61+
if err := os.RemoveAll(tmpDir); err != nil {
62+
t.Logf("failed to remove temp dir: %v", err)
63+
}
64+
}()
65+
66+
if err := tt.setupFunc(tmpDir); err != nil {
67+
t.Fatalf("setup failed: %v", err)
68+
}
69+
70+
result := isOstreeSystem(filepath.Join(tmpDir, "run", "ostree-booted"))
71+
if result != tt.expected {
72+
t.Errorf("%s: got %v, want %v", tt.description, result, tt.expected)
73+
}
74+
})
75+
}
76+
}
77+
78+
// TestCrypttabOptionsGeneration tests that the crypttab options are generated correctly
79+
func TestCrypttabOptionsGeneration(t *testing.T) {
80+
tests := []struct {
81+
name string
82+
hasNetworkDev bool
83+
isOstree bool
84+
expectedResult string
85+
expectedFormat string
86+
description string
87+
}{
88+
{
89+
name: "regular system without network",
90+
hasNetworkDev: false,
91+
isOstree: false,
92+
expectedResult: "",
93+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks\n",
94+
description: "Should have no options on non-ostree without network",
95+
},
96+
{
97+
name: "regular system with network",
98+
hasNetworkDev: true,
99+
isOstree: false,
100+
expectedResult: ",_netdev",
101+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,_netdev\n",
102+
description: "Should have _netdev on non-ostree with network",
103+
},
104+
{
105+
name: "ostree system without network",
106+
hasNetworkDev: false,
107+
isOstree: true,
108+
expectedResult: ",x-initrd.attach",
109+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,x-initrd.attach\n",
110+
description: "Should have x-initrd.attach on ostree without network",
111+
},
112+
{
113+
name: "ostree system with network",
114+
hasNetworkDev: true,
115+
isOstree: true,
116+
expectedResult: ",_netdev,x-initrd.attach",
117+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,_netdev,x-initrd.attach\n",
118+
description: "Should have both _netdev and x-initrd.attach on ostree with network",
119+
},
120+
}
121+
122+
for _, tt := range tests {
123+
t.Run(tt.name, func(t *testing.T) {
124+
options := buildCrypttabOptions(tt.hasNetworkDev, tt.isOstree)
125+
126+
// Check the options string is exactly what we expect
127+
if options != tt.expectedResult {
128+
t.Errorf("%s:\n got options: %q\n want options: %q", tt.description, options, tt.expectedResult)
129+
}
130+
131+
// Build the full crypttab line for format verification
132+
crypttabLine := "testdev UUID=test-uuid /path/to/keyfile luks" + options + "\n"
133+
if crypttabLine != tt.expectedFormat {
134+
t.Errorf("%s:\n got line: %q\n want line: %q", tt.description, crypttabLine, tt.expectedFormat)
135+
}
136+
})
137+
}
138+
}
139+
140+
// TestCrypttabOptionsWithClevis tests crypttab options when using Clevis
141+
func TestCrypttabOptionsWithClevis(t *testing.T) {
142+
tests := []struct {
143+
name string
144+
hasNetworkDev bool
145+
isOstree bool
146+
expectedKeyfile string
147+
expectedOptions string
148+
expectedLine string
149+
description string
150+
}{
151+
{
152+
name: "clevis on regular system",
153+
hasNetworkDev: true,
154+
isOstree: false,
155+
expectedKeyfile: "none",
156+
expectedOptions: ",_netdev",
157+
expectedLine: "testdev UUID=test-uuid none luks,_netdev\n",
158+
description: "Clevis should use 'none' as keyfile on non-ostree",
159+
},
160+
{
161+
name: "clevis on ostree system",
162+
hasNetworkDev: true,
163+
isOstree: true,
164+
expectedKeyfile: "none",
165+
expectedOptions: ",_netdev,x-initrd.attach",
166+
expectedLine: "testdev UUID=test-uuid none luks,_netdev,x-initrd.attach\n",
167+
description: "Clevis should use 'none' as keyfile and have x-initrd.attach on ostree",
168+
},
169+
}
170+
171+
for _, tt := range tests {
172+
t.Run(tt.name, func(t *testing.T) {
173+
options := buildCrypttabOptions(tt.hasNetworkDev, tt.isOstree)
174+
175+
// Check the options are correct
176+
if options != tt.expectedOptions {
177+
t.Errorf("%s:\n got options: %q\n want options: %q", tt.description, options, tt.expectedOptions)
178+
}
179+
180+
// Verify full crypttab entry format
181+
crypttabLine := "testdev UUID=test-uuid " + tt.expectedKeyfile + " luks" + options + "\n"
182+
if crypttabLine != tt.expectedLine {
183+
t.Errorf("%s:\n got line: %q\n want line: %q", tt.description, crypttabLine, tt.expectedLine)
184+
}
185+
})
186+
}
187+
}

0 commit comments

Comments
 (0)