Skip to content

Commit

Permalink
Merge pull request #2960 from mkenigs/translate-184
Browse files Browse the repository at this point in the history
Create machine_config_to_ignition
  • Loading branch information
openshift-merge-robot authored Apr 1, 2022
2 parents 9b886c7 + 006d3a9 commit 10957fc
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 31 deletions.
23 changes: 23 additions & 0 deletions pkg/controller/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -795,3 +795,26 @@ func IsLayeredPool(pool *mcfgv1.MachineConfigPool) bool {
}
return false
}

// Returns list of extensions possible to install on a CoreOS based system.
func GetSupportedExtensions() map[string][]string {
// In future when list of extensions grow, it will make
// more sense to populate it in a dynamic way.

// These are RHCOS supported extensions.
// Each extension keeps a list of packages required to get enabled on host.
return map[string][]string{
"usbguard": {"usbguard"},
"kerberos": {"krb5-workstation", "libkadm5"},
"kernel-devel": {"kernel-devel", "kernel-headers"},
"sandboxed-containers": {"kata-containers"},
}
}

// canonicalizeKernelType returns a valid kernelType. We consider empty("") and default kernelType as same
func CanonicalizeKernelType(kernelType string) string {
if kernelType == KernelTypeRealtime {
return KernelTypeRealtime
}
return KernelTypeDefault
}
112 changes: 112 additions & 0 deletions pkg/controller/render/machine_config_to_ignition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package render

import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"

baseutil "github.com/coreos/butane/base/util"
"github.com/coreos/ignition/v2/config/util"
ign3types "github.com/coreos/ignition/v2/config/v3_2/types"
mcSpecv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
"github.com/openshift/machine-config-operator/test/helpers"

"gopkg.in/yaml.v3"
)

const MCDContentPath = "/etc/machine-config-daemon/mcd_content.json"

type MCDContent struct {
KernelArguments []string `json:"kernelArguments"`
FIPS bool `json:"fips"`
}

func MachineConfigToIgnition(mcSpec *mcSpecv1.MachineConfigSpec) (ign3types.Config, error) {
// Config
ignition, err := ctrlcommon.ParseAndConvertConfig(mcSpec.Config.Raw)
if err != nil {
return ign3types.Config{}, fmt.Errorf("parsing Ignition config failed: %w", err)
}

var treeFile TreeFile
// Extensions
supportedExtensions := ctrlcommon.GetSupportedExtensions()
for _, mcSpecExtension := range mcSpec.Extensions {
treeFile.Packages = append(treeFile.Packages, supportedExtensions[mcSpecExtension]...)
}

// KernelType
switch kernelType := ctrlcommon.CanonicalizeKernelType(mcSpec.KernelType); kernelType {
case ctrlcommon.KernelTypeDefault:
// no-op
case ctrlcommon.KernelTypeRealtime:
treeFile.Packages = append(treeFile.Packages, "kernel-rt", "kernel-rt-modules", "kernel-rt-modules-extra")
treeFile.OverrideRemove = append(treeFile.OverrideRemove, "kernel", "kernel-modules", "kernel-modules-extra")
default:
return ign3types.Config{}, fmt.Errorf("unsupported kernel type %s", kernelType)
}

// add content from Extensions and KernelType to ignition
if !treeFile.isEmpty() {
ignitionOriginFile, err := treeFile.toIgnFile(false)
if err != nil {
return ign3types.Config{}, fmt.Errorf("could not convert OriginFile to Ignition file: %w", err)
}
ignition.Storage.Files = append(ignition.Storage.Files, ignitionOriginFile)
}

// KernelArguments and FIPS
mcdContent, err := json.Marshal(&MCDContent{
KernelArguments: mcSpec.KernelArguments,
FIPS: mcSpec.FIPS,
})
if err != nil {
return ign3types.Config{}, fmt.Errorf("couldn't marshal MCD content: %w", err)
}
ignition.Storage.Files = append(ignition.Storage.Files, helpers.NewIgnFile(MCDContentPath, string(mcdContent)))

return ignition, nil
}

type TreeFile struct {
Packages []string `yaml:"packages,omitempty"`
OverrideRemove []string `yaml:"override-remove,omitempty"`
}

func (treeFile TreeFile) isEmpty() bool {
return len(treeFile.Packages) == 0 && len(treeFile.OverrideRemove) == 0
}

func (treeFile TreeFile) toIgnFile(allowCompression bool) (ign3types.File, error) {
treeFileContents, err := yaml.Marshal(treeFile)
if err != nil {
return ign3types.File{}, fmt.Errorf("failed to marshal extensions as yaml: %w", err)
}
fullYamlContents := append([]byte("# Generated by the MCO\n\n"), treeFileContents...)
src, gzipped, err := baseutil.MakeDataURL(fullYamlContents, nil, allowCompression)
if err != nil {
return ign3types.File{}, fmt.Errorf("could not encode extensions file: %w", err)
}
hash := sha256.New()
hash.Write([]byte(src))
sha := hex.EncodeToString(hash.Sum(nil))[0:7]
mode := 0644
file := ign3types.File{
Node: ign3types.Node{
Path: "/etc/rpm-ostree/origin.d/extensions-" + sha + ".yaml",
},
FileEmbedded1: ign3types.FileEmbedded1{
Contents: ign3types.Resource{
Source: util.StrToPtr(src),
},
Mode: &mode,
},
}
if gzipped {
file.Contents.Compression = util.StrToPtr("gzip")
}

return file, nil
}
179 changes: 179 additions & 0 deletions pkg/controller/render/machine_config_to_ignition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package render

import (
"encoding/json"
"testing"

baseutil "github.com/coreos/butane/base/util"
"github.com/coreos/ignition/v2/config/util"
ign3types "github.com/coreos/ignition/v2/config/v3_2/types"
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
"github.com/openshift/machine-config-operator/test/helpers"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
)

type machineConfigToIgnitionTestCase struct {
name string
spec mcfgv1.MachineConfigSpec
expectedIgnition ign3types.Config
}

// create tree file from bytes rather than marshaling to allow inlining readable test cases
func createTreeFile(t *testing.T, path string, content []byte) ign3types.File {
src, _, err := baseutil.MakeDataURL(content, nil, false)
require.Nil(t, err)
return ign3types.File{
Node: ign3types.Node{
Path: path,
},
FileEmbedded1: ign3types.FileEmbedded1{
Contents: ign3types.Resource{
Source: util.StrToPtr(src),
},
Mode: util.IntToPtr(0644),
},
}
}

func TestMachineConfigToIgnition(t *testing.T) {
// common inputs
defaultInputIgnition := ign3types.Config{
Ignition: ign3types.Ignition{
Version: "3.2.0",
},
}
rawDefaultInputIgnition, err := json.Marshal(defaultInputIgnition)
require.Nil(t, err)
defaultInputConfig := runtime.RawExtension{
Raw: rawDefaultInputIgnition,
}

// expected content
defaultMCDContent, err := json.Marshal(MCDContent{})
require.Nil(t, err)
defaultMCDContentFile := helpers.NewIgnFile(MCDContentPath, string(defaultMCDContent))
defaultExpectedIgnition := defaultInputIgnition
defaultExpectedIgnition.Storage.Files = append(defaultExpectedIgnition.Storage.Files, defaultMCDContentFile)

testCases := []machineConfigToIgnitionTestCase{
// test OSImageURL is simple enough to be added to testCases directly
{
name: "test OSImageURL",
spec: mcfgv1.MachineConfigSpec{
Config: defaultInputConfig,
OSImageURL: "URL that should be ignored",
},
expectedIgnition: defaultExpectedIgnition,
},
}

// test Config
inputIgnition := defaultInputIgnition
tempUser := ign3types.PasswdUser{Name: "core", SSHAuthorizedKeys: []ign3types.SSHAuthorizedKey{"5678", "abc"}}
inputIgnition.Passwd.Users = []ign3types.PasswdUser{tempUser}
rawInputIgnition, err := json.Marshal(inputIgnition)
require.Nil(t, err)

expectedIgnition, err := ctrlcommon.ParseAndConvertConfig(rawInputIgnition)
expectedIgnition.Storage.Files = append(expectedIgnition.Storage.Files, defaultMCDContentFile)
require.Nil(t, err)

testCases = append(testCases, machineConfigToIgnitionTestCase{
name: "test Config",
spec: mcfgv1.MachineConfigSpec{
Config: runtime.RawExtension{
Raw: rawInputIgnition,
},
},
expectedIgnition: expectedIgnition,
})

// test Extensions
treeFile := createTreeFile(t, "/etc/rpm-ostree/origin.d/extensions-fdb77c1.yaml", []byte(`# Generated by the MCO
packages:
- kernel-devel
- kernel-headers
`))
expectedIgnition = defaultExpectedIgnition
expectedIgnition.Storage.Files = append([]ign3types.File{treeFile}, expectedIgnition.Storage.Files...)
testCases = append(testCases, machineConfigToIgnitionTestCase{
name: "test Extensions",
spec: mcfgv1.MachineConfigSpec{
Config: defaultInputConfig,
Extensions: []string{"kernel-devel"},
},
expectedIgnition: expectedIgnition,
})

// test KernelType
treeFile = createTreeFile(t, "/etc/rpm-ostree/origin.d/extensions-cd51cd4.yaml", []byte(`# Generated by the MCO
packages:
- kernel-rt
- kernel-rt-modules
- kernel-rt-modules-extra
override-remove:
- kernel
- kernel-modules
- kernel-modules-extra
`))
expectedIgnition = defaultExpectedIgnition
expectedIgnition.Storage.Files = append([]ign3types.File{treeFile}, expectedIgnition.Storage.Files...)
testCases = append(testCases, machineConfigToIgnitionTestCase{
name: "test KernelType",
spec: mcfgv1.MachineConfigSpec{
Config: defaultInputConfig,
KernelType: ctrlcommon.KernelTypeRealtime,
},
expectedIgnition: expectedIgnition,
})

// test KernelArguments
mcdContentFile := helpers.NewIgnFile(MCDContentPath, `{"kernelArguments":["hugepagesz=1G","hugepages=4"],"fips":false}`)
testCases = append(testCases, machineConfigToIgnitionTestCase{
name: "test KernelArguments",
spec: mcfgv1.MachineConfigSpec{
Config: defaultInputConfig,
KernelArguments: []string{"hugepagesz=1G", "hugepages=4"},
},
expectedIgnition: ign3types.Config{
Ignition: defaultExpectedIgnition.Ignition,
Storage: ign3types.Storage{
Files: []ign3types.File{
mcdContentFile,
},
},
},
})

// test FIPS
mcdContentFile = helpers.NewIgnFile(MCDContentPath, `{"kernelArguments":null,"fips":true}`)
testCases = append(testCases, machineConfigToIgnitionTestCase{
name: "test FIPS",
spec: mcfgv1.MachineConfigSpec{
Config: defaultInputConfig,
FIPS: true,
},
expectedIgnition: ign3types.Config{
Ignition: defaultExpectedIgnition.Ignition,
Storage: ign3types.Storage{
Files: []ign3types.File{
mcdContentFile,
},
},
},
})

for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
ignition, err := MachineConfigToIgnition(&testCase.spec)
require.Nil(t, err)
require.Equal(t, testCase.expectedIgnition, ignition)
})
}
}
Loading

0 comments on commit 10957fc

Please sign in to comment.