Skip to content

Commit 88e9194

Browse files
committed
device_managers: add abstraction for sgdisk and sfdisk
Add an interface to abstract implementation details for partition management, allowing tooling to pivot between implementations. TBD: argument/tag reading for triggering the pivot Fixes: https://issues.redhat.com/browse/COS-2930
1 parent 1cac765 commit 88e9194

File tree

5 files changed

+228
-289
lines changed

5 files changed

+228
-289
lines changed
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package device_managers
2+
3+
import (
4+
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
5+
)
6+
7+
type DeviceManager interface {
8+
CreatePartition(p Partition)
9+
DeletePartition(num int)
10+
Info(num int)
11+
WipeTable(wipe bool)
12+
Pretend() (string, error)
13+
Commit() error
14+
ParseOutput(string, []int) (map[int]Output, error)
15+
}
16+
17+
type Partition struct {
18+
types.Partition
19+
StartSector *int64
20+
SizeInSectors *int64
21+
StartMiB string
22+
SizeMiB string
23+
}
24+
25+
type Output struct {
26+
Start int64
27+
Size int64
28+
}

internal/device_managers/sfdisk/sfdisk.go

+65-92
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ import (
1919
"fmt"
2020
"io"
2121
"os/exec"
22+
"regexp"
23+
"strconv"
24+
"strings"
2225

26+
sharedErrors "github.com/coreos/ignition/v2/config/shared/errors"
2327
"github.com/coreos/ignition/v2/config/util"
24-
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
28+
"github.com/coreos/ignition/v2/internal/device_managers"
2529
"github.com/coreos/ignition/v2/internal/distro"
2630
"github.com/coreos/ignition/v2/internal/log"
2731
)
@@ -30,30 +34,18 @@ type Operation struct {
3034
logger *log.Logger
3135
dev string
3236
wipe bool
33-
parts []Partition
37+
parts []device_managers.Partition
3438
deletions []int
3539
infos []int
3640
}
3741

38-
// We ignore types.Partition.StartMiB/SizeMiB in favor of
39-
// StartSector/SizeInSectors. The caller is expected to do the conversion.
40-
type Partition struct {
41-
types.Partition
42-
StartSector *int64
43-
SizeInSectors *int64
44-
45-
// shadow StartMiB/SizeMiB so they're not accidentally used
46-
StartMiB string
47-
SizeMiB string
48-
}
49-
5042
// Begin begins an sfdisk operation
5143
func Begin(logger *log.Logger, dev string) *Operation {
5244
return &Operation{logger: logger, dev: dev}
5345
}
5446

55-
// CreatePartition adds the supplied partition to the list of partitions to be created as part of an operation.
56-
func (op *Operation) CreatePartition(p Partition) {
47+
// CreatePartition adds the supplied partition to the list of partitions to be created as part of an operation
48+
func (op *Operation) CreatePartition(p device_managers.Partition) {
5749
op.parts = append(op.parts, p)
5850
}
5951

@@ -81,7 +73,7 @@ func (op *Operation) Pretend() (string, error) {
8173
return "", err
8274
}
8375

84-
script := op.sfdiskBuildOptions()
76+
script := op.buildOptions()
8577
cmd := exec.Command("sh", "-c", fmt.Sprintf("echo -e \"%s\" | sudo %s --no-act %s", script, distro.SfdiskCmd(), op.dev))
8678
stdout, err := cmd.StdoutPipe()
8779

@@ -114,8 +106,8 @@ func (op *Operation) Pretend() (string, error) {
114106
}
115107

116108
// Commit commits an partitioning operation.
117-
func (op *Operation) SfdiskCommit() error {
118-
script := op.sfdiskBuildOptions()
109+
func (op *Operation) Commit() error {
110+
script := op.buildOptions()
119111
if len(script) == 0 {
120112
return nil
121113
}
@@ -139,7 +131,60 @@ func (op *Operation) SfdiskCommit() error {
139131
return nil
140132
}
141133

142-
func (op Operation) sfdiskBuildOptions() string {
134+
// ParseOutput takes the output from sfdisk. Similarly to sgdisk
135+
// it then uses regex to parse the output into understood values like 'start' 'size' and attempts
136+
// to catch any failures and wrap them to return to the caller.
137+
func (op *Operation) ParseOutput(sfdiskOutput string, partitionNumbers []int) (map[int]device_managers.Output, error) {
138+
if len(partitionNumbers) == 0 {
139+
return nil, nil
140+
}
141+
142+
// Prepare the data, and a regex for matching on partitions
143+
partitionRegex := regexp.MustCompile(`^/dev/\S+\s+\S*\s+(\d+)\s+(\d+)\s+\d+\s+\S+\s+\S+\s+\S+.*$`)
144+
output := map[int]device_managers.Output{}
145+
current := device_managers.Output{}
146+
i := 0
147+
lines := strings.Split(sfdiskOutput, "\n")
148+
for _, line := range lines {
149+
matches := partitionRegex.FindStringSubmatch(line)
150+
151+
// Sanity check number of partition entries
152+
if i > len(partitionNumbers) {
153+
return nil, sharedErrors.ErrBadSfdiskPretend
154+
}
155+
156+
// Verify that we are not reading a 'failed' or 'error'
157+
errorRegex := regexp.MustCompile(`(?i)(failed|error)`)
158+
if errorRegex.MatchString(line) {
159+
return nil, fmt.Errorf("%w: sfdisk returned :%v", sharedErrors.ErrBadSfdiskPretend, line)
160+
}
161+
162+
// When we get a match it should be
163+
// Whole line at [0]
164+
// Start at [1]
165+
// Size at [2]
166+
if len(matches) > 2 {
167+
start, err := strconv.Atoi(matches[1])
168+
if err != nil {
169+
return nil, err
170+
}
171+
end, err := strconv.Atoi(matches[2])
172+
if err != nil {
173+
return nil, err
174+
}
175+
176+
current.Start = int64(start)
177+
// Add one due to overlap
178+
current.Size = int64(end - start + 1)
179+
output[partitionNumbers[i]] = current
180+
i++
181+
}
182+
}
183+
184+
return output, nil
185+
}
186+
187+
func (op Operation) buildOptions() string {
143188
var script bytes.Buffer
144189

145190
for _, p := range op.parts {
@@ -202,75 +247,3 @@ func (op *Operation) handleInfo() error {
202247
}
203248
return nil
204249
}
205-
206-
// Copy old functionality from sgdisk to switch between the two during testing.
207-
// Will be removed.
208-
func (op *Operation) SgdiskCommit() error {
209-
opts := op.sgdiskBuildOptions()
210-
if len(opts) == 0 {
211-
return nil
212-
}
213-
op.logger.Info("running sgdisk with options: %v", opts)
214-
cmd := exec.Command(distro.SgdiskCmd(), opts...)
215-
216-
if _, err := op.logger.LogCmd(cmd, "deleting %d partitions and creating %d partitions on %q", len(op.deletions), len(op.parts), op.dev); err != nil {
217-
return fmt.Errorf("create partitions failed: %v", err)
218-
}
219-
220-
return nil
221-
}
222-
223-
// Copy old functionality from sgdisk to switch between the two during testing.
224-
// Will be removed.
225-
func (op Operation) sgdiskBuildOptions() []string {
226-
opts := []string{}
227-
228-
if op.wipe {
229-
opts = append(opts, "--zap-all")
230-
}
231-
232-
// Do all deletions before creations
233-
for _, partition := range op.deletions {
234-
opts = append(opts, fmt.Sprintf("--delete=%d", partition))
235-
}
236-
237-
for _, p := range op.parts {
238-
opts = append(opts, fmt.Sprintf("--new=%d:%s:+%s", p.Number, partitionGetStart(p), partitionGetSize(p)))
239-
if p.Label != nil {
240-
opts = append(opts, fmt.Sprintf("--change-name=%d:%s", p.Number, *p.Label))
241-
}
242-
if util.NotEmpty(p.TypeGUID) {
243-
opts = append(opts, fmt.Sprintf("--typecode=%d:%s", p.Number, *p.TypeGUID))
244-
}
245-
if util.NotEmpty(p.GUID) {
246-
opts = append(opts, fmt.Sprintf("--partition-guid=%d:%s", p.Number, *p.GUID))
247-
}
248-
}
249-
250-
for _, partition := range op.infos {
251-
opts = append(opts, fmt.Sprintf("--info=%d", partition))
252-
}
253-
254-
if len(opts) == 0 {
255-
return nil
256-
}
257-
258-
opts = append(opts, op.dev)
259-
return opts
260-
}
261-
262-
// Copy old functionality from sgdisk to switch between the two during testing.
263-
// Will be removed.
264-
func partitionGetStart(p Partition) string {
265-
if p.StartSector != nil {
266-
return fmt.Sprintf("%d", *p.StartSector)
267-
}
268-
return "0"
269-
}
270-
271-
func partitionGetSize(p Partition) string {
272-
if p.SizeInSectors != nil {
273-
return fmt.Sprintf("%d", *p.SizeInSectors)
274-
}
275-
return "0"
276-
}

internal/exec/stages/disks/partitions_test.go renamed to internal/device_managers/sfdisk/sfdisk_test.go

+13-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
package disks
1+
package sfdisk_test
22

33
import (
44
"errors"
55
"reflect"
66
"testing"
77

88
internalErrors "github.com/coreos/ignition/v2/config/shared/errors"
9+
"github.com/coreos/ignition/v2/internal/device_managers"
10+
"github.com/coreos/ignition/v2/internal/device_managers/sfdisk"
911
)
1012

1113
func TestPartitionParse(t *testing.T) {
@@ -14,7 +16,7 @@ func TestPartitionParse(t *testing.T) {
1416
name string
1517
sfdiskOut string
1618
partitionNumbers []int
17-
expectedOutput map[int]sfdiskOutput
19+
expectedOutput map[int]device_managers.Output
1820
expectedError error
1921
}{
2022
{
@@ -37,8 +39,8 @@ Device Boot Start End Sectors Size Id Type
3739
/dev/vda1 2048 2057 10 5K 83 Linux
3840
The partition table is unchanged (--no-act).`,
3941
partitionNumbers: []int{1},
40-
expectedOutput: map[int]sfdiskOutput{
41-
1: {start: 2048, size: 10},
42+
expectedOutput: map[int]device_managers.Output{
43+
1: {Start: 2048, Size: 10},
4244
},
4345
expectedError: nil,
4446
},
@@ -64,9 +66,9 @@ Device Boot Start End Sectors Size Id Type
6466
/dev/vda2 4096 4105 10 5K 83 Linux
6567
The partition table is unchanged (--no-act).`,
6668
partitionNumbers: []int{1, 2},
67-
expectedOutput: map[int]sfdiskOutput{
68-
1: {start: 2048, size: 10},
69-
2: {start: 4096, size: 10},
69+
expectedOutput: map[int]device_managers.Output{
70+
1: {Start: 2048, Size: 10},
71+
2: {Start: 4096, Size: 10},
7072
},
7173
expectedError: nil,
7274
},
@@ -84,16 +86,16 @@ Failed to add #1 partition: Numerical result out of range
8486
Leaving.
8587
`,
8688
partitionNumbers: []int{1},
87-
expectedOutput: map[int]sfdiskOutput{
88-
1: {start: 0, size: 0},
89+
expectedOutput: map[int]device_managers.Output{
90+
1: {Start: 0, Size: 0},
8991
},
9092
expectedError: internalErrors.ErrBadSfdiskPretend,
9193
},
9294
}
93-
95+
op := sfdisk.Begin(nil, "")
9496
for i, tt := range tests {
9597
t.Run(tt.name, func(t *testing.T) {
96-
output, err := parseSfdiskPretend(tt.sfdiskOut, tt.partitionNumbers)
98+
output, err := op.ParseOutput(tt.sfdiskOut, tt.partitionNumbers)
9799
if tt.expectedError != nil {
98100
if !errors.Is(err, tt.expectedError) {
99101
t.Errorf("#%d: bad error: result = %v, expected = %v", i, err, tt.expectedError)

0 commit comments

Comments
 (0)