Skip to content

Commit 58a3428

Browse files
committed
Install extensions via Containerfile for OCL
Add logic to the Containerfile used to build the new OS image to be able to install extensions when using OCL. Extensions are installed via rpm-ostree and commited to the container image. Signed-off-by: Urvashi <umohnani@redhat.com>
1 parent 478aa75 commit 58a3428

File tree

5 files changed

+114
-16
lines changed

5 files changed

+114
-16
lines changed

pkg/controller/build/buildrequest/assets/Containerfile.on-cluster-build-template

+19-10
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,6 @@ COPY ./machineconfig/machineconfig.json.gz /tmp/machineconfig.json.gz
1010
RUN mkdir -p /etc/machine-config-daemon && \
1111
cat /tmp/machineconfig.json.gz | base64 -d | gunzip - > /etc/machine-config-daemon/currentconfig
1212

13-
{{if .ExtensionsImage}}
14-
# Pull our extensions image. Not sure yet what / how this should be wired up
15-
# though. Ideally, I'd like to use some Buildah tricks to have the extensions
16-
# directory mounted into the container at build-time so that I don't have to
17-
# copy the RPMs into the container, configure the repo, and do the
18-
# installation. Alternatively, I'd have to start a pod with an HTTP server.
19-
FROM {{.ExtensionsImage}} AS extensions
20-
{{end}}
21-
22-
2313
FROM {{.BaseOSImage}} AS configs
2414
# Copy the extracted MachineConfig into the expected place in the image.
2515
COPY --from=extract /etc/machine-config-daemon/currentconfig /etc/machine-config-daemon/currentconfig
@@ -28,6 +18,25 @@ COPY --from=extract /etc/machine-config-daemon/currentconfig /etc/machine-config
2818
# since it should be set by the container runtime / builder.
2919
RUN container="oci" exec -a ignition-apply /usr/lib/dracut/modules.d/30ignition/ignition --ignore-unsupported <(cat /etc/machine-config-daemon/currentconfig | jq '.spec.config') && \
3020
ostree container commit
21+
{{if and .ExtensionsImage .Extensions}}
22+
COPY --from={{.ExtensionsImage}} / /run/mco-extensions/os-extensions-content
23+
# Add the extensions repo to /etc/yum.repos.d/coreos-extensions.repo
24+
RUN cat <<EOF > /etc/yum.repos.d/coreos-extensions.repo
25+
[coreos-extensions]
26+
enabled=1
27+
metadata_expire=1m
28+
baseurl=/run/mco-extensions/os-extensions-content/usr/share/rpm-ostree/extensions/
29+
gpgcheck=0
30+
skip_if_unavailable=False
31+
EOF
32+
# Set file permissions
33+
RUN chmod 644 /etc/yum.repos.d/coreos-extensions.repo
34+
# Install the extensions via rpm-ostree
35+
RUN extensions="{{- range $index, $item := .Extensions }}{{- if $index }} {{ end }}{{$item}}{{- end }}" && \
36+
echo "Installing packages: $extensions" && \
37+
rpm-ostree install $extensions && \
38+
ostree container commit
39+
{{end}}
3140

3241
LABEL machineconfig={{.MachineOSBuild.Spec.DesiredConfig.Name}}
3342
LABEL machineconfigpool={{.MachineOSConfig.Spec.MachineConfigPool.Name}}

pkg/controller/build/buildrequest/buildrequest.go

+2
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,15 @@ func (br buildRequestImpl) renderContainerfile() (string, error) {
211211
ReleaseVersion string
212212
BaseOSImage string
213213
ExtensionsImage string
214+
Extensions []string
214215
}{
215216
MachineOSBuild: br.opts.MachineOSBuild,
216217
MachineOSConfig: br.opts.MachineOSConfig,
217218
UserContainerfile: br.userContainerfile,
218219
ReleaseVersion: br.opts.getReleaseVersion(),
219220
BaseOSImage: br.opts.getBaseOSImagePullspec(),
220221
ExtensionsImage: br.opts.getExtensionsImagePullspec(),
222+
Extensions: br.opts.getExtensions(),
221223
}
222224

223225
if err := tmpl.Execute(out, items); err != nil {

pkg/controller/build/buildrequest/buildrequest_test.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,26 @@ func TestBuildRequest(t *testing.T) {
4040
unexpectedContainerfileContents []string
4141
}{
4242
{
43-
name: "With extensions image",
44-
optsFunc: getBuildRequestOpts,
43+
name: "With extensions image and extensions",
44+
optsFunc: func() BuildRequestOpts {
45+
opts := getBuildRequestOpts()
46+
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
47+
return opts
48+
},
4549
expectedContainerfileContents: append(expectedContents(), []string{
46-
fmt.Sprintf("FROM %s AS extensions", osImageURLConfig.BaseOSExtensionsContainerImage),
50+
fmt.Sprintf("COPY --from=%s", osImageURLConfig.BaseOSExtensionsContainerImage),
4751
}...),
4852
},
4953
{
50-
name: "Missing extensions image",
54+
name: "Missing extensions image and extensions",
5155
optsFunc: func() BuildRequestOpts {
5256
opts := getBuildRequestOpts()
5357
opts.OSImageURLConfig.BaseOSExtensionsContainerImage = ""
58+
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
5459
return opts
5560
},
5661
unexpectedContainerfileContents: []string{
57-
fmt.Sprintf("FROM %s AS extensions", osImageURLConfig.BaseOSContainerImage),
62+
fmt.Sprintf("COPY --from=%s", osImageURLConfig.BaseOSContainerImage),
5863
},
5964
},
6065
{
@@ -98,12 +103,13 @@ func TestBuildRequest(t *testing.T) {
98103
opts.MachineOSConfig.Spec.BuildInputs.BaseOSImagePullspec = "base-os-image-from-machineosconfig"
99104
opts.MachineOSConfig.Spec.BuildInputs.BaseOSExtensionsImagePullspec = "base-ext-image-from-machineosconfig"
100105
opts.MachineOSConfig.Spec.BuildInputs.ReleaseVersion = "release-version-from-machineosconfig"
106+
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
101107
return opts
102108
},
103109
expectedContainerfileContents: []string{
104110
"FROM base-os-image-from-machineosconfig AS extract",
105111
"FROM base-os-image-from-machineosconfig AS configs",
106-
"FROM base-ext-image-from-machineosconfig AS extensions",
112+
"COPY --from=base-ext-image-from-machineosconfig",
107113
"LABEL releaseversion=release-version-from-machineosconfig",
108114
},
109115
unexpectedContainerfileContents: expectedContents(),

pkg/controller/build/buildrequest/buildrequestopts.go

+8
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ func (b BuildRequestOpts) getReleaseVersion() string {
6868
return b.OSImageURLConfig.ReleaseVersion
6969
}
7070

71+
// Gets the extensions from the MachineConfig if available.
72+
func (b BuildRequestOpts) getExtensions() []string {
73+
if len(b.MachineConfig.Spec.Extensions) > 0 {
74+
return b.MachineConfig.Spec.Extensions
75+
}
76+
return []string{}
77+
}
78+
7179
// Gets all of the image build request opts from the Kube API server.
7280
func newBuildRequestOptsFromAPI(ctx context.Context, kubeclient clientset.Interface, mcfgclient mcfgclientset.Interface, mosb *mcfgv1alpha1.MachineOSBuild, mosc *mcfgv1alpha1.MachineOSConfig) (*BuildRequestOpts, error) {
7381
og := optsGetter{

test/e2e-techpreview/onclusterlayering_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -1053,3 +1053,76 @@ func TestSSHKeyAndPasswordForOSBuilder(t *testing.T) {
10531053
t.Logf("Deleted MachineConfig %s", testConfig.Name)
10541054
})
10551055
}
1056+
1057+
func TestExtensionsForOSBuilder(t *testing.T) {
1058+
t.Helper()
1059+
1060+
cs := framework.NewClientSet("")
1061+
1062+
// label random node from pool, get the node
1063+
unlabelFunc := helpers.LabelRandomNodeFromPool(t, cs, "worker", "node-role.kubernetes.io/layered")
1064+
1065+
// prepare for on cluster build test
1066+
prepareForOnClusterLayeringTest(t, cs, onClusterLayeringTestOpts{
1067+
poolName: layeredMCPName,
1068+
customDockerfiles: map[string]string{},
1069+
useEtcPkiEntitlement: true,
1070+
})
1071+
1072+
testConfig := &mcfgv1.MachineConfig{
1073+
ObjectMeta: metav1.ObjectMeta{
1074+
Name: "99-extensions",
1075+
Labels: helpers.MCLabelForRole(layeredMCPName),
1076+
},
1077+
Spec: mcfgv1.MachineConfigSpec{
1078+
Config: runtime.RawExtension{
1079+
Raw: helpers.MarshalOrDie(ctrlcommon.NewIgnConfig()),
1080+
},
1081+
Extensions: []string{"usbguard", "kerberos"},
1082+
},
1083+
}
1084+
1085+
helpers.SetMetadataOnObject(t, testConfig)
1086+
1087+
// Create the MachineConfig and wait for the configuration to be applied
1088+
_, err := cs.MachineConfigs().Create(context.TODO(), testConfig, metav1.CreateOptions{})
1089+
require.Nil(t, err, "failed to create MC")
1090+
t.Logf("Created MC %s", testConfig.Name)
1091+
1092+
// wait for rendered config to finish creating
1093+
renderedConfig, err := helpers.WaitForRenderedConfig(t, cs, layeredMCPName, testConfig.Name)
1094+
require.Nil(t, err)
1095+
t.Logf("Finished rendering config %s", renderedConfig)
1096+
1097+
// wait for mcp to complete updating
1098+
err = helpers.WaitForPoolComplete(t, cs, layeredMCPName, renderedConfig)
1099+
require.Nil(t, err)
1100+
t.Logf("Pool %s completed updating", layeredMCPName)
1101+
1102+
// Validate the extensions are installed
1103+
osNode := helpers.GetSingleNodeByRole(t, cs, layeredMCPName) // Re-fetch node with updated configurations
1104+
1105+
foundPkg := helpers.ExecCmdOnNode(t, cs, osNode, "rpm", "-q", "usbguard")
1106+
if strings.Contains(foundPkg, "package usbguard is not installed") {
1107+
t.Fatalf("usbguard extensions not found, got %s", foundPkg)
1108+
}
1109+
t.Logf("usbguard extension installed, got %s", foundPkg)
1110+
1111+
foundPkg = helpers.ExecCmdOnNode(t, cs, osNode, "rpm", "-q", "kerberos")
1112+
if strings.Contains(foundPkg, "package kerberos is not installed") {
1113+
t.Fatalf("kerberos extensions not found, got %s", foundPkg)
1114+
}
1115+
t.Logf("kerberos extension installed, got %s", foundPkg)
1116+
1117+
t.Logf("Node %s has both usbguard and kerberos extensions installed", osNode.Name)
1118+
1119+
// Clean-up: Delete the applied MachineConfig and ensure configurations are rolled back
1120+
1121+
t.Cleanup(func() {
1122+
unlabelFunc()
1123+
if err := cs.MachineConfigs().Delete(context.TODO(), testConfig.Name, metav1.DeleteOptions{}); err != nil {
1124+
t.Error(err)
1125+
}
1126+
t.Logf("Deleted MachineConfig %s", testConfig.Name)
1127+
})
1128+
}

0 commit comments

Comments
 (0)