]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add RHEL support
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 10 Oct 2023 09:48:01 +0000 (11:48 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 11 Oct 2023 09:11:56 +0000 (11:11 +0200)
To make RHEL work, we have to look up the necessary certificates and
add them to the generated repo files. This requires the image build to
be done from a system with a RHEL subscription.

mkosi/distributions/__init__.py
mkosi/distributions/rhel.py [new file with mode: 0644]
mkosi/distributions/rhel_ubi.py
mkosi/installer/dnf.py
mkosi/resources/mkosi.md

index 839d6ec136df2045ded1f803295095bf3d8ecfdd..858e093b76d66f6fc5fb1670c38f30e60ae4e711 100644 (file)
@@ -81,6 +81,7 @@ class Distribution(StrEnum):
     opensuse     = enum.auto()
     mageia       = enum.auto()
     centos       = enum.auto()
+    rhel         = enum.auto()
     rhel_ubi     = enum.auto()
     openmandriva = enum.auto()
     rocky        = enum.auto()
@@ -96,6 +97,7 @@ class Distribution(StrEnum):
             Distribution.fedora,
             Distribution.mageia,
             Distribution.centos,
+            Distribution.rhel,
             Distribution.rhel_ubi,
             Distribution.openmandriva,
             Distribution.rocky,
diff --git a/mkosi/distributions/rhel.py b/mkosi/distributions/rhel.py
new file mode 100644 (file)
index 0000000..cfcd568
--- /dev/null
@@ -0,0 +1,107 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
+from collections.abc import Iterable
+from pathlib import Path
+from typing import Any, Optional
+
+from mkosi.distributions import centos
+from mkosi.installer.dnf import Repo
+from mkosi.log import die
+from mkosi.state import MkosiState
+
+
+class Installer(centos.Installer):
+    @classmethod
+    def pretty_name(cls) -> str:
+        return "RHEL"
+
+    @staticmethod
+    def gpgurls(state: MkosiState) -> tuple[str, ...]:
+        return ("https://access.redhat.com/security/data/fd431d51.txt",)
+
+    @staticmethod
+    def sslcacert(state: MkosiState) -> Optional[Path]:
+        if state.config.mirror:
+            return None
+
+        p = Path("etc/rhsm/ca/redhat-uep.pem")
+        if (state.pkgmngr / p).exists():
+            p = state.pkgmngr / p
+        elif (Path("/") / p).exists():
+            p = Path("/") / p
+        else:
+            die("redhat-uep.pem certificate not found in host system or package manager tree")
+
+        return p
+
+    @staticmethod
+    def sslclientkey(state: MkosiState) -> Optional[Path]:
+        if state.config.mirror:
+            return None
+
+        pattern = "etc/pki/entitlement/*-key.pem"
+
+        p = next((p for p in sorted(state.pkgmngr.glob(pattern))), None)
+        if not p:
+            p = next((p for p in Path("/").glob(pattern)), None)
+        if not p:
+            die("Entitlement key not found in host system or package manager tree")
+
+        return p
+
+    @staticmethod
+    def sslclientcert(state: MkosiState) -> Optional[Path]:
+        if state.config.mirror:
+            return None
+
+        pattern = "etc/pki/entitlement/*.pem"
+
+        p = next((p for p in sorted(state.pkgmngr.glob(pattern)) if "key" not in p.name), None)
+        if not p:
+            p = next((p for p in sorted(Path("/").glob(pattern)) if "key" not in p.name), None)
+        if not p:
+            die("Entitlement certificate not found in host system or package manager tree")
+
+        return p
+
+    @classmethod
+    def repository_variants(cls, state: MkosiState, repo: str) -> Iterable[Repo]:
+        if state.config.local_mirror:
+            yield Repo(repo, f"baseurl={state.config.local_mirror}", cls.gpgurls(state))
+        else:
+            mirror = state.config.mirror or "https://cdn.redhat.com/content/dist/"
+
+            common: dict[str, Any] = dict(
+                gpgurls=cls.gpgurls(state),
+                sslcacert=cls.sslcacert(state),
+                sslclientcert=cls.sslclientcert(state),
+                sslclientkey=cls.sslclientkey(state),
+            )
+
+            v = state.config.release
+            major = int(float(v))
+            yield Repo(
+                f"rhel-{v}-{repo}-rpms",
+                f"baseurl={centos.join_mirror(mirror, f'rhel{major}/{v}/$basearch/{repo}/os')}",
+                enabled=True,
+                **common,
+            )
+            yield Repo(
+                f"rhel-{v}-{repo}-debug-rpms",
+                f"baseurl={centos.join_mirror(mirror, f'rhel{major}/{v}/$basearch/{repo}/debug')}",
+                enabled=False,
+                **common,
+            )
+            yield Repo(
+                f"rhel-{v}-{repo}-source",
+                f"baseurl={centos.join_mirror(mirror, f'rhel{major}/{v}/$basearch/{repo}/source')}",
+                enabled=False,
+                **common,
+            )
+
+    @classmethod
+    def repositories(cls, state: MkosiState, release: int) -> Iterable[Repo]:
+        yield from cls.repository_variants(state, "baseos")
+        yield from cls.repository_variants(state, "appstream")
+        yield from cls.repository_variants(state, "codeready-builder")
+        yield from cls.epel_repositories(state)
index 95af161752b1fc6c2cf1ddf31a64d5d314bca874..ce81e401e5450e39b12509ec70c64cc3290b9e5d 100644 (file)
@@ -47,3 +47,4 @@ class Installer(centos.Installer):
         yield from cls.repository_variants(state, "baseos")
         yield from cls.repository_variants(state, "appstream")
         yield from cls.repository_variants(state, "codeready-builder")
+        yield from cls.epel_repositories(state)
index b760c5c282955311cb5745d6a7bd602f4b0ab3e5..963e2177564bafbf118657e1c5ef11f2dd97acba 100644 (file)
@@ -4,7 +4,7 @@ import shutil
 import textwrap
 from collections.abc import Iterable
 from pathlib import Path
-from typing import NamedTuple
+from typing import NamedTuple, Optional
 
 from mkosi.run import apivfs_cmd, bwrap
 from mkosi.state import MkosiState
@@ -18,6 +18,9 @@ class Repo(NamedTuple):
     url: str
     gpgurls: tuple[str, ...]
     enabled: bool = True
+    sslcacert: Optional[Path] = None
+    sslclientkey: Optional[Path] = None
+    sslclientcert: Optional[Path] = None
 
 
 def dnf_executable(state: MkosiState) -> str:
@@ -68,6 +71,13 @@ def setup_dnf(state: MkosiState, repos: Iterable[Repo], filelists: bool = True)
                     )
                 )
 
+                if repo.sslcacert:
+                    f.write(f"sslcacert={repo.sslcacert}\n")
+                if repo.sslclientcert:
+                    f.write(f"sslclientcert={repo.sslclientcert}\n")
+                if repo.sslclientkey:
+                    f.write(f"sslclientkey={repo.sslclientkey}\n")
+
                 for i, url in enumerate(repo.gpgurls):
                     f.write("gpgkey=" if i == 0 else len("gpgkey=") * " ")
                     f.write(f"{url}\n")
index 7d934679ed729df3f32c28ca1fe528429106f543..936140738f114fe98d1de940f31e3f9c3f799710 100644 (file)
@@ -395,9 +395,10 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 
 : The distribution to install in the image. Takes one of the following
   arguments: `fedora`, `debian`, `ubuntu`, `arch`, `opensuse`, `mageia`,
-  `centos`, `rhel-ubi`, `openmandriva`, `rocky`, `alma`, `custom`.
-  If not specified, defaults to the distribution of the host or `custom`
-  if the distribution of the host is not a supported distribution.
+  `centos`, `rhel`, `rhel-ubi`, `openmandriva`, `rocky`, `alma`,
+  `custom`. If not specified, defaults to the distribution of the host
+  or `custom` if the distribution of the host is not a supported
+  distribution.
 
 `Release=`, `--release=`, `-r`
 
@@ -1321,6 +1322,8 @@ distributions:
 
 * *CentOS*
 
+* *RHEL*
+
 * *RHEL UBI*
 
 * *OpenMandriva*
@@ -1350,6 +1353,10 @@ combination of base trees, skeleton trees, and prepare scripts.
 
 Currently, *Fedora Linux* packages all relevant tools as of Fedora 28.
 
+Note that when not using a custom mirror, `RHEL` images can only be
+built from a host system with a `RHEL` subscription (established using
+e.g. `subscription-manager`).
+
 # Execution Flow
 
 Execution flow for `mkosi build`. Default values/calls are shown in parentheses.