Skip to content
Draft

(WIP) #5482

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion pkg/daemon/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -1683,7 +1683,66 @@ func (dn *CoreOSDaemon) applyExtensions(oldConfig, newConfig *mcfgv1.MachineConf
// Add "update" to the start of argument list
args = append([]string{constants.RPMOSTreeUpdateArg}, args...)
logSystem("Applying extensions : %+q", args)
return runRpmOstree(args...)
if err := runRpmOstree(args...); err != nil {
return err
}

// Verify that the extensions were staged successfully
if err := dn.verifyExtensionsInStagedDeployment(newConfig); err != nil {
return fmt.Errorf("extension verification failed: %w", err)
}

return nil
}

// verifyExtensionsInStagedDeployment verifies that all required extension packages
// are present in the staged deployment after rpm-ostree has applied the changes.
// Assisted by: Cursor
func (dn *Daemon) verifyExtensionsInStagedDeployment(newConfig *mcfgv1.MachineConfig) error {
// Get the staged deployment
_, staged, err := dn.NodeUpdaterClient.GetBootedAndStagedDeployment()
if err != nil {
return fmt.Errorf("failed to get staged deployment: %w", err)
}
if staged == nil {
return fmt.Errorf("no staged deployment found after applying extensions")
}

// Get the required packages for the new config's extensions
requiredPackages, err := ctrlcommon.GetPackagesForSupportedExtensions(newConfig.Spec.Extensions)
if err != nil {
return fmt.Errorf("failed to get required packages for extensions: %w", err)
}

// Verify the extension packages have installed
if err := verifyExtensionPackagesInDeployment(requiredPackages, staged.RequestedPackages); err != nil {
return err
}

logSystem("Extension verification successful: all required packages are staged")
return nil
}

// verifyExtensionPackagesInDeployment checks that all required packages are present
// in the deployment's requested packages list.
// Assisted by: Cursor
func verifyExtensionPackagesInDeployment(requiredPackages []string, deploymentPackages []string) error {
// Build a set of packages in the deployment
deploymentPackageSet := sets.New(deploymentPackages...)

// Check that all required packages are present
missingPackages := []string{}
for _, pkg := range requiredPackages {
if !deploymentPackageSet.Has(pkg) {
missingPackages = append(missingPackages, pkg)
}
}

if len(missingPackages) > 0 {
return fmt.Errorf("extensions not staged correctly, missing packages: %v", missingPackages)
}

return nil
}

// switchKernel updates kernel on host with the kernelType specified in MachineConfig.
Expand Down
91 changes: 91 additions & 0 deletions pkg/daemon/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,97 @@ func TestFindClosestFilePolicyPathMatch(t *testing.T) {
}
}

// Assisted by: Cursor
func TestVerifyExtensionPackagesInDeployment(t *testing.T) {
tests := []struct {
name string
requiredPackages []string
deploymentPackages []string
expectError bool
errorContains string
}{
{
name: "no required packages, no deployment packages",
requiredPackages: []string{},
deploymentPackages: []string{},
expectError: false,
},
{
name: "no required packages, some deployment packages",
requiredPackages: []string{},
deploymentPackages: []string{"some-package", "another-package"},
expectError: false,
},
{
name: "all required packages present",
requiredPackages: []string{"crun-wasm"},
deploymentPackages: []string{"crun-wasm", "other-package"},
expectError: false,
},
{
name: "multiple required packages all present",
requiredPackages: []string{"NetworkManager-libreswan", "libreswan"},
deploymentPackages: []string{"NetworkManager-libreswan", "libreswan", "other-package"},
expectError: false,
},
{
name: "missing single required package",
requiredPackages: []string{"crun-wasm"},
deploymentPackages: []string{"other-package"},
expectError: true,
errorContains: "crun-wasm",
},
{
name: "missing multiple required packages",
requiredPackages: []string{"NetworkManager-libreswan", "libreswan"},
deploymentPackages: []string{"other-package"},
expectError: true,
errorContains: "NetworkManager-libreswan",
},
{
name: "partial packages present - missing one",
requiredPackages: []string{"NetworkManager-libreswan", "libreswan"},
deploymentPackages: []string{"NetworkManager-libreswan", "other-package"},
expectError: true,
errorContains: "libreswan",
},
{
name: "two-node-ha extension packages all present",
requiredPackages: []string{"pacemaker", "pcs", "fence-agents-all"},
deploymentPackages: []string{"pacemaker", "pcs", "fence-agents-all"},
expectError: false,
},
{
name: "two-node-ha extension packages partially present",
requiredPackages: []string{"pacemaker", "pcs", "fence-agents-all"},
deploymentPackages: []string{"pacemaker", "pcs"},
expectError: true,
errorContains: "fence-agents-all",
},
{
name: "required packages present with extra packages in deployment",
requiredPackages: []string{"usbguard"},
deploymentPackages: []string{"usbguard", "random-package-1", "random-package-2"},
expectError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := verifyExtensionPackagesInDeployment(tt.requiredPackages, tt.deploymentPackages)

if tt.expectError {
assert.Error(t, err, "expected error but got none")
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains, "error message should contain expected text")
}
} else {
assert.NoError(t, err, "unexpected error")
}
})
}
}

func TestGenerateExtensionsArgs(t *testing.T) {
tests := []struct {
name string
Expand Down