Skip to content

Commit 92eff1f

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 92eff1f

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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 os.RemoveAll(tmpDir)
61+
62+
if err := tt.setupFunc(tmpDir); err != nil {
63+
t.Fatalf("setup failed: %v", err)
64+
}
65+
66+
result := isOstreeSystem(filepath.Join(tmpDir, "run", "ostree-booted"))
67+
if result != tt.expected {
68+
t.Errorf("%s: got %v, want %v", tt.description, result, tt.expected)
69+
}
70+
})
71+
}
72+
}
73+
74+
// TestCrypttabOptionsGeneration tests that the crypttab options are generated correctly
75+
func TestCrypttabOptionsGeneration(t *testing.T) {
76+
tests := []struct {
77+
name string
78+
hasNetworkDev bool
79+
isOstree bool
80+
expectedResult string
81+
expectedFormat string
82+
description string
83+
}{
84+
{
85+
name: "regular system without network",
86+
hasNetworkDev: false,
87+
isOstree: false,
88+
expectedResult: "",
89+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks\n",
90+
description: "Should have no options on non-ostree without network",
91+
},
92+
{
93+
name: "regular system with network",
94+
hasNetworkDev: true,
95+
isOstree: false,
96+
expectedResult: ",_netdev",
97+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,_netdev\n",
98+
description: "Should have _netdev on non-ostree with network",
99+
},
100+
{
101+
name: "ostree system without network",
102+
hasNetworkDev: false,
103+
isOstree: true,
104+
expectedResult: ",x-initrd.attach",
105+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,x-initrd.attach\n",
106+
description: "Should have x-initrd.attach on ostree without network",
107+
},
108+
{
109+
name: "ostree system with network",
110+
hasNetworkDev: true,
111+
isOstree: true,
112+
expectedResult: ",_netdev,x-initrd.attach",
113+
expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,_netdev,x-initrd.attach\n",
114+
description: "Should have both _netdev and x-initrd.attach on ostree with network",
115+
},
116+
}
117+
118+
for _, tt := range tests {
119+
t.Run(tt.name, func(t *testing.T) {
120+
options := buildCrypttabOptions(tt.hasNetworkDev, tt.isOstree)
121+
122+
// Check the options string is exactly what we expect
123+
if options != tt.expectedResult {
124+
t.Errorf("%s:\n got options: %q\n want options: %q", tt.description, options, tt.expectedResult)
125+
}
126+
127+
// Build the full crypttab line for format verification
128+
crypttabLine := "testdev UUID=test-uuid /path/to/keyfile luks" + options + "\n"
129+
if crypttabLine != tt.expectedFormat {
130+
t.Errorf("%s:\n got line: %q\n want line: %q", tt.description, crypttabLine, tt.expectedFormat)
131+
}
132+
})
133+
}
134+
}
135+
136+
// TestCrypttabOptionsWithClevis tests crypttab options when using Clevis
137+
func TestCrypttabOptionsWithClevis(t *testing.T) {
138+
tests := []struct {
139+
name string
140+
hasNetworkDev bool
141+
isOstree bool
142+
expectedKeyfile string
143+
expectedOptions string
144+
expectedLine string
145+
description string
146+
}{
147+
{
148+
name: "clevis on regular system",
149+
hasNetworkDev: true,
150+
isOstree: false,
151+
expectedKeyfile: "none",
152+
expectedOptions: ",_netdev",
153+
expectedLine: "testdev UUID=test-uuid none luks,_netdev\n",
154+
description: "Clevis should use 'none' as keyfile on non-ostree",
155+
},
156+
{
157+
name: "clevis on ostree system",
158+
hasNetworkDev: true,
159+
isOstree: true,
160+
expectedKeyfile: "none",
161+
expectedOptions: ",_netdev,x-initrd.attach",
162+
expectedLine: "testdev UUID=test-uuid none luks,_netdev,x-initrd.attach\n",
163+
description: "Clevis should use 'none' as keyfile and have x-initrd.attach on ostree",
164+
},
165+
}
166+
167+
for _, tt := range tests {
168+
t.Run(tt.name, func(t *testing.T) {
169+
options := buildCrypttabOptions(tt.hasNetworkDev, tt.isOstree)
170+
171+
// Check the options are correct
172+
if options != tt.expectedOptions {
173+
t.Errorf("%s:\n got options: %q\n want options: %q", tt.description, options, tt.expectedOptions)
174+
}
175+
176+
// Verify full crypttab entry format
177+
crypttabLine := "testdev UUID=test-uuid " + tt.expectedKeyfile + " luks" + options + "\n"
178+
if crypttabLine != tt.expectedLine {
179+
t.Errorf("%s:\n got line: %q\n want line: %q", tt.description, crypttabLine, tt.expectedLine)
180+
}
181+
})
182+
}
183+
}

0 commit comments

Comments
 (0)