-
Notifications
You must be signed in to change notification settings - Fork 413
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2960 from mkenigs/translate-184
Create machine_config_to_ignition
- Loading branch information
Showing
4 changed files
with
322 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
179
pkg/controller/render/machine_config_to_ignition_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
Oops, something went wrong.