Skip to content

Commit

Permalink
device_managers: move and add abstraction for sgdisk and sfdisk
Browse files Browse the repository at this point in the history
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
  • Loading branch information
prestist committed Oct 8, 2024
1 parent 1ca010f commit 1a20248
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 294 deletions.
28 changes: 28 additions & 0 deletions internal/device_managers/deviceManagers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package device_managers

import (
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
)

type DeviceManager interface {
CreatePartition(p Partition)
DeletePartition(num int)
Info(num int)
WipeTable(wipe bool)
Pretend() (string, error)
Commit() error
ParseOutput(string, []int) (map[int]Output, error)
}

type Partition struct {
types.Partition
StartSector *int64
SizeInSectors *int64
StartMiB string
SizeMiB string
}

type Output struct {
Start int64
Size int64
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ import (
"fmt"
"io"
"os/exec"
"regexp"
"strconv"
"strings"

sharedErrors "github.com/coreos/ignition/v2/config/shared/errors"
"github.com/coreos/ignition/v2/config/util"
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
"github.com/coreos/ignition/v2/internal/device_managers"
"github.com/coreos/ignition/v2/internal/distro"
"github.com/coreos/ignition/v2/internal/log"
)
Expand All @@ -30,30 +34,18 @@ type Operation struct {
logger *log.Logger
dev string
wipe bool
parts []Partition
parts []device_managers.Partition
deletions []int
infos []int
}

// We ignore types.Partition.StartMiB/SizeMiB in favor of
// StartSector/SizeInSectors. The caller is expected to do the conversion.
type Partition struct {
types.Partition
StartSector *int64
SizeInSectors *int64

// shadow StartMiB/SizeMiB so they're not accidentally used
StartMiB string
SizeMiB string
}

// Begin begins an sfdisk operation
func Begin(logger *log.Logger, dev string) *Operation {
return &Operation{logger: logger, dev: dev}
}

// CreatePartition adds the supplied partition to the list of partitions to be created as part of an operation.
func (op *Operation) CreatePartition(p Partition) {
// CreatePartition adds the supplied partition to the list of partitions to be created as part of an operation
func (op *Operation) CreatePartition(p device_managers.Partition) {
op.parts = append(op.parts, p)
}

Expand Down Expand Up @@ -81,7 +73,7 @@ func (op *Operation) Pretend() (string, error) {
return "", err
}

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

Expand Down Expand Up @@ -114,35 +106,108 @@ func (op *Operation) Pretend() (string, error) {
}

// Commit commits an partitioning operation.
func (op *Operation) SfdiskCommit() error {
script := op.sfdiskBuildOptions()
func (op *Operation) Commit() error {
println("Commit: HERE")
fmt.Println(op.parts)
script := op.buildOptions()
if len(script) == 0 {
return nil
}

// If wipe we need to reset the partition table
if op.wipe {
// Erase the existing partition tables
cmd := exec.Command("sudo", distro.WipefsCmd(), "-a", op.dev)
cmd := exec.Command(distro.WipefsCmd(), "-a", op.dev)
if _, err := op.logger.LogCmd(cmd, "option wipe selected, and failed to execute on %q", op.dev); err != nil {
return fmt.Errorf("wipe partition table failed: %v", err)
}
}

op.logger.Info("running sfdisk with script: %v", script)
exec.Command("sh", "-c", fmt.Sprintf("echo label: gpt | sudo %s %s", distro.SfdiskCmd(), op.dev))
cmd := exec.Command("sh", "-c", fmt.Sprintf("echo \"%s\" | sudo %s %s", script, distro.SfdiskCmd(), op.dev))
op.runSfdisk(true)

return nil
}

func (op *Operation) runSfdisk(shouldWrite bool) error {
var opts []string
if !shouldWrite {
opts = append(opts, "--no-act")
}
opts = append(opts, "-X", "gpt", op.dev)
fmt.Printf("The options are %v", opts)
cmd := exec.Command(distro.SfdiskCmd(), opts...)
cmd.Stdin = strings.NewReader(op.buildOptions())
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 {
return fmt.Errorf("create partitions failed: %v", err)
}

return nil
}

func (op Operation) sfdiskBuildOptions() string {
// ParseOutput takes the output from sfdisk. Similarly to sgdisk
// it then uses regex to parse the output into understood values like 'start' 'size' and attempts
// to catch any failures and wrap them to return to the caller.
func (op *Operation) ParseOutput(sfdiskOutput string, partitionNumbers []int) (map[int]device_managers.Output, error) {
if len(partitionNumbers) == 0 {
return nil, nil
}

// Look for new lines starting with /dev/ and the following string it
// Additionally Group on Start sector, and End sector
// Example output match would be "/dev/vda1 2048 2057 10 5K 83 Linux"
partitionRegex := regexp.MustCompile(`^/dev/\S+\s+\S*\s+(\d+)\s+(\d+)\s+\d+\s+\S+\s+\S+\s+\S+.*$`)
output := map[int]device_managers.Output{}
current := device_managers.Output{}
i := 0
lines := strings.Split(sfdiskOutput, "\n")
for _, line := range lines {
matches := partitionRegex.FindStringSubmatch(line)

// Sanity check number of partition entries
if i > len(partitionNumbers) {
return nil, sharedErrors.ErrBadSfdiskPretend
}

// Verify that we are not reading a 'failed' or 'error'
errorRegex := regexp.MustCompile(`(?i)(failed|error)`)
if errorRegex.MatchString(line) {
return nil, fmt.Errorf("%w: sfdisk returned :%v", sharedErrors.ErrBadSfdiskPretend, line)
}

// When we get a match it should be
// Whole line at [0]
// Start at [1]
// End at [2]
if len(matches) > 2 {
start, err := strconv.Atoi(matches[1])
if err != nil {
return nil, err
}
end, err := strconv.Atoi(matches[2])
if err != nil {
return nil, err
}

current.Start = int64(start)
// Add one due to overlap
current.Size = int64(end - start + 1)
output[partitionNumbers[i]] = current
i++
}
}

return output, nil
}

func (op Operation) buildOptions() string {
var script bytes.Buffer

for _, p := range op.parts {
println("Starting Build Options Script Building")

fmt.Println(p)
println(script.String())

if p.Number != 0 {
script.WriteString(fmt.Sprintf("%d : ", p.Number))
}
Expand Down Expand Up @@ -170,7 +235,9 @@ func (op Operation) sfdiskBuildOptions() string {

// Add escaped new line to allow for 1 or more partitions
// i.e "1: size=50 \\n size=10" will result in part 1, and 2
script.WriteString("\\n ")
script.WriteString("\n")
println("here!")
println(script.String())

}

Expand Down Expand Up @@ -202,75 +269,3 @@ func (op *Operation) handleInfo() error {
}
return nil
}

// Copy old functionality from sgdisk to switch between the two during testing.
// Will be removed.
func (op *Operation) SgdiskCommit() error {
opts := op.sgdiskBuildOptions()
if len(opts) == 0 {
return nil
}
op.logger.Info("running sgdisk with options: %v", opts)
cmd := exec.Command(distro.SgdiskCmd(), opts...)

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 {
return fmt.Errorf("create partitions failed: %v", err)
}

return nil
}

// Copy old functionality from sgdisk to switch between the two during testing.
// Will be removed.
func (op Operation) sgdiskBuildOptions() []string {
opts := []string{}

if op.wipe {
opts = append(opts, "--zap-all")
}

// Do all deletions before creations
for _, partition := range op.deletions {
opts = append(opts, fmt.Sprintf("--delete=%d", partition))
}

for _, p := range op.parts {
opts = append(opts, fmt.Sprintf("--new=%d:%s:+%s", p.Number, partitionGetStart(p), partitionGetSize(p)))
if p.Label != nil {
opts = append(opts, fmt.Sprintf("--change-name=%d:%s", p.Number, *p.Label))
}
if util.NotEmpty(p.TypeGUID) {
opts = append(opts, fmt.Sprintf("--typecode=%d:%s", p.Number, *p.TypeGUID))
}
if util.NotEmpty(p.GUID) {
opts = append(opts, fmt.Sprintf("--partition-guid=%d:%s", p.Number, *p.GUID))
}
}

for _, partition := range op.infos {
opts = append(opts, fmt.Sprintf("--info=%d", partition))
}

if len(opts) == 0 {
return nil
}

opts = append(opts, op.dev)
return opts
}

// Copy old functionality from sgdisk to switch between the two during testing.
// Will be removed.
func partitionGetStart(p Partition) string {
if p.StartSector != nil {
return fmt.Sprintf("%d", *p.StartSector)
}
return "0"
}

func partitionGetSize(p Partition) string {
if p.SizeInSectors != nil {
return fmt.Sprintf("%d", *p.SizeInSectors)
}
return "0"
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package disks
package sfdisk_test

import (
"errors"
"reflect"
"testing"

internalErrors "github.com/coreos/ignition/v2/config/shared/errors"
"github.com/coreos/ignition/v2/internal/device_managers"
"github.com/coreos/ignition/v2/internal/device_managers/sfdisk"
)

func TestPartitionParse(t *testing.T) {
Expand All @@ -14,7 +16,7 @@ func TestPartitionParse(t *testing.T) {
name string
sfdiskOut string
partitionNumbers []int
expectedOutput map[int]sfdiskOutput
expectedOutput map[int]device_managers.Output
expectedError error
}{
{
Expand All @@ -37,8 +39,8 @@ Device Boot Start End Sectors Size Id Type
/dev/vda1 2048 2057 10 5K 83 Linux
The partition table is unchanged (--no-act).`,
partitionNumbers: []int{1},
expectedOutput: map[int]sfdiskOutput{
1: {start: 2048, size: 10},
expectedOutput: map[int]device_managers.Output{
1: {Start: 2048, Size: 10},
},
expectedError: nil,
},
Expand All @@ -64,9 +66,9 @@ Device Boot Start End Sectors Size Id Type
/dev/vda2 4096 4105 10 5K 83 Linux
The partition table is unchanged (--no-act).`,
partitionNumbers: []int{1, 2},
expectedOutput: map[int]sfdiskOutput{
1: {start: 2048, size: 10},
2: {start: 4096, size: 10},
expectedOutput: map[int]device_managers.Output{
1: {Start: 2048, Size: 10},
2: {Start: 4096, Size: 10},
},
expectedError: nil,
},
Expand All @@ -84,16 +86,16 @@ Failed to add #1 partition: Numerical result out of range
Leaving.
`,
partitionNumbers: []int{1},
expectedOutput: map[int]sfdiskOutput{
1: {start: 0, size: 0},
expectedOutput: map[int]device_managers.Output{
1: {Start: 0, Size: 0},
},
expectedError: internalErrors.ErrBadSfdiskPretend,
},
}

op := sfdisk.Begin(nil, "")
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := parseSfdiskPretend(tt.sfdiskOut, tt.partitionNumbers)
output, err := op.ParseOutput(tt.sfdiskOut, tt.partitionNumbers)
if tt.expectedError != nil {
if !errors.Is(err, tt.expectedError) {
t.Errorf("#%d: bad error: result = %v, expected = %v", i, err, tt.expectedError)
Expand Down
Loading

0 comments on commit 1a20248

Please sign in to comment.