Skip to content

Commit

Permalink
Refactor manualCustomization to ease having different signature for h…
Browse files Browse the repository at this point in the history
…andlers

Other benefits:
- removes usage of reflection in this function and handlers.
- make it obvious when test cases are not handled
  • Loading branch information
upils committed Sep 26, 2023
1 parent aa6b0dc commit f403d0c
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 99 deletions.
43 changes: 17 additions & 26 deletions internal/statemachine/classic_states.go
Original file line number Diff line number Diff line change
Expand Up @@ -1000,38 +1000,29 @@ func (stateMachine *StateMachine) manualCustomization() error {
return fmt.Errorf("Error setting up /etc/resolv.conf in the chroot: \"%s\"", err.Error())
}

type customizationHandler struct {
inputData interface{}
handlerFunc func(interface{}, string, bool) error
err = manualCopyFile(classicStateMachine.ImageDef.Customization.Manual.CopyFile, stateMachine.tempDirs.chroot, stateMachine.commonFlags.Debug)
if err != nil {
return err
}
customizationHandlers := []customizationHandler{
{
inputData: classicStateMachine.ImageDef.Customization.Manual.CopyFile,
handlerFunc: manualCopyFile,
},
{
inputData: classicStateMachine.ImageDef.Customization.Manual.Execute,
handlerFunc: manualExecute,
},
{
inputData: classicStateMachine.ImageDef.Customization.Manual.TouchFile,
handlerFunc: manualTouchFile,
},
{
inputData: classicStateMachine.ImageDef.Customization.Manual.AddGroup,
handlerFunc: manualAddGroup,
},
{
inputData: classicStateMachine.ImageDef.Customization.Manual.AddUser,
handlerFunc: manualAddUser,
},

err = manualExecute(classicStateMachine.ImageDef.Customization.Manual.Execute, stateMachine.tempDirs.chroot, stateMachine.commonFlags.Debug)
if err != nil {
return err
}

for _, customization := range customizationHandlers {
err := customization.handlerFunc(customization.inputData, stateMachine.tempDirs.chroot, stateMachine.commonFlags.Debug)
err = manualTouchFile(classicStateMachine.ImageDef.Customization.Manual.TouchFile, stateMachine.tempDirs.chroot, stateMachine.commonFlags.Debug)
if err != nil {
return err
}

err = manualAddGroup(classicStateMachine.ImageDef.Customization.Manual.AddGroup, stateMachine.tempDirs.chroot, stateMachine.commonFlags.Debug)
if err != nil {
return err
}

err = manualAddUser(classicStateMachine.ImageDef.Customization.Manual.AddUser, stateMachine.tempDirs.chroot, stateMachine.commonFlags.Debug)
if err != nil {
return err
}

return nil
Expand Down
153 changes: 121 additions & 32 deletions internal/statemachine/classic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1261,8 +1261,8 @@ func TestFailedCustomizeCloudInit(t *testing.T) {
})
}

// TestManualCustomization unit tests the manualCustomization function
func TestManualCustomization(t *testing.T) {
// TestStateMachine_manualCustomization unit tests the manualCustomization function
func TestStateMachine_manualCustomization(t *testing.T) {
t.Run("test_manual_customization", func(t *testing.T) {
asserter := helper.Asserter{T: t}
saveCWD := helper.SaveCWD()
Expand Down Expand Up @@ -1353,53 +1353,142 @@ func TestManualCustomization(t *testing.T) {
})
}

// TestFailedManualCustomization tests failures in the manualCustomization function
func TestFailedManualCustomization(t *testing.T) {
// TestStateMachine_manualCustomization_fail tests failures in the manualCustomization function
func TestStateMachine_manualCustomization_fail(t *testing.T) {
t.Run("test_failed_manual_customization", func(t *testing.T) {
asserter := helper.Asserter{T: t}
saveCWD := helper.SaveCWD()
defer saveCWD()
restoreCWD := helper.SaveCWD()
t.Cleanup(restoreCWD)

var stateMachine ClassicStateMachine
stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
stateMachine.parent = &stateMachine

stateMachine.ImageDef = imagedefinition.ImageDefinition{
Customization: &imagedefinition.Customization{
Manual: &imagedefinition.Manual{
TouchFile: []*imagedefinition.TouchFile{
{
TouchPath: filepath.Join("this", "path", "does", "not", "exist"),
},
},
},
},
}

// need workdir set up for this
err := stateMachine.makeTemporaryDirectories()
asserter.AssertErrNil(err, true)

// create an /etc/resolv.conf in the chroot
err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "etc"), 0755)
asserter.AssertErrNil(err, true)
_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "etc", "resolv.conf"))
asserter.AssertErrNil(err, true)

// now test the failed touch file customization
err = stateMachine.manualCustomization()
asserter.AssertErrContains(err, "no such file or directory")
os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)

// mock helper.BackupAndCopyResolvConf
helperBackupAndCopyResolvConf = mockBackupAndCopyResolvConf
defer func() {
t.Cleanup(func() {
helperBackupAndCopyResolvConf = helper.BackupAndCopyResolvConf
}()
})
err = stateMachine.manualCustomization()
asserter.AssertErrContains(err, "Error setting up /etc/resolv.conf")
helperBackupAndCopyResolvConf = helper.BackupAndCopyResolvConf
})

tests := []struct {
name string
expectedErr string
manualCustomizations *imagedefinition.Manual
}{
{
name: "failing manualCopyFile",
expectedErr: "cp: cannot stat 'this/path/does/not/exist'",
manualCustomizations: &imagedefinition.Manual{
CopyFile: []*imagedefinition.CopyFile{
{
Source: filepath.Join("this", "path", "does", "not", "exist"),
Dest: filepath.Join("this", "path", "does", "not", "exist"),
},
},
},
},
{
name: "failing manualExecute",
expectedErr: "chroot: failed to run command",
manualCustomizations: &imagedefinition.Manual{
Execute: []*imagedefinition.Execute{
{
ExecutePath: filepath.Join("this", "path", "does", "not", "exist"),
},
},
},
},
{
name: "failing manualTouchFile",
expectedErr: "no such file or directory",
manualCustomizations: &imagedefinition.Manual{
TouchFile: []*imagedefinition.TouchFile{
{
TouchPath: filepath.Join("this", "path", "does", "not", "exist"),
},
},
},
},
{
name: "failing manualAddGroup",
expectedErr: "group 'root' already exists",
manualCustomizations: &imagedefinition.Manual{
AddGroup: []*imagedefinition.AddGroup{
{
GroupName: "root",
GroupID: "0",
},
},
},
},
{
name: "failing manualAddUser",
expectedErr: "user 'root' already exists",
manualCustomizations: &imagedefinition.Manual{
AddUser: []*imagedefinition.AddUser{
{
UserName: "root",
UserID: "0",
},
},
},
},
}
asserter := helper.Asserter{T: t}

var stateMachine ClassicStateMachine
stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
stateMachine.parent = &stateMachine
stateMachine.ImageDef = imagedefinition.ImageDefinition{
Architecture: getHostArch(),
Series: getHostSuite(),
Rootfs: &imagedefinition.Rootfs{
Archive: "ubuntu",
},
}

// need workdir set up for this
err := stateMachine.makeTemporaryDirectories()
asserter.AssertErrNil(err, true)

// also create chroot
err = stateMachine.createChroot()
asserter.AssertErrNil(err, true)

// create an /etc/resolv.conf in the chroot
err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "etc"), 0755)
asserter.AssertErrNil(err, true)
_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "etc", "resolv.conf"))
asserter.AssertErrNil(err, true)

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
asserter := helper.Asserter{T: t}
saveCWD := helper.SaveCWD()
t.Cleanup(saveCWD)

stateMachine.ImageDef.Customization = &imagedefinition.Customization{
Manual: tc.manualCustomizations,
}

err = stateMachine.manualCustomization()

if len(tc.expectedErr) == 0 {
asserter.AssertErrNil(err, true)
} else {
asserter.AssertErrContains(err, tc.expectedErr)
}

})
}
os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
}

// TestPrepareClassicImage unit tests the prepareClassicImage function
Expand Down
72 changes: 31 additions & 41 deletions internal/statemachine/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,30 +805,26 @@ func mountTempFS(targetDir, scratchDir, mountpoint string) (mountCmds, umountCmd
}

// manualCopyFile copies a file into the chroot
func manualCopyFile(copyFileInterfaces interface{}, targetDir string, debug bool) error {
copyFileSlice := reflect.ValueOf(copyFileInterfaces)
for i := 0; i < copyFileSlice.Len(); i++ {
copyFile := copyFileSlice.Index(i).Interface().(*imagedefinition.CopyFile)

func manualCopyFile(customizations []*imagedefinition.CopyFile, targetDir string, debug bool) error {
for _, c := range customizations {
// Copy the file into the specified location in the chroot
dest := filepath.Join(targetDir, copyFile.Dest)
dest := filepath.Join(targetDir, c.Dest)
// source := filepath.Join(s)
if debug {
fmt.Printf("Copying file \"%s\" to \"%s\"\n", copyFile.Source, dest)
fmt.Printf("Copying file \"%s\" to \"%s\"\n", c.Source, dest)
}
if err := osutilCopySpecialFile(copyFile.Source, dest); err != nil {
if err := osutilCopySpecialFile(c.Source, dest); err != nil {
return fmt.Errorf("Error copying file \"%s\" into chroot: %s",
copyFile.Source, err.Error())
c.Source, err.Error())
}
}
return nil
}

// manualExecute executes an executable file in the chroot
func manualExecute(executeInterfaces interface{}, targetDir string, debug bool) error {
executeSlice := reflect.ValueOf(executeInterfaces)
for i := 0; i < executeSlice.Len(); i++ {
execute := executeSlice.Index(i).Interface().(*imagedefinition.Execute)
executeCmd := execCommand("chroot", targetDir, execute.ExecutePath)
// manualExecute executes executable files in the chroot
func manualExecute(customizations []*imagedefinition.Execute, targetDir string, debug bool) error {
for _, c := range customizations {
executeCmd := execCommand("chroot", targetDir, c.ExecutePath)
if debug {
fmt.Printf("Executing command \"%s\"\n", executeCmd.String())
}
Expand All @@ -842,12 +838,10 @@ func manualExecute(executeInterfaces interface{}, targetDir string, debug bool)
return nil
}

// manualTouchFile touches a file in the chroot
func manualTouchFile(touchFileInterfaces interface{}, targetDir string, debug bool) error {
touchFileSlice := reflect.ValueOf(touchFileInterfaces)
for i := 0; i < touchFileSlice.Len(); i++ {
touchFile := touchFileSlice.Index(i).Interface().(*imagedefinition.TouchFile)
fullPath := filepath.Join(targetDir, touchFile.TouchPath)
// manualTouchFile touches files in the chroot
func manualTouchFile(customizations []*imagedefinition.TouchFile, targetDir string, debug bool) error {
for _, c := range customizations {
fullPath := filepath.Join(targetDir, c.TouchPath)
if debug {
fmt.Printf("Creating empty file \"%s\"\n", fullPath)
}
Expand All @@ -859,16 +853,14 @@ func manualTouchFile(touchFileInterfaces interface{}, targetDir string, debug bo
return nil
}

// manualAddGroup adds a group in the chroot
func manualAddGroup(addGroupInterfaces interface{}, targetDir string, debug bool) error {
addGroupSlice := reflect.ValueOf(addGroupInterfaces)
for i := 0; i < addGroupSlice.Len(); i++ {
addGroup := addGroupSlice.Index(i).Interface().(*imagedefinition.AddGroup)
addGroupCmd := execCommand("chroot", targetDir, "groupadd", addGroup.GroupName)
debugStatement := fmt.Sprintf("Adding group \"%s\"\n", addGroup.GroupName)
if addGroup.GroupID != "" {
addGroupCmd.Args = append(addGroupCmd.Args, []string{"--gid", addGroup.GroupID}...)
debugStatement = fmt.Sprintf("%s with GID %s\n", strings.TrimSpace(debugStatement), addGroup.GroupID)
// manualAddGroup adds groups in the chroot
func manualAddGroup(customizations []*imagedefinition.AddGroup, targetDir string, debug bool) error {
for _, c := range customizations {
addGroupCmd := execCommand("chroot", targetDir, "groupadd", c.GroupName)
debugStatement := fmt.Sprintf("Adding group \"%s\"\n", c.GroupName)
if c.GroupID != "" {
addGroupCmd.Args = append(addGroupCmd.Args, []string{"--gid", c.GroupID}...)
debugStatement = fmt.Sprintf("%s with GID %s\n", strings.TrimSpace(debugStatement), c.GroupID)
}
if debug {
fmt.Print(debugStatement)
Expand All @@ -883,16 +875,14 @@ func manualAddGroup(addGroupInterfaces interface{}, targetDir string, debug bool
return nil
}

// manualAddUser adds a group in the chroot
func manualAddUser(addUserInterfaces interface{}, targetDir string, debug bool) error {
addUserSlice := reflect.ValueOf(addUserInterfaces)
for i := 0; i < addUserSlice.Len(); i++ {
addUser := addUserSlice.Index(i).Interface().(*imagedefinition.AddUser)
addUserCmd := execCommand("chroot", targetDir, "useradd", addUser.UserName)
debugStatement := fmt.Sprintf("Adding user \"%s\"\n", addUser.UserName)
if addUser.UserID != "" {
addUserCmd.Args = append(addUserCmd.Args, []string{"--uid", addUser.UserID}...)
debugStatement = fmt.Sprintf("%s with UID %s\n", strings.TrimSpace(debugStatement), addUser.UserID)
// manualAddUser adds users in the chroot
func manualAddUser(customizations []*imagedefinition.AddUser, targetDir string, debug bool) error {
for _, c := range customizations {
addUserCmd := execCommand("chroot", targetDir, "useradd", c.UserName)
debugStatement := fmt.Sprintf("Adding user \"%s\"\n", c.UserName)
if c.UserID != "" {
addUserCmd.Args = append(addUserCmd.Args, []string{"--uid", c.UserID}...)
debugStatement = fmt.Sprintf("%s with UID %s\n", strings.TrimSpace(debugStatement), c.UserID)
}
if debug {
fmt.Print(debugStatement)
Expand Down

0 comments on commit f403d0c

Please sign in to comment.