Skip to content

Commit 0ce0cbc

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 0ce0cbc

File tree

5 files changed

+94
-17
lines changed

5 files changed

+94
-17
lines changed

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

+18-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,24 @@ 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+
# Install any extensions specified
22+
{{if and .ExtensionsImage .Extensions}}
23+
# Mount the extensions image to use the content from it
24+
# and add the extensions repo to /etc/yum.repos.d/coreos-extensions.repo
25+
RUN --mount=type=bind,from={{.ExtensionsImage}},source=/,target=/tmp/mco-extensions/os-extensions-content,bind-propagation=rshared,rw,z \
26+
echo -e "[coreos-extensions]\n\
27+
enabled=1\n\
28+
metadata_expire=1m\n\
29+
baseurl=/tmp/mco-extensions/os-extensions-content/usr/share/rpm-ostree/extensions/\n\
30+
gpgcheck=0\n\
31+
skip_if_unavailable=False" > /etc/yum.repos.d/coreos-extensions.repo && \
32+
chmod 644 /etc/yum.repos.d/coreos-extensions.repo && \
33+
extensions="{{- range $index, $item := .Extensions }}{{- if $index }} {{ end }}{{$item}}{{- end }}" && \
34+
echo "Installing packages: $extensions" && \
35+
rpm-ostree install $extensions && \
36+
rm /etc/yum.repos.d/coreos-extensions.repo
37+
RUN ostree container commit
38+
{{end}}
3139

3240
LABEL machineconfig={{.MachineOSBuild.Spec.DesiredConfig.Name}}
3341
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

+15-6
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,28 @@ 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("RUN --mount=type=bind,from=%s", osImageURLConfig.BaseOSExtensionsContainerImage),
51+
"extensions=\"usbguard\"",
4752
}...),
4853
},
4954
{
50-
name: "Missing extensions image",
55+
name: "Missing extensions image and extensions",
5156
optsFunc: func() BuildRequestOpts {
5257
opts := getBuildRequestOpts()
5358
opts.OSImageURLConfig.BaseOSExtensionsContainerImage = ""
59+
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
5460
return opts
5561
},
5662
unexpectedContainerfileContents: []string{
57-
fmt.Sprintf("FROM %s AS extensions", osImageURLConfig.BaseOSContainerImage),
63+
fmt.Sprintf("RUN --mount=type=bind,from=%s", osImageURLConfig.BaseOSContainerImage),
64+
"extensions=\"usbguard\"",
5865
},
5966
},
6067
{
@@ -98,12 +105,14 @@ func TestBuildRequest(t *testing.T) {
98105
opts.MachineOSConfig.Spec.BuildInputs.BaseOSImagePullspec = "base-os-image-from-machineosconfig"
99106
opts.MachineOSConfig.Spec.BuildInputs.BaseOSExtensionsImagePullspec = "base-ext-image-from-machineosconfig"
100107
opts.MachineOSConfig.Spec.BuildInputs.ReleaseVersion = "release-version-from-machineosconfig"
108+
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
101109
return opts
102110
},
103111
expectedContainerfileContents: []string{
104112
"FROM base-os-image-from-machineosconfig AS extract",
105113
"FROM base-os-image-from-machineosconfig AS configs",
106-
"FROM base-ext-image-from-machineosconfig AS extensions",
114+
"RUN --mount=type=bind,from=base-ext-image-from-machineosconfig",
115+
"extensions=\"usbguard\"",
107116
"LABEL releaseversion=release-version-from-machineosconfig",
108117
},
109118
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

+51-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ type onClusterLayeringTestOpts struct {
8888

8989
// Inject YUM repo information from a Centos 9 stream container
9090
useYumRepos bool
91+
92+
// Add Extensions for testing
93+
useExtensions bool
9194
}
9295

9396
func TestOnClusterBuildsOnOKD(t *testing.T) {
@@ -113,12 +116,13 @@ func TestOnClusterBuildsCustomPodBuilder(t *testing.T) {
113116

114117
// Tests that an on-cluster build can be performed and that the resulting image
115118
// is rolled out to an opted-in node.
116-
func TestOnClusterBuildRollsOutImage(t *testing.T) {
119+
func TestOnClusterBuildRollsOutImageWithExtensionsInstalled(t *testing.T) {
117120
imagePullspec := runOnClusterLayeringTest(t, onClusterLayeringTestOpts{
118121
poolName: layeredMCPName,
119122
customDockerfiles: map[string]string{
120123
layeredMCPName: cowsayDockerfile,
121124
},
125+
useExtensions: true,
122126
})
123127

124128
cs := framework.NewClientSet("")
@@ -129,12 +133,14 @@ func TestOnClusterBuildRollsOutImage(t *testing.T) {
129133

130134
helpers.AssertNodeBootedIntoImage(t, cs, node, imagePullspec)
131135
t.Logf("Node %s is booted into image %q", node.Name, imagePullspec)
136+
assertExtensionInstalledOnNode(t, cs, node, true)
132137

133138
t.Log(helpers.ExecCmdOnNode(t, cs, node, "chroot", "/rootfs", "cowsay", "Moo!"))
134139

135140
unlabelFunc()
136141

137142
assertNodeRevertsToNonLayered(t, cs, node)
143+
assertExtensionInstalledOnNode(t, cs, node, false)
138144
}
139145

140146
func assertNodeRevertsToNonLayered(t *testing.T, cs *framework.ClientSet, node corev1.Node) {
@@ -151,6 +157,22 @@ func assertNodeRevertsToNonLayered(t *testing.T, cs *framework.ClientSet, node c
151157
helpers.AssertFileNotOnNode(t, cs, node, runtimeassets.RevertServiceMachineConfigFile)
152158
}
153159

160+
func assertExtensionInstalledOnNode(t *testing.T, cs *framework.ClientSet, node corev1.Node, shouldExist bool) {
161+
foundPkg, err := helpers.ExecCmdOnNodeWithError(cs, node, "chroot", "/rootfs", "rpm", "-q", "usbguard")
162+
if shouldExist {
163+
require.NoError(t, err, "usbguard extension not found")
164+
if strings.Contains(foundPkg, "package usbguard is not installed") {
165+
t.Fatalf("usbguard package not installed on node %s, got %s", node.Name, foundPkg)
166+
}
167+
t.Logf("usbguard extension installed, got %s", foundPkg)
168+
} else {
169+
if !strings.Contains(foundPkg, "package usbguard is not installed") {
170+
t.Fatalf("usbguard package is installed on node %s, got %s", node.Name, foundPkg)
171+
}
172+
t.Logf("usbguard extension not installed as expected, got %s", foundPkg)
173+
}
174+
}
175+
154176
// This test extracts the /etc/yum.repos.d and /etc/pki/rpm-gpg content from a
155177
// Centos Stream 9 image and injects them into the MCO namespace. It then
156178
// performs a build with the expectation that these artifacts will be used,
@@ -923,6 +945,34 @@ func prepareForOnClusterLayeringTest(t *testing.T, cs *framework.ClientSet, test
923945
t.Cleanup(makeIdempotentAndRegister(t, helpers.CreateMCP(t, cs, testOpts.poolName)))
924946
}
925947

948+
if testOpts.useExtensions {
949+
extensionsMC := &mcfgv1.MachineConfig{
950+
ObjectMeta: metav1.ObjectMeta{
951+
Name: "99-extensions",
952+
Labels: helpers.MCLabelForRole(testOpts.poolName),
953+
},
954+
Spec: mcfgv1.MachineConfigSpec{
955+
Config: runtime.RawExtension{
956+
Raw: helpers.MarshalOrDie(ctrlcommon.NewIgnConfig()),
957+
},
958+
Extensions: []string{"usbguard"},
959+
},
960+
}
961+
962+
helpers.SetMetadataOnObject(t, extensionsMC)
963+
// Apply the extensions MC
964+
mcCleanupFunc := helpers.ApplyMC(t, cs, extensionsMC)
965+
t.Cleanup(func() {
966+
mcCleanupFunc()
967+
t.Logf("Deleted MachineConfig %s", extensionsMC.Name)
968+
})
969+
t.Logf("Created new MachineConfig %q", extensionsMC.Name)
970+
// Wait for rendered config to finish creating
971+
renderedConfig, err := helpers.WaitForRenderedConfig(t, cs, testOpts.poolName, extensionsMC.Name)
972+
require.NoError(t, err)
973+
t.Logf("Finished rendering config %s", renderedConfig)
974+
}
975+
926976
_, err := helpers.WaitForRenderedConfig(t, cs, testOpts.poolName, "00-worker")
927977
require.NoError(t, err)
928978

0 commit comments

Comments
 (0)