-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bib: add experimental cross arch image building #139
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,9 +115,19 @@ func makeManifest(c *ManifestConfig, cacheRoot string) (manifest.OSBuildManifest | |
} | ||
|
||
// resolve container | ||
resolver := container.NewResolver(c.Architecture.String()) | ||
hostArch := arch.Current() | ||
resolverNative := container.NewResolver(hostArch.String()) | ||
resolverTarget := container.NewResolver(c.Architecture.String()) | ||
|
||
containerSpecs := make(map[string][]container.Spec) | ||
for plName, sourceSpecs := range manifest.GetContainerSourceSpecs() { | ||
var resolver container.Resolver | ||
if plName == "build" { | ||
resolver = resolverNative | ||
} else { | ||
resolver = resolverTarget | ||
} | ||
|
||
for _, c := range sourceSpecs { | ||
resolver.Add(c) | ||
} | ||
|
@@ -152,8 +162,8 @@ func saveManifest(ms manifest.OSBuildManifest, fpath string) error { | |
} | ||
|
||
func manifestFromCobra(cmd *cobra.Command, args []string) ([]byte, error) { | ||
hostArch := arch.Current() | ||
repos, err := loadRepos(hostArch.String()) | ||
buildArch := arch.Current() | ||
repos, err := loadRepos(buildArch.String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -163,6 +173,20 @@ func manifestFromCobra(cmd *cobra.Command, args []string) ([]byte, error) { | |
configFile, _ := cmd.Flags().GetString("config") | ||
tlsVerify, _ := cmd.Flags().GetBool("tls-verify") | ||
imgType, _ := cmd.Flags().GetString("type") | ||
targetArch, _ := cmd.Flags().GetString("target-arch") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be handling "target-variant" as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Happy to add more here - but pardon my ignorance, what does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some Arm platforms have multiple variants. So if BIB is calling skopeo or podman then it is needs to pass the variant as well as the Arch if the user specified a variant. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a TODO here for now, if you could share some container or any other link where I can inspect this a bit more that would be super helpful. I will then add/fix this in a followup. |
||
if targetArch != "" { | ||
// TODO: detect if binfmt_misc for target arch is | ||
// available, e.g. by mounting the binfmt_misc fs into | ||
// the container and inspects the files or by | ||
// including tiny statically linked target-arch | ||
// binaries inside our bib container | ||
fmt.Fprintf(os.Stderr, "WARNING: target-arch is experimental and needs an installed 'qemu-user' package\n") | ||
if imgType == "iso" { | ||
return nil, fmt.Errorf("cannot build iso for different target arches yet") | ||
} | ||
buildArch = arch.FromString(targetArch) | ||
} | ||
// TODO: add "target-variant", see https://github.com/osbuild/bootc-image-builder/pull/139/files#r1467591868 | ||
|
||
var config *BuildConfig | ||
if configFile != "" { | ||
|
@@ -179,7 +203,7 @@ func manifestFromCobra(cmd *cobra.Command, args []string) ([]byte, error) { | |
ImgType: imgType, | ||
Config: config, | ||
Repos: repos, | ||
Architecture: hostArch, | ||
Architecture: buildArch, | ||
TLSVerify: tlsVerify, | ||
} | ||
return makeManifest(manifestConfig, rpmCacheRoot) | ||
|
@@ -315,6 +339,7 @@ func run() error { | |
manifestCmd.Flags().String("config", "", "build config file") | ||
manifestCmd.Flags().String("type", "qcow2", "image type to build [qcow2, ami]") | ||
manifestCmd.Flags().Bool("tls-verify", true, "require HTTPS and verify certificates when contacting registries") | ||
manifestCmd.Flags().String("target-arch", "", "build for the given target architecture (experimental)") | ||
|
||
logrus.SetLevel(logrus.ErrorLevel) | ||
buildCmd.Flags().AddFlagSet(manifestCmd.Flags()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import abc | ||
import os | ||
import pathlib | ||
import platform | ||
import subprocess | ||
import sys | ||
import time | ||
|
@@ -93,33 +94,46 @@ def find_ovmf(): | |
|
||
class QEMU(VM): | ||
MEM = "2000" | ||
# TODO: support qemu-system-aarch64 too :) | ||
QEMU = "qemu-system-x86_64" | ||
|
||
def __init__(self, img, snapshot=True, cdrom=None): | ||
def __init__(self, img, arch="", snapshot=True, cdrom=None): | ||
super().__init__() | ||
self._img = pathlib.Path(img) | ||
self._qmp_socket = self._img.with_suffix(".qemp-socket") | ||
self._qemu_p = None | ||
self._snapshot = snapshot | ||
self._cdrom = cdrom | ||
self._ssh_port = None | ||
if not arch: | ||
arch = platform.machine() | ||
self._arch = arch | ||
|
||
def __del__(self): | ||
self.force_stop() | ||
|
||
# XXX: move args to init() so that __enter__ can use them? | ||
def start(self, wait_event="ssh", snapshot=True, use_ovmf=False): | ||
if self.running(): | ||
return | ||
log_path = self._img.with_suffix(".serial-log") | ||
self._ssh_port = get_free_port() | ||
self._address = "localhost" | ||
qemu_cmdline = [ | ||
self.QEMU, "-enable-kvm", | ||
def _gen_qemu_cmdline(self, snapshot, use_ovmf): | ||
if self._arch in ("arm64", "aarch64"): | ||
qemu_cmdline = [ | ||
"qemu-system-aarch64", | ||
"-machine", "virt", | ||
"-cpu", "cortex-a57", | ||
"-smp", "2", | ||
"-bios", "/usr/share/AAVMF/AAVMF_CODE.fd", | ||
] | ||
elif self._arch in ("amd64", "x86_64"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i also see the options here, so I'm assuming command line it should only accept amd64 or arm64? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should eventually support all of the platforms that RHEL supports, otherwise our parent company will not be happy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We definitely need to test more scenarios here but if the container is available for both host and target arch and qemu-user is available for the target arch the existing code should DTRT even for e.g. ppc64le. |
||
qemu_cmdline = [ | ||
"qemu-system-x86_64", | ||
"-M", "accel=kvm", | ||
# get "illegal instruction" inside the VM otherwise | ||
"-cpu", "host", | ||
] | ||
if use_ovmf: | ||
qemu_cmdline.extend(["-bios", find_ovmf()]) | ||
else: | ||
raise ValueError(f"unsupported architecture {self._arch}") | ||
|
||
# common part | ||
qemu_cmdline += [ | ||
"-m", self.MEM, | ||
# get "illegal instruction" inside the VM otherwise | ||
"-cpu", "host", | ||
"-serial", "stdio", | ||
"-monitor", "none", | ||
"-netdev", f"user,id=net.0,hostfwd=tcp::{self._ssh_port}-:22", | ||
|
@@ -128,18 +142,23 @@ def start(self, wait_event="ssh", snapshot=True, use_ovmf=False): | |
] | ||
if not os.environ.get("OSBUILD_TEST_QEMU_GUI"): | ||
qemu_cmdline.append("-nographic") | ||
if use_ovmf: | ||
qemu_cmdline.extend(["-bios", find_ovmf()]) | ||
if self._cdrom: | ||
qemu_cmdline.extend(["-cdrom", self._cdrom]) | ||
if snapshot: | ||
qemu_cmdline.append("-snapshot") | ||
qemu_cmdline.append(self._img) | ||
self._log(f"vm starting, log available at {log_path}") | ||
return qemu_cmdline | ||
|
||
# XXX: move args to init() so that __enter__ can use them? | ||
def start(self, wait_event="ssh", snapshot=True, use_ovmf=False): | ||
if self.running(): | ||
return | ||
self._ssh_port = get_free_port() | ||
self._address = "localhost" | ||
|
||
# XXX: use systemd-run to ensure cleanup? | ||
self._qemu_p = subprocess.Popen( | ||
qemu_cmdline, | ||
self._gen_qemu_cmdline(snapshot, use_ovmf), | ||
stdout=sys.stdout, | ||
stderr=sys.stderr, | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm reading this right, in the standard case, we'll get two identical resolvers and each one will get the same container it will have to resolve. It's not terrible but a bit wasteful. Maybe we could instead:
I now also realise that even in this case, we're resolving the same container twice (and have been since we started using the container as a build root).
Again, not terrible, but maybe we can be smarter about it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, I will look at this, maybe in a followup as it's not that pressing but I agree that it's a bit wasteful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again, I was thinking it could be something like:
which requires osbuild/images#433 (so that we get pointers to the resolver first) - I have no super strong opinion here though, happy to discuss more if you think this is less obvious or too verbose :)