Skip to content
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

WIP: add multi-arch support #179

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/container-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ jobs:
BUILDAH_FORMAT: oci
IMG_TAG: ${{ matrix.package_source }}-${{ matrix.os }}-${{ matrix.arch }}
steps:
- uses: docker/setup-buildx-action@v3
- uses: actions/checkout@v4
- name: Build the server image
run: make KIND=server PACKAGE_SOURCE=${{ matrix.package_source }} OS_NAME=${{ matrix.os}} BUILD_ARCH=${{ matrix.arch}} build-image
run: make KIND=server PACKAGE_SOURCE=${{ matrix.package_source }} OS_NAME=${{ matrix.os}} build-image
- name: Upload server image
uses: ishworkh/[email protected]
with:
Expand Down Expand Up @@ -89,7 +90,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Build the ad server image
run: make KIND=ad-server PACKAGE_SOURCE=${{ matrix.package_source }} OS_NAME=${{ matrix.os }} BUILD_ARCH=${{ matrix.arch }} build-image
run: make KIND=ad-server PACKAGE_SOURCE=${{ matrix.package_source }} OS_NAME=${{ matrix.os }} build-image
- name: Upload ad server image
uses: ishworkh/[email protected]
with:
Expand All @@ -109,7 +110,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: build the client image
run: make KIND=client OS_NAME=${{ matrix.os }} BUILD_ARCH=${{ matrix.arch }} build-image
run: make KIND=client OS_NAME=${{ matrix.os }} build-image
# The client image is used as a base for the samba-toolbox build process.
- name: Upload the client image
uses: ishworkh/[email protected]
Expand Down Expand Up @@ -144,7 +145,7 @@ jobs:
- name: Apply latest tag to image (for fedora)
run: ${{ env.CONTAINER_CMD }} tag samba-client:${{ env.IMG_TAG }} quay.io/samba.org/samba-client:latest
- name: Build the toolbox image
run: make KIND=toolbox OS_NAME=${{ matrix.os }} BUILD_ARCH=${{ matrix.arch }} build-image
run: make KIND=toolbox OS_NAME=${{ matrix.os }} build-image
# Upload the toolbox image for reference and/or image push
- name: Upload the toolbox image
uses: ishworkh/[email protected]
Expand Down
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,9 @@ DYN_BUILDFILE=$(shell $(call _BUILD_KP,$(KIND),$(if $(PACKAGE_SOURCE),$(PACKAGE_

REPO_BASE=quay.io/samba.org/

_BUILD_KP=$(BUILD_IMAGE) $(if $(CONTAINER_CMD),--container-engine=$(CONTAINER_CMD)) $(BI_PREFIX_ARGS) --kind=$1 --package-source=$2 --distro-base=$(SRC_OS_NAME) --repo-base=$(REPO_BASE) $(if $(BUILD_ARCH),--arch=$(BUILD_ARCH)) $3
_BUILD_KP=$(BUILD_IMAGE) $(if $(CONTAINER_CMD),--container-engine=$(CONTAINER_CMD)) $(BI_PREFIX_ARGS) --kind=$1 --package-source=$2 --distro-base=$(SRC_OS_NAME) --repo-base=$(REPO_BASE) $3


arch_flag=$(strip $(if $(filter docker,$(CONTAINER_CMD)),\
$(if $(filter-out $(HOST_ARCH),$(BUILD_ARCH)),\
$(error Setting BUILD_ARCH != $(HOST_ARCH) not supported on docker)),\
$(if $(BUILD_ARCH),--arch $(BUILD_ARCH))))

build: build-server build-nightly-server build-ad-server build-client \
build-toolbox
.PHONY: build
Expand Down
156 changes: 63 additions & 93 deletions hack/build-image
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,22 @@ and list build status files (aka buildfiles).

Usage:
# build an image
./hack/build-image --kind server --distro-base fedora --arch amd64
./hack/build-image --kind server --distro-base fedora

# print out the FQIN
./hack/build-image --kind samba-server --distro-base fedora \\
--arch amd64 --print
--print

# print out the FQIN and additional tags
./hack/build-image --kind samba-server --distro-base fedora \\
--arch amd64 --print-tags
--print-tags

# print out the FQIN and additional tags for multiple images, with
# and without a repository base
./hack/build-image --kind samba-server \\
--distro-base fedora \\
--distro-base centos \\
--distro-base opensuse \\
--arch amd64 \\
--repo-base quay.io/foobar --without-repo-bases --print-tags

"""
Expand All @@ -48,12 +47,6 @@ import sys

logger = logging.getLogger("build-image")

# Set FORCE_ARCH_FLAG if you want to test passing the --arch flag to podman all
# the time. This was the previous behavior but we found it to have some issues.
# When this is false the --arch flag is passed to podman ONLY when the target
# arch != the host system arch.
FORCE_ARCH_FLAG = False

# IMAGE_KINDS - map aliases/names to canonical names for the kinds
# of images we can build
IMG_SERVER = "samba-server"
Expand All @@ -73,17 +66,11 @@ IMAGE_KINDS = {
"samba-toolbox": IMG_TOOLBOX,
}

# ARCHITECTURES - map supported arch names/alias to canonical names
AMD64 = "amd64"
ARM64 = "arm64"
ARCHITECTURES = {
# alternate names
"x86_64": AMD64,
"aarch64": ARM64,
# canonical names
"amd64": AMD64,
"arm64": ARM64,
}
# PLATFORMS - list of supported platforms
PLATFORMS = [
"linux/amd64",
"linux/arm64"
]

# DISTROS - list of supported distro bases
FEDORA = "fedora"
Expand Down Expand Up @@ -134,15 +121,6 @@ def check_kind(kind):
except KeyError:
raise ValueError(f"invalid kind: {kind}")


def check_arch(arch):
"""Return the canonical name for the arch or raise a ValueError."""
try:
return ARCHITECTURES[arch]
except KeyError:
raise ValueError(f"invalid arch: {arch}")


def check_distro(distro):
"""Return the canonical name for a distro base or raise a ValueError."""
if distro in DISTROS:
Expand Down Expand Up @@ -203,37 +181,51 @@ def container_engine(cli):

def container_build(cli, target):
"""Construct and execute a command to build the target container image."""
args = [container_engine(cli), "build"]
engine = container_engine(cli)
build_args = [engine]
builder_rm_args = [engine]
builder_create_args = [engine]
builder_name = "samba-in-kubernetes"

if "docker" in engine:
build_args += ["buildx", "build", f"--builder={builder_name}"]
builder_rm_args += ["buildx", "rm", builder_name]
builder_create_args += ["buildx", "create", f"--name={builder_name}"]
elif "podman" in engine:
build_args += ["build", f"--manifest={builder_name}"]
builder_rm_args += ["manifest", "rm", builder_name]
builder_create_args += ["manifest", "create", builder_name]
else:
raise ValueError(f"invalid container engine: {engine}")

pkgs_from = PACKAGES_FROM[target.pkg_source]
if pkgs_from:
args.append(f"--build-arg=INSTALL_PACKAGES_FROM={pkgs_from}")
# docker doesn't currently support alt. architectures
if "docker" in args[0]:
if target.arch != host_arch():
raise RuntimeError("Docker does not support --arch")
elif target.arch != host_arch() or FORCE_ARCH_FLAG:
# We've noticed a few small quirks when using podman with the --arch
# option. The main issue is that building the client image works
# but then the toolbox image fails because it somehow doesn't see
# the image we just built as usable. This doesn't happen when
# --arch is not provided. So if the target arch and the host_arch
# are the same, skip passing the extra argument.
args.append(f"--arch={target.arch}")
build_args.append(f"--build-arg=INSTALL_PACKAGES_FROM={pkgs_from}")
if cli.extra_build_arg:
args.extend(cli.extra_build_arg)
build_args.extend(cli.extra_build_arg)
for tname in target.all_names(baseless=cli.without_repo_bases):
args.append("-t")
args.append(tname)
args.append("-f")
args.append(target_containerfile(target))
args.append(kind_source_dir(target.name))
args = [str(a) for a in args]
run(cli, args, check=True)
build_args.append("-t")
build_args.append(tname)
build_args.append("-f")
build_args.append(target_containerfile(target))
build_args.append(kind_source_dir(target.name))
build_args = [str(a) for a in build_args]
build_args.append(f"--platform={','.join(PLATFORMS)}")

# Make sure that no builder exists, so that we have a clean environment.
run(cli, builder_rm_args)
# Create builder
run(cli, builder_create_args, check=True)
# Build image
run(cli, build_args, check=True)


def container_push(cli, push_name):
"""Construct and execute a command to push a container image."""
args = [container_engine(cli), "push", push_name]
args = [container_engine(cli)]
if "podman" in args[0]:
args.append("manifest")
args = args + ["push", push_name]
run(cli, args, check=True)


Expand Down Expand Up @@ -281,17 +273,6 @@ def target_containerfile(target):
"""Return the path to a containerfile given an image target."""
return str(kind_source_dir(target.name) / f"Containerfile.{target.distro}")


def host_arch():
"""Return the name of the host's native architecture."""
return check_arch(platform.machine().lower())


def default_arches():
"""Return a list of the default architectures to use for building."""
return [host_arch()]


class RepoConfig:
def __init__(self, default_repo_base, distro_repo=None):
self.default = default_repo_base
Expand All @@ -303,18 +284,17 @@ class RepoConfig:

class TargetImage:
def __init__(
self, name, pkg_source, distro, arch, extra_tag="", *, repo_base=""
self, name, pkg_source, distro, extra_tag="", *, repo_base=""
):
self.name = name
self.pkg_source = pkg_source
self.distro = distro
self.arch = arch
self.extra_tag = extra_tag
self.repo_base = repo_base
self.additional_tags = []

def tag_name(self):
tag_parts = [self.pkg_source, self.distro, self.arch]
tag_parts = [self.pkg_source, self.distro]
if self.extra_tag:
tag_parts.append(self.extra_tag)
tag = "-".join(tag_parts)
Expand Down Expand Up @@ -355,21 +335,20 @@ class TargetImage:
base = ""
rest = image_name
iname, tag = rest.split(":", 1)
tparts = tag.split("-", 3)
if len(tparts) < 3:
tparts = tag.split("-", 2)
if len(tparts) < 2:
raise ValueError(f"too few tag components: {tag!r}")
return cls(
iname,
check_pkg_source(tparts[0]),
check_distro(tparts[1]),
check_arch(tparts[2]),
extra_tag=(tparts[3] if len(tparts) > 3 else ""),
extra_tag=(tparts[2] if len(tparts) > 3 else ""),
repo_base=base,
)


def generate_images(cli):
"""Given full image names or a matrix of kind/pkg_source/distro_base/arch
"""Given full image names or a matrix of kind/pkg_source/distro_base
values generate a list of target images to build/process.
"""
images = {}
Expand All @@ -379,16 +358,14 @@ def generate_images(cli):
for kind in cli.kind or []:
for pkg_source in cli.package_source or DEFAULT_PKG_SOURCES:
for distro_base in cli.distro_base or DEFAULT_DISTRO_BASES:
for arch in cli.arch or default_arches():
timg = TargetImage(
kind,
pkg_source,
distro_base,
arch,
extra_tag=(cli.extra_tag or ""),
repo_base=rc.find_base(distro_base),
)
images[str(timg)] = timg
timg = TargetImage(
kind,
pkg_source,
distro_base,
extra_tag=(cli.extra_tag or ""),
repo_base=rc.find_base(distro_base),
)
images[str(timg)] = timg
return list(images.values())


Expand All @@ -401,15 +378,15 @@ def add_special_tags(img, distro_qualified=True):
# to keep us compatible with older tagging schemes from earlier versions of
# the project.
if img.distro in [FEDORA, OPENSUSE]:
if img.arch == host_arch() and img.pkg_source == DEFAULT:
if img.pkg_source == DEFAULT:
img.additional_tags.append((LATEST, QUAL_NONE))
if img.arch == host_arch() and img.pkg_source == NIGHTLY:
if img.pkg_source == NIGHTLY:
img.additional_tags.append((NIGHTLY, QUAL_NONE))
if not distro_qualified:
return # skip creating "distro qualified" tags
if img.arch == host_arch() and img.pkg_source == "default":
if img.pkg_source == "default":
img.additional_tags.append((f"{img.distro}-{LATEST}", QUAL_DISTRO))
if img.arch == host_arch() and img.pkg_source == "nightly":
if img.pkg_source == "nightly":
img.additional_tags.append((f"{img.distro}-{NIGHTLY}", QUAL_DISTRO))


Expand Down Expand Up @@ -592,13 +569,6 @@ def main():
"(like: --repo-base-for=centos=wonky.io/smb)"
),
)
parser.add_argument(
"--arch",
"-a",
type=check_arch,
action="append",
help="The name of the CPU architecture to build for",
)
parser.add_argument(
"--package-source",
"-p",
Expand Down
2 changes: 1 addition & 1 deletion hack/install-tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ case "$1" in
echo ""
echo "available tools:"
echo " --gitlint"
echo " --yamllint"
echo " --yamllint"
echo " --shellcheck"
;;
esac