From 487f9ef174a2b73a6ba4cceb04f2ec2a7775dd06 Mon Sep 17 00:00:00 2001 From: dbw7 Date: Tue, 27 Aug 2024 22:58:26 -0400 Subject: [PATCH 1/8] elemental validation and SL Micro 6.0 handling improvements --- RELEASE_NOTES.md | 2 + pkg/eib/eib.go | 21 +-- pkg/image/validation/elemental.go | 67 ++++++++++ pkg/image/validation/elemental_test.go | 174 +++++++++++++++++++++++++ pkg/image/validation/validation.go | 9 +- 5 files changed, 261 insertions(+), 12 deletions(-) create mode 100644 pkg/image/validation/elemental.go create mode 100644 pkg/image/validation/elemental_test.go diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4bae9b93..1ca5772c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -11,6 +11,8 @@ * Helm chart installation backOffLimit changed from 1000(default) to 20 * Improved Kubernetes resource installation handling * Ensure that kernel arguments are applied during firstboot when kexec is used in ISO installations +* Improved Elemental handling when using SL Micro 6.0 +* Added Elemental configuration validation ## API diff --git a/pkg/eib/eib.go b/pkg/eib/eib.go index d6f1d8a4..0624c27a 100644 --- a/pkg/eib/eib.go +++ b/pkg/eib/eib.go @@ -98,14 +98,19 @@ func appendElementalRPMs(ctx *image.Context) { log.AuditInfo("Elemental registration is configured. The necessary RPM packages will be downloaded.") - appendRPMs(ctx, []image.AddRepo{ - { - URL: ctx.ArtifactSources.Elemental.RegisterRepository, - }, - { - URL: ctx.ArtifactSources.Elemental.SystemAgentRepository, - }, - }, combustion.ElementalPackages...) + if ctx.ImageDefinition.APIVersion == "1.0" { + appendRPMs(ctx, []image.AddRepo{ + { + URL: ctx.ArtifactSources.Elemental.RegisterRepository, + }, + { + URL: ctx.ArtifactSources.Elemental.SystemAgentRepository, + }, + }, combustion.ElementalPackages...) + } else if ctx.ImageDefinition.APIVersion == "1.1" { + appendRPMs(ctx, nil, combustion.ElementalPackages...) + } + } func appendFips(ctx *image.Context) { diff --git a/pkg/image/validation/elemental.go b/pkg/image/validation/elemental.go new file mode 100644 index 00000000..68165d0c --- /dev/null +++ b/pkg/image/validation/elemental.go @@ -0,0 +1,67 @@ +package validation + +import ( + "errors" + "fmt" + "github.com/suse-edge/edge-image-builder/pkg/image" + "go.uber.org/zap" + "os" + "path/filepath" +) + +const ( + elementalComponent = "Elemental" +) + +func validateElemental(ctx *image.Context) []FailedValidation { + var failures []FailedValidation + + elementalConfigDir := filepath.Join(ctx.ImageConfigDir, "elemental") + failures = append(failures, validateElementalDir(elementalConfigDir)...) + + if ctx.ImageDefinition.APIVersion == "1.1" && ctx.ImageDefinition.OperatingSystem.Packages.RegCode == "" { + failures = append(failures, FailedValidation{ + UserMessage: "Operating system package registration code field must be defined when using Elemental with SL Micro 6.0", + }) + } + + return failures +} + +func validateElementalDir(elementalConfigDir string) []FailedValidation { + var failures []FailedValidation + + elementalConfigDirEntries, err := os.ReadDir(elementalConfigDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + + zap.S().Errorf("Elemental config directory could not be read: %s", err) + failures = append(failures, FailedValidation{ + UserMessage: fmt.Sprintf("Elemental config directory could not be read: %s", err), + }) + } + + if len(elementalConfigDirEntries) == 0 { + failures = append(failures, FailedValidation{ + UserMessage: "Elemental config directory should not be present if it is empty", + }) + } + + if len(elementalConfigDirEntries) > 1 { + failures = append(failures, FailedValidation{ + UserMessage: "Elemental config directory should only contain a singular 'elemental_config.yaml' file", + }) + } + + if len(elementalConfigDirEntries) == 1 { + if elementalConfigDirEntries[0].Name() != "elemental_config.yaml" { + failures = append(failures, FailedValidation{ + UserMessage: "Elemental config file should only be named `elemental_config.yaml`", + }) + } + } + + return failures +} diff --git a/pkg/image/validation/elemental_test.go b/pkg/image/validation/elemental_test.go new file mode 100644 index 00000000..52609930 --- /dev/null +++ b/pkg/image/validation/elemental_test.go @@ -0,0 +1,174 @@ +package validation + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/suse-edge/edge-image-builder/pkg/image" + "os" + "path/filepath" + "testing" +) + +func TestValidateElemental(t *testing.T) { + configDir, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) + + defer func() { + assert.NoError(t, os.RemoveAll(configDir)) + }() + + elementalDir := filepath.Join(configDir, "elemental") + require.NoError(t, os.MkdirAll(elementalDir, os.ModePerm)) + + validElementalConfig := filepath.Join(elementalDir, "elemental_config.yaml") + require.NoError(t, os.WriteFile(validElementalConfig, []byte(""), 0o600)) + + tests := map[string]struct { + ImageDefinition image.Definition + ExpectedFailedMessages []string + }{ + `valid 1.1`: { + ImageDefinition: image.Definition{ + APIVersion: "1.1", + OperatingSystem: image.OperatingSystem{ + Packages: image.Packages{ + RegCode: "registration-code", + }, + }, + }, + }, + `1.1 no registration code`: { + ImageDefinition: image.Definition{ + APIVersion: "1.1", + }, + ExpectedFailedMessages: []string{ + "Operating system package registration code field must be defined when using Elemental with SL Micro 6.0", + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ctx := image.Context{ + ImageConfigDir: configDir, + ImageDefinition: &test.ImageDefinition, + } + failures := validateElemental(&ctx) + assert.Len(t, failures, len(test.ExpectedFailedMessages)) + + var foundMessages []string + for _, foundValidation := range failures { + foundMessages = append(foundMessages, foundValidation.UserMessage) + } + + for _, expectedMessage := range test.ExpectedFailedMessages { + assert.Contains(t, foundMessages, expectedMessage) + } + + }) + } +} + +func TestValidateElementalConfigDir(t *testing.T) { + configDirValid, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) + + configDirEmpty, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) + + configDirMultipleFiles, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) + + configDirInvalidName, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) + + configDirUnreadable, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) + + defer func() { + assert.NoError(t, os.RemoveAll(configDirValid)) + assert.NoError(t, os.RemoveAll(configDirEmpty)) + assert.NoError(t, os.RemoveAll(configDirMultipleFiles)) + assert.NoError(t, os.RemoveAll(configDirInvalidName)) + assert.NoError(t, os.RemoveAll(configDirUnreadable)) + }() + + elementalDirValid := filepath.Join(configDirValid, "elemental") + require.NoError(t, os.MkdirAll(elementalDirValid, os.ModePerm)) + + validElementalConfig := filepath.Join(elementalDirValid, "elemental_config.yaml") + require.NoError(t, os.WriteFile(validElementalConfig, []byte(""), 0o600)) + + elementalDirEmpty := filepath.Join(configDirEmpty, "elemental") + require.NoError(t, os.MkdirAll(elementalDirEmpty, os.ModePerm)) + + elementalDirMultipleFiles := filepath.Join(configDirMultipleFiles, "elemental") + require.NoError(t, os.MkdirAll(elementalDirMultipleFiles, os.ModePerm)) + + firstElementalConfig := filepath.Join(elementalDirMultipleFiles, "elemental_config1.yaml") + require.NoError(t, os.WriteFile(firstElementalConfig, []byte(""), 0o600)) + secondElementalConfig := filepath.Join(elementalDirMultipleFiles, "elemental_config2.yaml") + require.NoError(t, os.WriteFile(secondElementalConfig, []byte(""), 0o600)) + + elementalDirInvalidName := filepath.Join(configDirInvalidName, "elemental") + require.NoError(t, os.MkdirAll(elementalDirInvalidName, os.ModePerm)) + + invalidElementalConfig := filepath.Join(elementalDirInvalidName, "elemental.yaml") + require.NoError(t, os.WriteFile(invalidElementalConfig, []byte(""), 0o600)) + + elementalDirUnreadable := filepath.Join(configDirUnreadable, "elemental") + require.NoError(t, os.MkdirAll(elementalDirUnreadable, os.ModePerm)) + require.NoError(t, os.Chmod(elementalDirUnreadable, 0333)) + + tests := map[string]struct { + ExpectedFailedMessages []string + ElementalDir string + }{ + `valid elemental dir`: { + ElementalDir: elementalDirValid, + }, + `empty elemental dir`: { + ElementalDir: elementalDirEmpty, + ExpectedFailedMessages: []string{ + "Elemental config directory should not be present if it is empty", + }, + }, + `multiple files in elemental dir`: { + ElementalDir: elementalDirMultipleFiles, + ExpectedFailedMessages: []string{ + "Elemental config directory should only contain a singular 'elemental_config.yaml' file", + }, + }, + `invalid name in elemental dir`: { + ElementalDir: elementalDirInvalidName, + ExpectedFailedMessages: []string{ + "Elemental config file should only be named `elemental_config.yaml`", + }, + }, + `unreadable elemental dir`: { + ElementalDir: elementalDirUnreadable, + ExpectedFailedMessages: []string{ + fmt.Sprintf("Elemental config directory could not be read: open %s: permission denied", elementalDirUnreadable), + "Elemental config directory should not be present if it is empty", + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + failures := validateElementalDir(test.ElementalDir) + assert.Len(t, failures, len(test.ExpectedFailedMessages)) + + var foundMessages []string + for _, foundValidation := range failures { + foundMessages = append(foundMessages, foundValidation.UserMessage) + } + + for _, expectedMessage := range test.ExpectedFailedMessages { + assert.Contains(t, foundMessages, expectedMessage) + } + + }) + } +} diff --git a/pkg/image/validation/validation.go b/pkg/image/validation/validation.go index eddf12b2..a149ef7c 100644 --- a/pkg/image/validation/validation.go +++ b/pkg/image/validation/validation.go @@ -16,10 +16,11 @@ func ValidateDefinition(ctx *image.Context) map[string][]FailedValidation { validations := map[string]validateComponent{ versionComponent: validateVersion, - imageComponent: validateImage, - osComponent: validateOperatingSystem, - registryComponent: validateEmbeddedArtifactRegistry, - k8sComponent: validateKubernetes, + imageComponent: validateImage, + osComponent: validateOperatingSystem, + registryComponent: validateEmbeddedArtifactRegistry, + k8sComponent: validateKubernetes, + elementalComponent: validateElemental, } for componentName, v := range validations { componentFailures := v(ctx) From 7030979a095f41b8aac875303cf6e960230f7820 Mon Sep 17 00:00:00 2001 From: dbw7 Date: Tue, 27 Aug 2024 23:09:29 -0400 Subject: [PATCH 2/8] add note to docs --- docs/building-images.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/building-images.md b/docs/building-images.md index b9501f45..27534495 100644 --- a/docs/building-images.md +++ b/docs/building-images.md @@ -484,6 +484,9 @@ the built image and used to register with Elemental on boot. > To ensure a successful build, this process requires the ```--privileged``` flag to be passed to the > ```podman run``` command. For more info on why this is required, please see > [Package resolution design](design/pkg-resolution.md#running-the-eib-container). +> +> Additionally, when using SL Micro 6.0, an [`sccRegistrationCode`](#operating-system) must be provided in the `operatingSystem` section +> of the image definition so that the necessary Elemental RPMs can be downloaded. ## Operating System Files From 0da7b1801edea08dcb2984e841458a36723bb2fa Mon Sep 17 00:00:00 2001 From: dbw7 Date: Tue, 27 Aug 2024 23:19:25 -0400 Subject: [PATCH 3/8] fixes for linter --- pkg/image/validation/elemental.go | 5 +++-- pkg/image/validation/elemental_test.go | 17 +++++++++-------- pkg/image/validation/validation.go | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/image/validation/elemental.go b/pkg/image/validation/elemental.go index 68165d0c..9e2e9450 100644 --- a/pkg/image/validation/elemental.go +++ b/pkg/image/validation/elemental.go @@ -3,10 +3,11 @@ package validation import ( "errors" "fmt" - "github.com/suse-edge/edge-image-builder/pkg/image" - "go.uber.org/zap" "os" "path/filepath" + + "github.com/suse-edge/edge-image-builder/pkg/image" + "go.uber.org/zap" ) const ( diff --git a/pkg/image/validation/elemental_test.go b/pkg/image/validation/elemental_test.go index 52609930..36156a48 100644 --- a/pkg/image/validation/elemental_test.go +++ b/pkg/image/validation/elemental_test.go @@ -2,12 +2,13 @@ package validation import ( "fmt" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/suse-edge/edge-image-builder/pkg/image" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/suse-edge/edge-image-builder/pkg/image" ) func TestValidateElemental(t *testing.T) { @@ -25,11 +26,11 @@ func TestValidateElemental(t *testing.T) { require.NoError(t, os.WriteFile(validElementalConfig, []byte(""), 0o600)) tests := map[string]struct { - ImageDefinition image.Definition + ImageDefinition *image.Definition ExpectedFailedMessages []string }{ `valid 1.1`: { - ImageDefinition: image.Definition{ + ImageDefinition: &image.Definition{ APIVersion: "1.1", OperatingSystem: image.OperatingSystem{ Packages: image.Packages{ @@ -39,7 +40,7 @@ func TestValidateElemental(t *testing.T) { }, }, `1.1 no registration code`: { - ImageDefinition: image.Definition{ + ImageDefinition: &image.Definition{ APIVersion: "1.1", }, ExpectedFailedMessages: []string{ @@ -52,7 +53,7 @@ func TestValidateElemental(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := image.Context{ ImageConfigDir: configDir, - ImageDefinition: &test.ImageDefinition, + ImageDefinition: test.ImageDefinition, } failures := validateElemental(&ctx) assert.Len(t, failures, len(test.ExpectedFailedMessages)) @@ -119,7 +120,7 @@ func TestValidateElementalConfigDir(t *testing.T) { elementalDirUnreadable := filepath.Join(configDirUnreadable, "elemental") require.NoError(t, os.MkdirAll(elementalDirUnreadable, os.ModePerm)) - require.NoError(t, os.Chmod(elementalDirUnreadable, 0333)) + require.NoError(t, os.Chmod(elementalDirUnreadable, 0o333)) tests := map[string]struct { ExpectedFailedMessages []string diff --git a/pkg/image/validation/validation.go b/pkg/image/validation/validation.go index a149ef7c..7f08ccc4 100644 --- a/pkg/image/validation/validation.go +++ b/pkg/image/validation/validation.go @@ -15,7 +15,7 @@ func ValidateDefinition(ctx *image.Context) map[string][]FailedValidation { failures := map[string][]FailedValidation{} validations := map[string]validateComponent{ - versionComponent: validateVersion, + versionComponent: validateVersion, imageComponent: validateImage, osComponent: validateOperatingSystem, registryComponent: validateEmbeddedArtifactRegistry, From 50ad797c1c64c2f3f68c5408541d6cb78d5bc253 Mon Sep 17 00:00:00 2001 From: dbw7 Date: Fri, 20 Sep 2024 12:28:33 -0400 Subject: [PATCH 4/8] feedback updates --- pkg/eib/eib.go | 14 +------- pkg/image/validation/elemental.go | 46 ++++++++++++++------------ pkg/image/validation/elemental_test.go | 5 +-- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/pkg/eib/eib.go b/pkg/eib/eib.go index 0624c27a..5b189926 100644 --- a/pkg/eib/eib.go +++ b/pkg/eib/eib.go @@ -97,19 +97,7 @@ func appendElementalRPMs(ctx *image.Context) { } log.AuditInfo("Elemental registration is configured. The necessary RPM packages will be downloaded.") - - if ctx.ImageDefinition.APIVersion == "1.0" { - appendRPMs(ctx, []image.AddRepo{ - { - URL: ctx.ArtifactSources.Elemental.RegisterRepository, - }, - { - URL: ctx.ArtifactSources.Elemental.SystemAgentRepository, - }, - }, combustion.ElementalPackages...) - } else if ctx.ImageDefinition.APIVersion == "1.1" { - appendRPMs(ctx, nil, combustion.ElementalPackages...) - } + appendRPMs(ctx, nil, combustion.ElementalPackages...) } diff --git a/pkg/image/validation/elemental.go b/pkg/image/validation/elemental.go index 9e2e9450..9fe8a80a 100644 --- a/pkg/image/validation/elemental.go +++ b/pkg/image/validation/elemental.go @@ -1,26 +1,37 @@ package validation import ( - "errors" "fmt" "os" "path/filepath" "github.com/suse-edge/edge-image-builder/pkg/image" - "go.uber.org/zap" ) const ( - elementalComponent = "Elemental" + elementalComponent = "Elemental" + elementalConfigFilename = "elemental_config.yaml" ) func validateElemental(ctx *image.Context) []FailedValidation { var failures []FailedValidation elementalConfigDir := filepath.Join(ctx.ImageConfigDir, "elemental") + if _, err := os.Stat(elementalConfigDir); err != nil { + if os.IsNotExist(err) { + return nil + } + + failures = append(failures, FailedValidation{ + UserMessage: fmt.Sprintf("Elemental config directory could not be read: %s", err), + Error: err, + }) + return failures + } + failures = append(failures, validateElementalDir(elementalConfigDir)...) - if ctx.ImageDefinition.APIVersion == "1.1" && ctx.ImageDefinition.OperatingSystem.Packages.RegCode == "" { + if ctx.ImageDefinition.OperatingSystem.Packages.RegCode == "" { failures = append(failures, FailedValidation{ UserMessage: "Operating system package registration code field must be defined when using Elemental with SL Micro 6.0", }) @@ -34,34 +45,27 @@ func validateElementalDir(elementalConfigDir string) []FailedValidation { elementalConfigDirEntries, err := os.ReadDir(elementalConfigDir) if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil - } - - zap.S().Errorf("Elemental config directory could not be read: %s", err) failures = append(failures, FailedValidation{ UserMessage: fmt.Sprintf("Elemental config directory could not be read: %s", err), + Error: err, }) } - if len(elementalConfigDirEntries) == 0 { + switch len(elementalConfigDirEntries) { + case 0: failures = append(failures, FailedValidation{ UserMessage: "Elemental config directory should not be present if it is empty", }) - } - - if len(elementalConfigDirEntries) > 1 { - failures = append(failures, FailedValidation{ - UserMessage: "Elemental config directory should only contain a singular 'elemental_config.yaml' file", - }) - } - - if len(elementalConfigDirEntries) == 1 { - if elementalConfigDirEntries[0].Name() != "elemental_config.yaml" { + case 1: + if elementalConfigDirEntries[0].Name() != elementalConfigFilename { failures = append(failures, FailedValidation{ - UserMessage: "Elemental config file should only be named `elemental_config.yaml`", + UserMessage: fmt.Sprintf("Elemental config file should only be named `%s`", elementalConfigFilename), }) } + default: + failures = append(failures, FailedValidation{ + UserMessage: fmt.Sprintf("Elemental config directory should only contain a singular '%s' file", elementalConfigFilename), + }) } return failures diff --git a/pkg/image/validation/elemental_test.go b/pkg/image/validation/elemental_test.go index 36156a48..d546c933 100644 --- a/pkg/image/validation/elemental_test.go +++ b/pkg/image/validation/elemental_test.go @@ -31,7 +31,6 @@ func TestValidateElemental(t *testing.T) { }{ `valid 1.1`: { ImageDefinition: &image.Definition{ - APIVersion: "1.1", OperatingSystem: image.OperatingSystem{ Packages: image.Packages{ RegCode: "registration-code", @@ -40,9 +39,7 @@ func TestValidateElemental(t *testing.T) { }, }, `1.1 no registration code`: { - ImageDefinition: &image.Definition{ - APIVersion: "1.1", - }, + ImageDefinition: &image.Definition{}, ExpectedFailedMessages: []string{ "Operating system package registration code field must be defined when using Elemental with SL Micro 6.0", }, From 04e9a651ed86011aa5357c153c4c72d1eb48f45c Mon Sep 17 00:00:00 2001 From: dbw7 Date: Fri, 20 Sep 2024 16:29:56 -0400 Subject: [PATCH 5/8] Update pkg/image/validation/elemental.go Co-authored-by: Atanas Dinov --- pkg/image/validation/elemental.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/image/validation/elemental.go b/pkg/image/validation/elemental.go index 9fe8a80a..d5ca880c 100644 --- a/pkg/image/validation/elemental.go +++ b/pkg/image/validation/elemental.go @@ -23,7 +23,7 @@ func validateElemental(ctx *image.Context) []FailedValidation { } failures = append(failures, FailedValidation{ - UserMessage: fmt.Sprintf("Elemental config directory could not be read: %s", err), + UserMessage: "Elemental config directory could not be read", Error: err, }) return failures From 97b862de991ef6a2fc10357ac1456a54b2ca9a9f Mon Sep 17 00:00:00 2001 From: dbw7 Date: Fri, 20 Sep 2024 16:30:02 -0400 Subject: [PATCH 6/8] Update pkg/image/validation/elemental.go Co-authored-by: Atanas Dinov --- pkg/image/validation/elemental.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/image/validation/elemental.go b/pkg/image/validation/elemental.go index d5ca880c..afec4daa 100644 --- a/pkg/image/validation/elemental.go +++ b/pkg/image/validation/elemental.go @@ -46,7 +46,7 @@ func validateElementalDir(elementalConfigDir string) []FailedValidation { elementalConfigDirEntries, err := os.ReadDir(elementalConfigDir) if err != nil { failures = append(failures, FailedValidation{ - UserMessage: fmt.Sprintf("Elemental config directory could not be read: %s", err), + UserMessage: "Elemental config directory could not be read", Error: err, }) } From be7963678eb43128cc7d3a2e8fd0658b7cd2ab42 Mon Sep 17 00:00:00 2001 From: dbw7 Date: Mon, 23 Sep 2024 02:05:44 -0400 Subject: [PATCH 7/8] feedback updates --- config/artifacts.yaml | 3 - pkg/image/validation/elemental.go | 2 + pkg/image/validation/elemental_test.go | 142 +++++++++++-------------- 3 files changed, 62 insertions(+), 85 deletions(-) diff --git a/config/artifacts.yaml b/config/artifacts.yaml index 48a04c55..0d8b2296 100644 --- a/config/artifacts.yaml +++ b/config/artifacts.yaml @@ -6,9 +6,6 @@ endpoint-copier-operator: chart: endpoint-copier-operator repository: https://suse-edge.github.io/charts version: 0.2.1 -elemental: - register-repository: https://download.opensuse.org/repositories/isv:/Rancher:/Elemental:/Staging/standard - system-agent-repository: https://download.opensuse.org/repositories/isv:/Rancher:/Elemental:/Staging/standard kubernetes: k3s: selinuxPackage: k3s-selinux-1.6-1.slemicro.noarch diff --git a/pkg/image/validation/elemental.go b/pkg/image/validation/elemental.go index afec4daa..e7bbc36d 100644 --- a/pkg/image/validation/elemental.go +++ b/pkg/image/validation/elemental.go @@ -49,6 +49,8 @@ func validateElementalDir(elementalConfigDir string) []FailedValidation { UserMessage: "Elemental config directory could not be read", Error: err, }) + + return failures } switch len(elementalConfigDirEntries) { diff --git a/pkg/image/validation/elemental_test.go b/pkg/image/validation/elemental_test.go index d546c933..7d032f09 100644 --- a/pkg/image/validation/elemental_test.go +++ b/pkg/image/validation/elemental_test.go @@ -1,7 +1,6 @@ package validation import ( - "fmt" "os" "path/filepath" "testing" @@ -11,7 +10,14 @@ import ( "github.com/suse-edge/edge-image-builder/pkg/image" ) -func TestValidateElemental(t *testing.T) { +func TestValidateElementalNoDir(t *testing.T) { + ctx := image.Context{} + + failures := validateElemental(&ctx) + assert.Len(t, failures, 0) +} + +func TestValidateElementalValid(t *testing.T) { configDir, err := os.MkdirTemp("", "eib-config-") require.NoError(t, err) @@ -29,7 +35,7 @@ func TestValidateElemental(t *testing.T) { ImageDefinition *image.Definition ExpectedFailedMessages []string }{ - `valid 1.1`: { + `valid`: { ImageDefinition: &image.Definition{ OperatingSystem: image.OperatingSystem{ Packages: image.Packages{ @@ -38,7 +44,7 @@ func TestValidateElemental(t *testing.T) { }, }, }, - `1.1 no registration code`: { + `no registration code`: { ImageDefinition: &image.Definition{}, ExpectedFailedMessages: []string{ "Operating system package registration code field must be defined when using Elemental with SL Micro 6.0", @@ -68,105 +74,77 @@ func TestValidateElemental(t *testing.T) { } } -func TestValidateElementalConfigDir(t *testing.T) { - configDirValid, err := os.MkdirTemp("", "eib-config-") +func TestValidateElementalConfigDirValid(t *testing.T) { + configDir, err := os.MkdirTemp("", "eib-config-") require.NoError(t, err) - configDirEmpty, err := os.MkdirTemp("", "eib-config-") - require.NoError(t, err) + defer func() { + assert.NoError(t, os.RemoveAll(configDir)) + }() - configDirMultipleFiles, err := os.MkdirTemp("", "eib-config-") - require.NoError(t, err) + elementalDir := filepath.Join(configDir, "elemental") + require.NoError(t, os.MkdirAll(elementalDir, os.ModePerm)) - configDirInvalidName, err := os.MkdirTemp("", "eib-config-") - require.NoError(t, err) + elementalConfig := filepath.Join(elementalDir, "elemental_config.yaml") + require.NoError(t, os.WriteFile(elementalConfig, []byte(""), 0o600)) + + failures := validateElementalDir(elementalDir) + assert.Len(t, failures, 0) +} - configDirUnreadable, err := os.MkdirTemp("", "eib-config-") +func TestValidateElementalConfigDirEmptyDir(t *testing.T) { + configDir, err := os.MkdirTemp("", "eib-config-") require.NoError(t, err) defer func() { - assert.NoError(t, os.RemoveAll(configDirValid)) - assert.NoError(t, os.RemoveAll(configDirEmpty)) - assert.NoError(t, os.RemoveAll(configDirMultipleFiles)) - assert.NoError(t, os.RemoveAll(configDirInvalidName)) - assert.NoError(t, os.RemoveAll(configDirUnreadable)) + assert.NoError(t, os.RemoveAll(configDir)) }() - elementalDirValid := filepath.Join(configDirValid, "elemental") - require.NoError(t, os.MkdirAll(elementalDirValid, os.ModePerm)) + elementalDir := filepath.Join(configDir, "elemental") + require.NoError(t, os.MkdirAll(elementalDir, os.ModePerm)) + + failures := validateElementalDir(elementalDir) + assert.Len(t, failures, 1) - validElementalConfig := filepath.Join(elementalDirValid, "elemental_config.yaml") - require.NoError(t, os.WriteFile(validElementalConfig, []byte(""), 0o600)) + assert.Contains(t, failures[0].UserMessage, "Elemental config directory should not be present if it is empty") +} - elementalDirEmpty := filepath.Join(configDirEmpty, "elemental") - require.NoError(t, os.MkdirAll(elementalDirEmpty, os.ModePerm)) +func TestValidateElementalConfigDirMultipleFiles(t *testing.T) { + configDir, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) - elementalDirMultipleFiles := filepath.Join(configDirMultipleFiles, "elemental") - require.NoError(t, os.MkdirAll(elementalDirMultipleFiles, os.ModePerm)) + defer func() { + assert.NoError(t, os.RemoveAll(configDir)) + }() - firstElementalConfig := filepath.Join(elementalDirMultipleFiles, "elemental_config1.yaml") + elementalDir := filepath.Join(configDir, "elemental") + require.NoError(t, os.MkdirAll(elementalDir, os.ModePerm)) + + firstElementalConfig := filepath.Join(elementalDir, "elemental_config1.yaml") require.NoError(t, os.WriteFile(firstElementalConfig, []byte(""), 0o600)) - secondElementalConfig := filepath.Join(elementalDirMultipleFiles, "elemental_config2.yaml") + secondElementalConfig := filepath.Join(elementalDir, "elemental_config2.yaml") require.NoError(t, os.WriteFile(secondElementalConfig, []byte(""), 0o600)) - elementalDirInvalidName := filepath.Join(configDirInvalidName, "elemental") - require.NoError(t, os.MkdirAll(elementalDirInvalidName, os.ModePerm)) - - invalidElementalConfig := filepath.Join(elementalDirInvalidName, "elemental.yaml") - require.NoError(t, os.WriteFile(invalidElementalConfig, []byte(""), 0o600)) + failures := validateElementalDir(elementalDir) + assert.Len(t, failures, 1) - elementalDirUnreadable := filepath.Join(configDirUnreadable, "elemental") - require.NoError(t, os.MkdirAll(elementalDirUnreadable, os.ModePerm)) - require.NoError(t, os.Chmod(elementalDirUnreadable, 0o333)) + assert.Contains(t, failures[0].UserMessage, "Elemental config directory should only contain a singular 'elemental_config.yaml' file") +} - tests := map[string]struct { - ExpectedFailedMessages []string - ElementalDir string - }{ - `valid elemental dir`: { - ElementalDir: elementalDirValid, - }, - `empty elemental dir`: { - ElementalDir: elementalDirEmpty, - ExpectedFailedMessages: []string{ - "Elemental config directory should not be present if it is empty", - }, - }, - `multiple files in elemental dir`: { - ElementalDir: elementalDirMultipleFiles, - ExpectedFailedMessages: []string{ - "Elemental config directory should only contain a singular 'elemental_config.yaml' file", - }, - }, - `invalid name in elemental dir`: { - ElementalDir: elementalDirInvalidName, - ExpectedFailedMessages: []string{ - "Elemental config file should only be named `elemental_config.yaml`", - }, - }, - `unreadable elemental dir`: { - ElementalDir: elementalDirUnreadable, - ExpectedFailedMessages: []string{ - fmt.Sprintf("Elemental config directory could not be read: open %s: permission denied", elementalDirUnreadable), - "Elemental config directory should not be present if it is empty", - }, - }, - } +func TestValidateElementalConfigDirUnreadable(t *testing.T) { + configDir, err := os.MkdirTemp("", "eib-config-") + require.NoError(t, err) - for name, test := range tests { - t.Run(name, func(t *testing.T) { - failures := validateElementalDir(test.ElementalDir) - assert.Len(t, failures, len(test.ExpectedFailedMessages)) + defer func() { + assert.NoError(t, os.RemoveAll(configDir)) + }() - var foundMessages []string - for _, foundValidation := range failures { - foundMessages = append(foundMessages, foundValidation.UserMessage) - } + elementalDir := filepath.Join(configDir, "elemental") + require.NoError(t, os.MkdirAll(elementalDir, os.ModePerm)) + require.NoError(t, os.Chmod(elementalDir, 0o333)) - for _, expectedMessage := range test.ExpectedFailedMessages { - assert.Contains(t, foundMessages, expectedMessage) - } + failures := validateElementalDir(elementalDir) + assert.Len(t, failures, 1) - }) - } + assert.Contains(t, failures[0].UserMessage, "Elemental config directory could not be read") } From dc86fa52f0bc55a7bd70333594d0da0f634949b0 Mon Sep 17 00:00:00 2001 From: dbw7 Date: Tue, 24 Sep 2024 10:48:08 -0400 Subject: [PATCH 8/8] last changes --- RELEASE_NOTES.md | 2 +- pkg/image/context.go | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a556f59d..cbed41c1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,7 +9,6 @@ * Added the ability to automatically copy files into the built images filesystem (see Image Configuration Directory Changes below) * Kubernetes manifests are now applied in a systemd service instead of using the `/manifests` directory * Helm chart installation backOffLimit changed from 1000(default) to 20 -* Improved Elemental handling when using SL Micro 6.0 * Added Elemental configuration validation * Dropped `-chart` suffix from installed Helm chart names * Added caching for container images @@ -33,6 +32,7 @@ * An optional directory named `os-files` may be included to copy files into the resulting image's filesystem at runtime * The `custom/files` directory may now include subdirectories, which will be maintained when copied to the image +* Elemental configuration now requires a registration code in order to install the necessary RPMs from the official sources ## Bug Fixes diff --git a/pkg/image/context.go b/pkg/image/context.go index 6d8e0c0b..7f5202d1 100644 --- a/pkg/image/context.go +++ b/pkg/image/context.go @@ -35,10 +35,6 @@ type ArtifactSources struct { Repository string `yaml:"repository"` Version string `yaml:"version"` } `yaml:"endpoint-copier-operator"` - Elemental struct { - RegisterRepository string `yaml:"register-repository"` - SystemAgentRepository string `yaml:"system-agent-repository"` - } `yaml:"elemental"` Kubernetes struct { K3s struct { SELinuxPackage string `yaml:"selinuxPackage"`