Skip to content

Commit 2581c8f

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 2581c8f

File tree

5 files changed

+103
-17
lines changed

5 files changed

+103
-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("COPY --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("COPY --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+
"COPY --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

+60-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,14 @@ 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+
useEtcPkiEntitlement: true,
126+
useExtensions: true,
122127
})
123128

124129
cs := framework.NewClientSet("")
@@ -129,12 +134,14 @@ func TestOnClusterBuildRollsOutImage(t *testing.T) {
129134

130135
helpers.AssertNodeBootedIntoImage(t, cs, node, imagePullspec)
131136
t.Logf("Node %s is booted into image %q", node.Name, imagePullspec)
137+
assertExtensionInstalledOnNode(t, cs, node)
132138

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

135141
unlabelFunc()
136142

137143
assertNodeRevertsToNonLayered(t, cs, node)
144+
assertExtensionNotOnNode(t, cs, node)
138145
}
139146

140147
func assertNodeRevertsToNonLayered(t *testing.T, cs *framework.ClientSet, node corev1.Node) {
@@ -151,6 +158,30 @@ func assertNodeRevertsToNonLayered(t *testing.T, cs *framework.ClientSet, node c
151158
helpers.AssertFileNotOnNode(t, cs, node, runtimeassets.RevertServiceMachineConfigFile)
152159
}
153160

161+
func assertExtensionInstalledOnNode(t *testing.T, cs *framework.ClientSet, node corev1.Node) {
162+
foundPkg, err := helpers.ExecCmdOnNodeWithError(cs, node, "rpm", "-q", "usbguard")
163+
require.NoError(t, err, "usbguard extension not found")
164+
t.Logf("usbguard extension installed, got %s", foundPkg)
165+
166+
foundPkg, err = helpers.ExecCmdOnNodeWithError(cs, node, "rpm", "-q", "kerberos")
167+
require.NoError(t, err, "kerberos extension not found")
168+
t.Logf("kerberos extension installed, got %s", foundPkg)
169+
170+
t.Logf("Node %s has both usbguard and kerberos extensions installed", node.Name)
171+
}
172+
173+
func assertExtensionNotOnNode(t *testing.T, cs *framework.ClientSet, node corev1.Node) {
174+
foundPkg, err := helpers.ExecCmdOnNodeWithError(cs, node, "rpm", "-q", "usbguard")
175+
require.Error(t, err, "usbguard extension is on node")
176+
t.Logf("usbguard extension not installed as expected, got %s", foundPkg)
177+
178+
foundPkg, err = helpers.ExecCmdOnNodeWithError(cs, node, "rpm", "-q", "kerberos")
179+
require.Error(t, err, "kerberos extension is on node")
180+
t.Logf("kerberos extension not installed as expected, got %s", foundPkg)
181+
182+
t.Logf("Node %s does not have usbguard and kerberos extensions installed as expected", node.Name)
183+
}
184+
154185
// This test extracts the /etc/yum.repos.d and /etc/pki/rpm-gpg content from a
155186
// Centos Stream 9 image and injects them into the MCO namespace. It then
156187
// performs a build with the expectation that these artifacts will be used,
@@ -923,6 +954,34 @@ func prepareForOnClusterLayeringTest(t *testing.T, cs *framework.ClientSet, test
923954
t.Cleanup(makeIdempotentAndRegister(t, helpers.CreateMCP(t, cs, testOpts.poolName)))
924955
}
925956

957+
if testOpts.useExtensions {
958+
extensionsMC := &mcfgv1.MachineConfig{
959+
ObjectMeta: metav1.ObjectMeta{
960+
Name: "99-extensions",
961+
Labels: helpers.MCLabelForRole(testOpts.poolName),
962+
},
963+
Spec: mcfgv1.MachineConfigSpec{
964+
Config: runtime.RawExtension{
965+
Raw: helpers.MarshalOrDie(ctrlcommon.NewIgnConfig()),
966+
},
967+
Extensions: []string{"usbguard", "kerberos"},
968+
},
969+
}
970+
971+
helpers.SetMetadataOnObject(t, extensionsMC)
972+
// Apply the extensions MC
973+
mcCleanupFunc := helpers.ApplyMC(t, cs, extensionsMC)
974+
t.Cleanup(func() {
975+
mcCleanupFunc()
976+
t.Logf("Deleted MachineConfig %s", extensionsMC.Name)
977+
})
978+
t.Logf("Created new MachineConfig %q", extensionsMC.Name)
979+
// Wait for rendered config to finish creating
980+
renderedConfig, err := helpers.WaitForRenderedConfig(t, cs, testOpts.poolName, extensionsMC.Name)
981+
require.NoError(t, err)
982+
t.Logf("Finished rendering config %s", renderedConfig)
983+
}
984+
926985
_, err := helpers.WaitForRenderedConfig(t, cs, testOpts.poolName, "00-worker")
927986
require.NoError(t, err)
928987

0 commit comments

Comments
 (0)