blueprint: make Convert respect nils

Previously, nil values in the conversion source were in some cases converted
to empty arrays or empty objects. This is undesirable, because it can be in
certain cases changing the semantics of the blueprint. See e.g.
f317064da5/pkg/distro/rhel7/imagetype.go (L239C7-L239C7)

This commit modifies the conversion process so nil values are converted
without any changes. Also, the `Convert` function was covered with a unit
test.
This commit is contained in:
Ondřej Budai 2023-08-10 14:32:44 +02:00 committed by Achilleas Koutsou
parent 336842d7bb
commit 1a6dac1cfa
2 changed files with 360 additions and 15 deletions

View file

@ -185,29 +185,42 @@ func (b *Blueprint) CryptPasswords() error {
}
func Convert(bp Blueprint) iblueprint.Blueprint {
pkgs := make([]iblueprint.Package, len(bp.Packages))
for idx := range bp.Packages {
pkgs[idx] = iblueprint.Package(bp.Packages[idx])
var pkgs []iblueprint.Package
if len(bp.Packages) > 0 {
pkgs = make([]iblueprint.Package, len(bp.Packages))
for idx := range bp.Packages {
pkgs[idx] = iblueprint.Package(bp.Packages[idx])
}
}
modules := make([]iblueprint.Package, len(bp.Modules))
for idx := range bp.Modules {
modules[idx] = iblueprint.Package(bp.Modules[idx])
var modules []iblueprint.Package
if len(bp.Modules) > 0 {
modules = make([]iblueprint.Package, len(bp.Modules))
for idx := range bp.Modules {
modules[idx] = iblueprint.Package(bp.Modules[idx])
}
}
groups := make([]iblueprint.Group, len(bp.Groups))
for idx := range bp.Groups {
groups[idx] = iblueprint.Group(bp.Groups[idx])
var groups []iblueprint.Group
if len(bp.Groups) > 0 {
groups = make([]iblueprint.Group, len(bp.Groups))
for idx := range bp.Groups {
groups[idx] = iblueprint.Group(bp.Groups[idx])
}
}
containers := make([]iblueprint.Container, len(bp.Containers))
for idx := range bp.Containers {
containers[idx] = iblueprint.Container(bp.Containers[idx])
var containers []iblueprint.Container
if len(bp.Containers) > 0 {
containers = make([]iblueprint.Container, len(bp.Containers))
for idx := range bp.Containers {
containers[idx] = iblueprint.Container(bp.Containers[idx])
}
}
customizations := iblueprint.Customizations{}
var customizations *iblueprint.Customizations
if c := bp.Customizations; c != nil {
customizations = iblueprint.Customizations{
customizations = &iblueprint.Customizations{
Hostname: c.Hostname,
InstallationDevice: c.InstallationDevice,
}
@ -331,7 +344,7 @@ func Convert(bp Blueprint) iblueprint.Blueprint {
Modules: modules,
Groups: groups,
Containers: containers,
Customizations: &customizations,
Customizations: customizations,
Distro: bp.Distro,
}

View file

@ -0,0 +1,332 @@
package blueprint
import (
"testing"
iblueprint "github.com/osbuild/images/pkg/blueprint"
"github.com/stretchr/testify/assert"
"github.com/osbuild/osbuild-composer/internal/common"
)
func TestConvert(t *testing.T) {
tests := []struct {
name string
src Blueprint
expected iblueprint.Blueprint
}{
{
name: "empty",
src: Blueprint{},
expected: iblueprint.Blueprint{},
},
{
name: "everything",
src: Blueprint{
Name: "name",
Description: "desc",
Version: "version",
Packages: []Package{
{
Name: "package-name",
Version: "package-version",
},
},
Modules: []Package{
{
Name: "module-name",
Version: "module-version",
},
},
Groups: []Group{
{
Name: "group-name",
},
},
Containers: []Container{
{
Source: "source",
Name: "name",
TLSVerify: common.ToPtr(true),
},
},
Customizations: &Customizations{
Hostname: common.ToPtr("hostname"),
Kernel: &KernelCustomization{
Name: "kernel-name",
Append: "kernel-append",
},
SSHKey: []SSHKeyCustomization{
{
User: "ssh-user",
Key: "ssh-key",
},
},
User: []UserCustomization{
{
Name: "user-name",
Description: common.ToPtr("user-desc"),
Password: common.ToPtr("user-password"),
Key: common.ToPtr("user-key"),
Home: common.ToPtr("/home/user"),
Shell: common.ToPtr("fish"),
Groups: []string{"wheel"},
UID: common.ToPtr(42),
GID: common.ToPtr(2023),
},
},
Group: []GroupCustomization{
{
Name: "group",
GID: common.ToPtr(7),
},
},
Timezone: &TimezoneCustomization{
Timezone: common.ToPtr("timezone"),
NTPServers: []string{"ntp-server"},
},
Locale: &LocaleCustomization{
Languages: []string{"language"},
Keyboard: common.ToPtr("keyboard"),
},
Firewall: &FirewallCustomization{
Ports: []string{"80"},
Services: &FirewallServicesCustomization{
Enabled: []string{"ssh"},
Disabled: []string{"ntp"},
},
Zones: []FirewallZoneCustomization{
{
Name: common.ToPtr("name"),
Sources: []string{"src"},
},
},
},
Services: &ServicesCustomization{
Enabled: []string{"osbuild-composer.service"},
Disabled: []string{"lorax-composer.service"},
},
Filesystem: []FilesystemCustomization{
{
Mountpoint: "/usr",
MinSize: 1024,
},
},
InstallationDevice: "/dev/sda",
FDO: &FDOCustomization{
ManufacturingServerURL: "http://manufacturing.fdo",
DiunPubKeyInsecure: "insecure-pubkey",
DiunPubKeyHash: "hash-pubkey",
DiunPubKeyRootCerts: "root-certs",
},
OpenSCAP: &OpenSCAPCustomization{
DataStream: "stream",
ProfileID: "profile",
Tailoring: &OpenSCAPTailoringCustomizations{
Selected: []string{"cloth"},
Unselected: []string{"leather"},
},
},
Ignition: &IgnitionCustomization{
Embedded: &EmbeddedIgnitionCustomization{
Config: "ignition-config",
},
FirstBoot: &FirstBootIgnitionCustomization{
ProvisioningURL: "http://provisioning.edge",
},
},
Directories: []DirectoryCustomization{
{
Path: "/dir",
User: common.ToPtr("dir-user"),
Group: common.ToPtr("dir-group"),
Mode: "0777",
EnsureParents: true,
},
},
Files: []FileCustomization{
{
Path: "/file",
User: common.ToPtr("file-user`"),
Group: common.ToPtr("file-group"),
Mode: "0755",
Data: "literal easter egg",
},
},
Repositories: []RepositoryCustomization{
{
Id: "repoid",
BaseURLs: []string{"http://baseurl"},
GPGKeys: []string{"repo-gpgkey"},
Metalink: "http://metalink",
Mirrorlist: "http://mirrorlist",
Name: "reponame",
Priority: common.ToPtr(987),
Enabled: common.ToPtr(true),
GPGCheck: common.ToPtr(true),
RepoGPGCheck: common.ToPtr(true),
SSLVerify: common.ToPtr(true),
Filename: "repofile",
},
},
},
Distro: "distro",
},
expected: iblueprint.Blueprint{
Name: "name",
Description: "desc",
Version: "version",
Packages: []iblueprint.Package{
{
Name: "package-name",
Version: "package-version",
},
},
Modules: []iblueprint.Package{
{
Name: "module-name",
Version: "module-version",
},
},
Groups: []iblueprint.Group{
{
Name: "group-name",
},
},
Containers: []iblueprint.Container{
{
Source: "source",
Name: "name",
TLSVerify: common.ToPtr(true),
},
},
Customizations: &iblueprint.Customizations{
Hostname: common.ToPtr("hostname"),
Kernel: &iblueprint.KernelCustomization{
Name: "kernel-name",
Append: "kernel-append",
},
SSHKey: []iblueprint.SSHKeyCustomization{
{
User: "ssh-user",
Key: "ssh-key",
},
},
User: []iblueprint.UserCustomization{
{
Name: "user-name",
Description: common.ToPtr("user-desc"),
Password: common.ToPtr("user-password"),
Key: common.ToPtr("user-key"),
Home: common.ToPtr("/home/user"),
Shell: common.ToPtr("fish"),
Groups: []string{"wheel"},
UID: common.ToPtr(42),
GID: common.ToPtr(2023),
},
},
Group: []iblueprint.GroupCustomization{
{
Name: "group",
GID: common.ToPtr(7),
},
},
Timezone: &iblueprint.TimezoneCustomization{
Timezone: common.ToPtr("timezone"),
NTPServers: []string{"ntp-server"},
},
Locale: &iblueprint.LocaleCustomization{
Languages: []string{"language"},
Keyboard: common.ToPtr("keyboard"),
},
Firewall: &iblueprint.FirewallCustomization{
Ports: []string{"80"},
Services: &iblueprint.FirewallServicesCustomization{
Enabled: []string{"ssh"},
Disabled: []string{"ntp"},
},
Zones: []iblueprint.FirewallZoneCustomization{
{
Name: common.ToPtr("name"),
Sources: []string{"src"},
},
},
},
Services: &iblueprint.ServicesCustomization{
Enabled: []string{"osbuild-composer.service"},
Disabled: []string{"lorax-composer.service"},
},
Filesystem: []iblueprint.FilesystemCustomization{
{
Mountpoint: "/usr",
MinSize: 1024,
},
},
InstallationDevice: "/dev/sda",
FDO: &iblueprint.FDOCustomization{
ManufacturingServerURL: "http://manufacturing.fdo",
DiunPubKeyInsecure: "insecure-pubkey",
DiunPubKeyHash: "hash-pubkey",
DiunPubKeyRootCerts: "root-certs",
},
OpenSCAP: &iblueprint.OpenSCAPCustomization{
DataStream: "stream",
ProfileID: "profile",
Tailoring: &iblueprint.OpenSCAPTailoringCustomizations{
Selected: []string{"cloth"},
Unselected: []string{"leather"},
},
},
Ignition: &iblueprint.IgnitionCustomization{
Embedded: &iblueprint.EmbeddedIgnitionCustomization{
Config: "ignition-config",
},
FirstBoot: &iblueprint.FirstBootIgnitionCustomization{
ProvisioningURL: "http://provisioning.edge",
},
},
Directories: []iblueprint.DirectoryCustomization{
{
Path: "/dir",
User: common.ToPtr("dir-user"),
Group: common.ToPtr("dir-group"),
Mode: "0777",
EnsureParents: true,
},
},
Files: []iblueprint.FileCustomization{
{
Path: "/file",
User: common.ToPtr("file-user`"),
Group: common.ToPtr("file-group"),
Mode: "0755",
Data: "literal easter egg",
},
},
Repositories: []iblueprint.RepositoryCustomization{
{
Id: "repoid",
BaseURLs: []string{"http://baseurl"},
GPGKeys: []string{"repo-gpgkey"},
Metalink: "http://metalink",
Mirrorlist: "http://mirrorlist",
Name: "reponame",
Priority: common.ToPtr(987),
Enabled: common.ToPtr(true),
GPGCheck: common.ToPtr(true),
RepoGPGCheck: common.ToPtr(true),
SSLVerify: common.ToPtr(true),
Filename: "repofile",
},
},
},
Distro: "distro",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, Convert(tt.src))
})
}
}