Skip to content

Commit e44459b

Browse files
committed
Add configurable boot_check_pattern for turingpi_node resource
- Add boot_check_pattern option (default: 'login:') - Support Talos Linux detection ('machine is running and ready') - Update documentation with pattern examples - Update all versions to 1.0.5
1 parent f3a4fd8 commit e44459b

File tree

11 files changed

+142
-29
lines changed

11 files changed

+142
-29
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.0.5] - 2025-12-22
11+
12+
### Added
13+
- `boot_check_pattern` option for turingpi_node resource
14+
- Configurable pattern matching for boot verification (default: `"login:"`)
15+
- Support for Talos Linux boot detection (`"machine is running and ready"`)
16+
1017
## [1.0.4] - 2025-12-22
1118

1219
### Added
@@ -67,7 +74,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6774
- Release automation workflow with GoReleaser
6875
- Multi-platform binaries (linux/darwin/windows, amd64/arm64)
6976

70-
[Unreleased]: https://github.com/jfreed-dev/terraform-provider-turingpi/compare/v1.0.4...HEAD
77+
[Unreleased]: https://github.com/jfreed-dev/terraform-provider-turingpi/compare/v1.0.5...HEAD
78+
[1.0.5]: https://github.com/jfreed-dev/terraform-provider-turingpi/compare/v1.0.4...v1.0.5
7179
[1.0.4]: https://github.com/jfreed-dev/terraform-provider-turingpi/compare/v1.0.3...v1.0.4
7280
[1.0.3]: https://github.com/jfreed-dev/terraform-provider-turingpi/compare/v1.0.2...v1.0.3
7381
[1.0.2]: https://github.com/jfreed-dev/terraform-provider-turingpi/compare/v1.0.1...v1.0.2

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ terraform {
1818
required_providers {
1919
turingpi = {
2020
source = "jfreed-dev/turingpi"
21-
version = "1.0.4"
21+
version = "1.0.5"
2222
}
2323
}
2424
}
@@ -74,11 +74,19 @@ Comprehensive node management: power control, firmware flashing, and boot verifi
7474

7575
```hcl
7676
resource "turingpi_node" "node1" {
77-
node = 1 # Node ID (1-4)
78-
power_state = "on" # "on" or "off" (default: "on")
79-
firmware_file = "/path/to/firmware.img" # optional
80-
boot_check = true # Monitor UART for login prompt (default: false)
81-
login_prompt_timeout = 120 # Timeout in seconds (default: 60)
77+
node = 1 # Node ID (1-4)
78+
power_state = "on" # "on" or "off" (default: "on")
79+
firmware_file = "/path/to/firmware.img" # optional
80+
boot_check = true # Monitor UART for boot pattern (default: false)
81+
boot_check_pattern = "login:" # Pattern to detect (default: "login:")
82+
login_prompt_timeout = 120 # Timeout in seconds (default: 60)
83+
}
84+
85+
# For Talos Linux, use the appropriate boot pattern:
86+
resource "turingpi_node" "talos" {
87+
node = 2
88+
boot_check = true
89+
boot_check_pattern = "machine is running and ready"
8290
}
8391
```
8492

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ terraform {
2323
required_providers {
2424
turingpi = {
2525
source = "jfreed-dev/turingpi"
26-
version = "1.0.4"
26+
version = "1.0.5"
2727
}
2828
}
2929
}

docs/resources/node.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ resource "turingpi_node" "node1" {
4242
}
4343
```
4444

45+
### Talos Linux Boot Verification
46+
47+
```hcl
48+
resource "turingpi_node" "talos_node" {
49+
node = 1
50+
power_state = "on"
51+
boot_check = true
52+
boot_check_pattern = "machine is running and ready"
53+
login_prompt_timeout = 180
54+
}
55+
```
56+
4557
### Complete Cluster Setup
4658

4759
```hcl
@@ -95,8 +107,9 @@ output "node_status" {
95107
- `node` - (Required, Integer) The node ID (1-4).
96108
- `power_state` - (Optional, String) The desired power state. Valid values are `"on"` or `"off"`. Defaults to `"on"`.
97109
- `firmware_file` - (Optional, String) Path to the firmware image file. If specified, firmware will be flashed to the node.
98-
- `boot_check` - (Optional, Boolean) Whether to monitor UART output for login prompt to verify successful boot. Defaults to `false`.
99-
- `login_prompt_timeout` - (Optional, Integer) Timeout in seconds to wait for login prompt when `boot_check` is enabled. Defaults to `60`.
110+
- `boot_check` - (Optional, Boolean) Whether to monitor UART output to verify successful boot. Defaults to `false`.
111+
- `boot_check_pattern` - (Optional, String) The pattern to search for in UART output to confirm successful boot. Defaults to `"login:"`. Use `"machine is running and ready"` for Talos Linux.
112+
- `login_prompt_timeout` - (Optional, Integer) Timeout in seconds to wait for boot pattern when `boot_check` is enabled. Defaults to `60`.
100113

101114
## Attribute Reference
102115

@@ -106,12 +119,20 @@ In addition to all arguments above, the following attributes are exported:
106119

107120
## Boot Verification
108121

109-
When `boot_check` is enabled, the provider monitors the node's UART output for a login prompt, indicating the operating system has successfully booted. This is useful for:
122+
When `boot_check` is enabled, the provider monitors the node's UART output for a specific pattern indicating the operating system has successfully booted. This is useful for:
110123

111124
- Ensuring firmware flashing completed successfully
112125
- Waiting for nodes to be ready before dependent operations
113126
- Detecting boot failures
114127

128+
### Supported Operating Systems
129+
130+
| OS | Pattern |
131+
|----|---------|
132+
| Standard Linux | `login:` (default) |
133+
| Talos Linux | `machine is running and ready` |
134+
| Custom | Any string present in UART output |
135+
115136
The `login_prompt_timeout` controls how long to wait for the boot to complete. Increase this value for slower compute modules or complex boot processes.
116137

117138
## Import

examples/basic/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ terraform {
22
required_providers {
33
turingpi = {
44
source = "jfreed-dev/turingpi"
5-
version = "1.0.4"
5+
version = "1.0.5"
66
}
77
}
88
}

examples/flash-firmware/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ terraform {
22
required_providers {
33
turingpi = {
44
source = "jfreed-dev/turingpi"
5-
version = "1.0.4"
5+
version = "1.0.5"
66
}
77
}
88
}

examples/full-provisioning/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ terraform {
22
required_providers {
33
turingpi = {
44
source = "jfreed-dev/turingpi"
5-
version = "1.0.4"
5+
version = "1.0.5"
66
}
77
}
88
}

provider/helpers.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func flashNode(node int, firmware string) {
3232
// Replace this with an API call to flash the firmware
3333
}
3434

35-
func checkBootStatus(endpoint string, node int, timeout int, token string) (bool, error) {
35+
func checkBootStatus(endpoint string, node int, timeout int, token string, pattern string) (bool, error) {
3636
url := fmt.Sprintf("%s/api/bmc?opt=get&type=uart&node=%d", endpoint, node)
3737

3838
deadline := time.Now().Add(time.Duration(timeout) * time.Second)
@@ -55,14 +55,14 @@ func checkBootStatus(endpoint string, node int, timeout int, token string) (bool
5555
return false, fmt.Errorf("failed to read UART response: %v", err)
5656
}
5757

58-
// Simulate login prompt detection
59-
if strings.Contains(string(body), "login:") {
60-
fmt.Printf("Node %d booted successfully: login prompt detected.\n", node)
58+
// Check for configured boot pattern in UART output
59+
if strings.Contains(string(body), pattern) {
60+
fmt.Printf("Node %d booted successfully: pattern %q detected.\n", node, pattern)
6161
return true, nil
6262
}
6363

6464
time.Sleep(5 * time.Second)
6565
}
6666

67-
return false, fmt.Errorf("timeout reached: node %d did not boot successfully", node)
67+
return false, fmt.Errorf("timeout reached: node %d did not boot successfully (pattern %q not found)", node, pattern)
6868
}

provider/helpers_test.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestCheckBootStatus_Success(t *testing.T) {
9494
defer server.Close()
9595

9696
// Use short timeout since mock server returns immediately
97-
success, err := checkBootStatus(server.URL, 1, 1, "test-token")
97+
success, err := checkBootStatus(server.URL, 1, 1, "test-token", "login:")
9898
if err != nil {
9999
t.Fatalf("unexpected error: %s", err)
100100
}
@@ -115,7 +115,7 @@ func TestCheckBootStatus_TokenInHeader(t *testing.T) {
115115
}))
116116
defer server.Close()
117117

118-
_, _ = checkBootStatus(server.URL, 1, 1, expectedToken)
118+
_, _ = checkBootStatus(server.URL, 1, 1, expectedToken, "login:")
119119

120120
expectedHeader := "Bearer " + expectedToken
121121
if capturedAuth != expectedHeader {
@@ -144,7 +144,7 @@ func TestCheckBootStatus_NodeInURL(t *testing.T) {
144144
}))
145145
defer server.Close()
146146

147-
_, _ = checkBootStatus(server.URL, tc.node, 1, "token")
147+
_, _ = checkBootStatus(server.URL, tc.node, 1, "token", "login:")
148148

149149
if capturedNode != tc.expectedNode {
150150
t.Errorf("expected node=%s in URL, got node=%s", tc.expectedNode, capturedNode)
@@ -163,7 +163,7 @@ func TestCheckBootStatus_Timeout(t *testing.T) {
163163

164164
// Use very short timeout to speed up test
165165
// Note: This test will take at least 1 second due to the timeout
166-
success, err := checkBootStatus(server.URL, 1, 1, "token")
166+
success, err := checkBootStatus(server.URL, 1, 1, "token", "login:")
167167

168168
if success {
169169
t.Error("expected success=false on timeout")
@@ -180,7 +180,7 @@ func TestCheckBootStatus_Timeout(t *testing.T) {
180180

181181
func TestCheckBootStatus_ConnectionError(t *testing.T) {
182182
// Use invalid URL to simulate connection error
183-
success, err := checkBootStatus("http://localhost:99999", 1, 1, "token")
183+
success, err := checkBootStatus("http://localhost:99999", 1, 1, "token", "login:")
184184

185185
if success {
186186
t.Error("expected success=false on connection error")
@@ -203,7 +203,7 @@ func TestCheckBootStatus_URLConstruction(t *testing.T) {
203203
}))
204204
defer server.Close()
205205

206-
_, _ = checkBootStatus(server.URL, 2, 1, "token")
206+
_, _ = checkBootStatus(server.URL, 2, 1, "token", "login:")
207207

208208
if capturedPath != "/api/bmc" {
209209
t.Errorf("expected path /api/bmc, got %s", capturedPath)
@@ -244,11 +244,42 @@ func TestCheckBootStatus_LoginPromptVariations(t *testing.T) {
244244
}))
245245
defer server.Close()
246246

247-
success, _ := checkBootStatus(server.URL, 1, 1, "token")
247+
success, _ := checkBootStatus(server.URL, 1, 1, "token", "login:")
248248

249249
if success != tc.expected {
250250
t.Errorf("expected success=%v for response '%s', got %v", tc.expected, tc.response, success)
251251
}
252252
})
253253
}
254254
}
255+
256+
func TestCheckBootStatus_CustomPattern(t *testing.T) {
257+
testCases := []struct {
258+
name string
259+
response string
260+
pattern string
261+
expected bool
262+
}{
263+
{"talos ready pattern", "[talos] machine is running and ready", "machine is running and ready", true},
264+
{"talos boot sequence done", "[talos] boot sequence: done", "boot sequence: done", true},
265+
{"custom pattern match", "System initialized successfully", "initialized successfully", true},
266+
{"pattern not found", "still booting...", "machine is running and ready", false},
267+
{"wrong pattern", "login:", "machine is running and ready", false},
268+
}
269+
270+
for _, tc := range testCases {
271+
t.Run(tc.name, func(t *testing.T) {
272+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
273+
w.WriteHeader(http.StatusOK)
274+
_, _ = w.Write([]byte(tc.response))
275+
}))
276+
defer server.Close()
277+
278+
success, _ := checkBootStatus(server.URL, 1, 1, "token", tc.pattern)
279+
280+
if success != tc.expected {
281+
t.Errorf("expected success=%v for pattern '%s' in response '%s', got %v", tc.expected, tc.pattern, tc.response, success)
282+
}
283+
})
284+
}
285+
}

provider/resource_node.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@ func resourceNode() *schema.Resource {
3939
Type: schema.TypeInt,
4040
Optional: true,
4141
Default: 60,
42-
Description: "Timeout in seconds to wait for login prompt via UART",
42+
Description: "Timeout in seconds to wait for boot check pattern via UART",
43+
},
44+
"boot_check_pattern": {
45+
Type: schema.TypeString,
46+
Optional: true,
47+
Default: "login:",
48+
Description: "Pattern to search for in UART output to confirm successful boot (e.g., 'login:' for standard Linux, 'machine is running and ready' for Talos)",
4349
},
4450
},
4551
}
@@ -52,6 +58,7 @@ func resourceNodeProvision(d *schema.ResourceData, meta interface{}) error {
5258
powerState := d.Get("power_state").(string)
5359
bootCheck := d.Get("boot_check").(bool)
5460
timeout := d.Get("login_prompt_timeout").(int)
61+
bootCheckPattern := d.Get("boot_check_pattern").(string)
5562

5663
// Step 1: Turn on the node
5764
if powerState == "on" {
@@ -67,8 +74,8 @@ func resourceNodeProvision(d *schema.ResourceData, meta interface{}) error {
6774

6875
// Step 3: Boot check
6976
if bootCheck {
70-
fmt.Printf("Checking boot status for node %d...\n", node)
71-
success, err := checkBootStatus(config.Endpoint, node, timeout, config.Token)
77+
fmt.Printf("Checking boot status for node %d (pattern: %q)...\n", node, bootCheckPattern)
78+
success, err := checkBootStatus(config.Endpoint, node, timeout, config.Token, bootCheckPattern)
7279
if err != nil {
7380
return fmt.Errorf("boot status check failed for node %d: %v", node, err)
7481
}

0 commit comments

Comments
 (0)