Skip to content

Commit

Permalink
Install extensions via Containerfile for OCL
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
umohnani8 committed Nov 19, 2024
1 parent 478aa75 commit 58a3428
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@ COPY ./machineconfig/machineconfig.json.gz /tmp/machineconfig.json.gz
RUN mkdir -p /etc/machine-config-daemon && \
cat /tmp/machineconfig.json.gz | base64 -d | gunzip - > /etc/machine-config-daemon/currentconfig

{{if .ExtensionsImage}}
# Pull our extensions image. Not sure yet what / how this should be wired up
# though. Ideally, I'd like to use some Buildah tricks to have the extensions
# directory mounted into the container at build-time so that I don't have to
# copy the RPMs into the container, configure the repo, and do the
# installation. Alternatively, I'd have to start a pod with an HTTP server.
FROM {{.ExtensionsImage}} AS extensions
{{end}}


FROM {{.BaseOSImage}} AS configs
# Copy the extracted MachineConfig into the expected place in the image.
COPY --from=extract /etc/machine-config-daemon/currentconfig /etc/machine-config-daemon/currentconfig
Expand All @@ -28,6 +18,25 @@ COPY --from=extract /etc/machine-config-daemon/currentconfig /etc/machine-config
# since it should be set by the container runtime / builder.
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') && \
ostree container commit
{{if and .ExtensionsImage .Extensions}}
COPY --from={{.ExtensionsImage}} / /run/mco-extensions/os-extensions-content
# Add the extensions repo to /etc/yum.repos.d/coreos-extensions.repo
RUN cat <<EOF > /etc/yum.repos.d/coreos-extensions.repo
[coreos-extensions]
enabled=1
metadata_expire=1m
baseurl=/run/mco-extensions/os-extensions-content/usr/share/rpm-ostree/extensions/
gpgcheck=0
skip_if_unavailable=False
EOF
# Set file permissions
RUN chmod 644 /etc/yum.repos.d/coreos-extensions.repo
# Install the extensions via rpm-ostree
RUN extensions="{{- range $index, $item := .Extensions }}{{- if $index }} {{ end }}{{$item}}{{- end }}" && \
echo "Installing packages: $extensions" && \
rpm-ostree install $extensions && \
ostree container commit
{{end}}

LABEL machineconfig={{.MachineOSBuild.Spec.DesiredConfig.Name}}
LABEL machineconfigpool={{.MachineOSConfig.Spec.MachineConfigPool.Name}}
Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/build/buildrequest/buildrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,15 @@ func (br buildRequestImpl) renderContainerfile() (string, error) {
ReleaseVersion string
BaseOSImage string
ExtensionsImage string
Extensions []string
}{
MachineOSBuild: br.opts.MachineOSBuild,
MachineOSConfig: br.opts.MachineOSConfig,
UserContainerfile: br.userContainerfile,
ReleaseVersion: br.opts.getReleaseVersion(),
BaseOSImage: br.opts.getBaseOSImagePullspec(),
ExtensionsImage: br.opts.getExtensionsImagePullspec(),
Extensions: br.opts.getExtensions(),
}

if err := tmpl.Execute(out, items); err != nil {
Expand Down
18 changes: 12 additions & 6 deletions pkg/controller/build/buildrequest/buildrequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,26 @@ func TestBuildRequest(t *testing.T) {
unexpectedContainerfileContents []string
}{
{
name: "With extensions image",
optsFunc: getBuildRequestOpts,
name: "With extensions image and extensions",
optsFunc: func() BuildRequestOpts {
opts := getBuildRequestOpts()
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
return opts
},
expectedContainerfileContents: append(expectedContents(), []string{
fmt.Sprintf("FROM %s AS extensions", osImageURLConfig.BaseOSExtensionsContainerImage),
fmt.Sprintf("COPY --from=%s", osImageURLConfig.BaseOSExtensionsContainerImage),
}...),
},
{
name: "Missing extensions image",
name: "Missing extensions image and extensions",
optsFunc: func() BuildRequestOpts {
opts := getBuildRequestOpts()
opts.OSImageURLConfig.BaseOSExtensionsContainerImage = ""
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
return opts
},
unexpectedContainerfileContents: []string{
fmt.Sprintf("FROM %s AS extensions", osImageURLConfig.BaseOSContainerImage),
fmt.Sprintf("COPY --from=%s", osImageURLConfig.BaseOSContainerImage),
},
},
{
Expand Down Expand Up @@ -98,12 +103,13 @@ func TestBuildRequest(t *testing.T) {
opts.MachineOSConfig.Spec.BuildInputs.BaseOSImagePullspec = "base-os-image-from-machineosconfig"
opts.MachineOSConfig.Spec.BuildInputs.BaseOSExtensionsImagePullspec = "base-ext-image-from-machineosconfig"
opts.MachineOSConfig.Spec.BuildInputs.ReleaseVersion = "release-version-from-machineosconfig"
opts.MachineConfig.Spec.Extensions = []string{"usbguard"}
return opts
},
expectedContainerfileContents: []string{
"FROM base-os-image-from-machineosconfig AS extract",
"FROM base-os-image-from-machineosconfig AS configs",
"FROM base-ext-image-from-machineosconfig AS extensions",
"COPY --from=base-ext-image-from-machineosconfig",
"LABEL releaseversion=release-version-from-machineosconfig",
},
unexpectedContainerfileContents: expectedContents(),
Expand Down
8 changes: 8 additions & 0 deletions pkg/controller/build/buildrequest/buildrequestopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ func (b BuildRequestOpts) getReleaseVersion() string {
return b.OSImageURLConfig.ReleaseVersion
}

// Gets the extensions from the MachineConfig if available.
func (b BuildRequestOpts) getExtensions() []string {
if len(b.MachineConfig.Spec.Extensions) > 0 {
return b.MachineConfig.Spec.Extensions
}
return []string{}
}

// Gets all of the image build request opts from the Kube API server.
func newBuildRequestOptsFromAPI(ctx context.Context, kubeclient clientset.Interface, mcfgclient mcfgclientset.Interface, mosb *mcfgv1alpha1.MachineOSBuild, mosc *mcfgv1alpha1.MachineOSConfig) (*BuildRequestOpts, error) {
og := optsGetter{
Expand Down
73 changes: 73 additions & 0 deletions test/e2e-techpreview/onclusterlayering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1053,3 +1053,76 @@ func TestSSHKeyAndPasswordForOSBuilder(t *testing.T) {
t.Logf("Deleted MachineConfig %s", testConfig.Name)
})
}

func TestExtensionsForOSBuilder(t *testing.T) {
t.Helper()

cs := framework.NewClientSet("")

// label random node from pool, get the node
unlabelFunc := helpers.LabelRandomNodeFromPool(t, cs, "worker", "node-role.kubernetes.io/layered")

// prepare for on cluster build test
prepareForOnClusterLayeringTest(t, cs, onClusterLayeringTestOpts{
poolName: layeredMCPName,
customDockerfiles: map[string]string{},
useEtcPkiEntitlement: true,
})

testConfig := &mcfgv1.MachineConfig{
ObjectMeta: metav1.ObjectMeta{
Name: "99-extensions",
Labels: helpers.MCLabelForRole(layeredMCPName),
},
Spec: mcfgv1.MachineConfigSpec{
Config: runtime.RawExtension{
Raw: helpers.MarshalOrDie(ctrlcommon.NewIgnConfig()),
},
Extensions: []string{"usbguard", "kerberos"},
},
}

helpers.SetMetadataOnObject(t, testConfig)

// Create the MachineConfig and wait for the configuration to be applied
_, err := cs.MachineConfigs().Create(context.TODO(), testConfig, metav1.CreateOptions{})
require.Nil(t, err, "failed to create MC")
t.Logf("Created MC %s", testConfig.Name)

// wait for rendered config to finish creating
renderedConfig, err := helpers.WaitForRenderedConfig(t, cs, layeredMCPName, testConfig.Name)
require.Nil(t, err)
t.Logf("Finished rendering config %s", renderedConfig)

// wait for mcp to complete updating
err = helpers.WaitForPoolComplete(t, cs, layeredMCPName, renderedConfig)
require.Nil(t, err)
t.Logf("Pool %s completed updating", layeredMCPName)

// Validate the extensions are installed
osNode := helpers.GetSingleNodeByRole(t, cs, layeredMCPName) // Re-fetch node with updated configurations

foundPkg := helpers.ExecCmdOnNode(t, cs, osNode, "rpm", "-q", "usbguard")
if strings.Contains(foundPkg, "package usbguard is not installed") {
t.Fatalf("usbguard extensions not found, got %s", foundPkg)
}
t.Logf("usbguard extension installed, got %s", foundPkg)

foundPkg = helpers.ExecCmdOnNode(t, cs, osNode, "rpm", "-q", "kerberos")
if strings.Contains(foundPkg, "package kerberos is not installed") {
t.Fatalf("kerberos extensions not found, got %s", foundPkg)
}
t.Logf("kerberos extension installed, got %s", foundPkg)

t.Logf("Node %s has both usbguard and kerberos extensions installed", osNode.Name)

// Clean-up: Delete the applied MachineConfig and ensure configurations are rolled back

t.Cleanup(func() {
unlabelFunc()
if err := cs.MachineConfigs().Delete(context.TODO(), testConfig.Name, metav1.DeleteOptions{}); err != nil {
t.Error(err)
}
t.Logf("Deleted MachineConfig %s", testConfig.Name)
})
}

0 comments on commit 58a3428

Please sign in to comment.