diff --git a/tools/osbuilder/.gitignore b/tools/osbuilder/.gitignore new file mode 100644 index 0000000000..623a742bda --- /dev/null +++ b/tools/osbuilder/.gitignore @@ -0,0 +1,2 @@ +image-builder/nsdax +dracut/Dockerfile diff --git a/tools/osbuilder/CODE_OF_CONDUCT.md b/tools/osbuilder/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..d73eb8f46e --- /dev/null +++ b/tools/osbuilder/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +## Kata Containers osbuilder Code of Conduct + +Kata Containers follows the [OpenStack Foundation Code of Conduct](https://www.openstack.org/legal/community-code-of-conduct/). diff --git a/tools/osbuilder/CONTRIBUTING.md b/tools/osbuilder/CONTRIBUTING.md new file mode 100644 index 0000000000..8a3af744a6 --- /dev/null +++ b/tools/osbuilder/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contributing + +## This repo is part of [Kata Containers](https://katacontainers.io) + +For details on how to contribute to the Kata Containers project, please see the main [contributing document](https://github.com/kata-containers/community/blob/master/CONTRIBUTING.md). \ No newline at end of file diff --git a/tools/osbuilder/LICENSE b/tools/osbuilder/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/tools/osbuilder/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tools/osbuilder/Makefile b/tools/osbuilder/Makefile new file mode 100644 index 0000000000..15e57b9b77 --- /dev/null +++ b/tools/osbuilder/Makefile @@ -0,0 +1,211 @@ +# +# Copyright (c) 2017 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# +MK_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +TEST_RUNNER := $(MK_DIR)/tests/test_images.sh +ROOTFS_BUILDER := $(MK_DIR)/rootfs-builder/rootfs.sh +INITRD_BUILDER := $(MK_DIR)/initrd-builder/initrd_builder.sh +IMAGE_BUILDER := $(MK_DIR)/image-builder/image_builder.sh + +DISTRO := centos +BUILD_METHOD := distro +BUILD_METHOD_LIST := distro dracut +AGENT_INIT ?= no +ROOTFS_BUILD_DEST := $(shell pwd) +IMAGES_BUILD_DEST := $(shell pwd) +ROOTFS_MARKER_SUFFIX := _rootfs.done +TARGET_ROOTFS := $(ROOTFS_BUILD_DEST)/$(DISTRO)_rootfs +TARGET_ROOTFS_MARKER := $(ROOTFS_BUILD_DEST)/.$(DISTRO)$(ROOTFS_MARKER_SUFFIX) +TARGET_IMAGE := $(IMAGES_BUILD_DEST)/kata-containers.img +TARGET_INITRD := $(IMAGES_BUILD_DEST)/kata-containers-initrd.img + +VERSION_FILE := ./VERSION +VERSION := $(shell grep -v ^\# $(VERSION_FILE)) +COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true) +COMMIT := $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO}) +VERSION_COMMIT := $(if $(COMMIT),$(VERSION)-$(COMMIT),$(VERSION)) + +ifeq ($(filter $(BUILD_METHOD),$(BUILD_METHOD_LIST)),) + $(error Invalid BUILD_METHOD value '$(BUILD_METHOD)'. Supported values: $(BUILD_METHOD_LIST)) +endif + +ifeq (dracut,$(BUILD_METHOD)) + DISTRO := + TARGET_ROOTFS := dracut_rootfs + TARGET_ROOTFS_MARKER := $(ROOTFS_BUILD_DEST)/.dracut$(ROOTFS_MARKER_SUFFIX) + # dracut specific variables + DRACUT_KVERSION := + DRACUT_OVERLAY_DIR := $(MK_DIR)/dracut_overlay + DRACUT_DIR := $(MK_DIR)/dracut + DRACUT_CONF_DIR := $(DRACUT_DIR)/dracut.conf.d + DRACUT_OPTIONS := --no-compress --conf /dev/null --confdir $(DRACUT_CONF_DIR) + + ifneq (,$(DRACUT_KVERSION)) + # Explicitly use bash, which is what dracut uses to process conf files + DRACUT_KMODULES := $(shell bash -c 'source $(DRACUT_CONF_DIR)/10-drivers.conf; echo "$$drivers"') + else + # If a kernel version is not specified, do not make systemd load modules + # at startup + DRACUT_OPTIONS += --no-kernel + endif + + ifeq (,$(DRACUT_OVERLAY_DIR)) + $(error DRACUT_OVERLAY_DIR cannot be empty) + endif +endif + +# Set the variable to silent logs using chronic +OSBUILDER_USE_CHRONIC := + +# silent_run allows running make recipes using the chronic wrapper, so logs are +# muted if the recipe command succeeds. +# Arguments: +# - Message +# - Command to run +ifeq (,$(OSBUILDER_USE_CHRONIC)) + define silent_run + @echo $(1) + $(2) + endef +else + define silent_run + @echo $(1) with command: $(2) + @chronic $(2) + endef +endif + +################################################################################ + +rootfs-%: $(ROOTFS_BUILD_DEST)/.%$(ROOTFS_MARKER_SUFFIX) + @ # DONT remove. This is not cancellation rule. + +.PRECIOUS: $(ROOTFS_BUILD_DEST)/.%$(ROOTFS_MARKER_SUFFIX) +$(ROOTFS_BUILD_DEST)/.%$(ROOTFS_MARKER_SUFFIX):: rootfs-builder/% + $(call silent_run,Creating rootfs for "$*",$(ROOTFS_BUILDER) -o $(VERSION_COMMIT) -r $(ROOTFS_BUILD_DEST)/$*_rootfs $*) + @touch $@ + +# To generate a dracut rootfs, we first generate a dracut initrd and then +# extract it in a local folder. +# Notes: +# - assuming a not compressed initrd. +.PRECIOUS: $(ROOTFS_BUILD_DEST)/.dracut$(ROOTFS_MARKER_SUFFIX) +$(ROOTFS_BUILD_DEST)/.dracut$(ROOTFS_MARKER_SUFFIX): $(TARGET_INITRD) + mkdir -p $(TARGET_ROOTFS) + (cd $(TARGET_ROOTFS); cat $< | cpio --extract --preserve-modification-time --make-directories) + @touch $@ + +image-%: $(IMAGES_BUILD_DEST)/kata-containers-image-%.img + @ # DONT remove. This is not cancellation rule. + +.PRECIOUS: $(IMAGES_BUILD_DEST)/kata-containers-image-%.img +$(IMAGES_BUILD_DEST)/kata-containers-image-%.img: rootfs-% + $(call silent_run,Creating image based on $^,$(IMAGE_BUILDER) -o $@ $(ROOTFS_BUILD_DEST)/$*_rootfs) + +initrd-%: $(IMAGES_BUILD_DEST)/kata-containers-initrd-%.img + @ # DONT remove. This is not cancellation rule. + +.PRECIOUS: $(IMAGES_BUILD_DEST)/kata-containers-initrd-%.img +$(IMAGES_BUILD_DEST)/kata-containers-initrd-%.img: rootfs-% + $(call silent_run,Creating initrd image for $*,$(INITRD_BUILDER) -o $@ $(ROOTFS_BUILD_DEST)/$*_rootfs) + +.PHONY: all +all: image initrd + +.PHONY: rootfs +rootfs: $(TARGET_ROOTFS_MARKER) + +.PHONY: image +image: $(TARGET_IMAGE) + +$(TARGET_IMAGE): $(TARGET_ROOTFS_MARKER) + $(call silent_run,Creating image based on "$(TARGET_ROOTFS)",$(IMAGE_BUILDER) -o $@ "$(TARGET_ROOTFS)") + + +.PHONY: initrd +initrd: $(TARGET_INITRD) + +ifeq (distro,$(BUILD_METHOD)) +$(TARGET_INITRD): $(TARGET_ROOTFS_MARKER) + $(call silent_run,Creating initrd image based on "$(TARGET_ROOTFS)",$(INITRD_BUILDER) "$(TARGET_ROOTFS)") +else +$(TARGET_INITRD): $(DRACUT_OVERLAY_DIR) + @echo Creating initrd image based on the host OS using dracut + dracut $(DRACUT_OPTIONS) --include $< / $@ $(DRACUT_KVERSION) +endif + +# Notes on overlay dir: +# - If user specified any kernel module in the dracut conf file, +# we need to make sure these are pre-loaded at startup using +# systemd modules-load.d +$(DRACUT_OVERLAY_DIR): + mkdir -p $@ + # Modules preload + $(ROOTFS_BUILDER) -o $(VERSION_COMMIT) -r $@ + mkdir -p $@/etc/modules-load.d + echo $(DRACUT_KMODULES) | tr " " "\n" > $@/etc/modules-load.d/kata-modules.conf + +.PHONY: test +test: + $(TEST_RUNNER) "$(DISTRO)" + +.PHONY: test-image-only +test-image-only: + $(TEST_RUNNER) --test-images-only "$(DISTRO)" + +.PHONY: test-initrd-only +test-initrd-only: + $(TEST_RUNNER) --test-initrds-only "$(DISTRO)" + +.PHONY: list-distros +list-distros: + @ $(ROOTFS_BUILDER) -l + +DESTDIR := / +KATADIR := /usr/libexec/kata-containers +OSBUILDER_DIR := $(KATADIR)/osbuilder +INSTALL_DIR :=$(DESTDIR)/$(OSBUILDER_DIR) +DIST_CONFIGS:= $(wildcard rootfs-builder/*/config.sh) + +SCRIPTS := +SCRIPTS += rootfs-builder/rootfs.sh +SCRIPTS += image-builder/image_builder.sh +SCRIPTS += initrd-builder/initrd_builder.sh + +HELPER_FILES := +HELPER_FILES += rootfs-builder/versions.txt +HELPER_FILES += scripts/lib.sh +HELPER_FILES += image-builder/nsdax.gpl.c + +define INSTALL_FILE + echo "Installing $(abspath $2/$1)"; + install -m 644 -D $1 $2/$1; +endef + +define INSTALL_SCRIPT + echo "Installing $(abspath $2/$1)"; + install -m 755 -D $1 $(abspath $2/$1); +endef + +.PHONY: install-scripts +install-scripts: + @echo "Installing scripts" + @$(foreach f,$(SCRIPTS),$(call INSTALL_SCRIPT,$f,$(INSTALL_DIR))) + @echo "Installing helper files" + @$(foreach f,$(HELPER_FILES),$(call INSTALL_FILE,$f,$(INSTALL_DIR))) + @echo "Installing installing config files" + @$(foreach f,$(DIST_CONFIGS),$(call INSTALL_FILE,$f,$(INSTALL_DIR))) + +.PHONY: clean +clean: + rm -rf $(TARGET_ROOTFS_MARKER) $(TARGET_ROOTFS) $(TARGET_IMAGE) $(TARGET_INITRD) $(DRACUT_OVERLAY_DIR) + +# Prints the name of the variable passed as suffix to the print- target, +# E.g., if Makefile contains: +# MY_MAKE_VAR := foobar +# Then: +# $ make printf-MY_MAKE_VAR +# Will print "foobar" +print-%: + @echo $($*) diff --git a/tools/osbuilder/README.md b/tools/osbuilder/README.md new file mode 100644 index 0000000000..349accc93f --- /dev/null +++ b/tools/osbuilder/README.md @@ -0,0 +1,212 @@ +[![Build Status](https://travis-ci.org/kata-containers/osbuilder.svg?branch=master)](https://travis-ci.org/kata-containers/osbuilder) + +# osbuilder + +* [osbuilder](#osbuilder) + * [Introduction](#introduction) + * [Terms](#terms) + * [Building](#building) + * [Rootfs creation](#rootfs-creation) + * [Rootfs with systemd as init](#rootfs-with-systemd-as-init) + * [Rootfs with the agent as init](#rootfs-with-the-agent-as-init) + * [dracut based rootfs](#dracut-based-rootfs) + * [Image creation](#image-creation) + * [Image with systemd as init](#image-with-systemd-as-init) + * [Image with the agent as init](#image-with-the-agent-as-init) + * [dracut based image](#dracut-based-image) + * [Initrd creation](#initrd-creation) + * [Rootfs based initrd](#rootfs-based-initrd) + * [dracut based initrd](#dracut-based-initrd) + * [dracut options](#dracut-options) + * [Add kernel modules](#add-kernel-modules) + * [Testing](#testing) + * [Platform-Distro Compatibility Matrix](#platform-distro-compatibility-matrix) + +## Introduction + +The Kata Containers runtime creates a virtual machine (VM) to isolate a set of +container workloads. The VM requires a guest kernel and a guest operating system +("guest OS") to boot and create containers inside the guest +environment. + +This repository contains tools to create a guest OS disk image. + +## Terms + +This section describes the terms used for all documentation in this repository. + +- rootfs + + The root filesystem or "rootfs" is a slight misnomer as it is not a true filesystem. It is a tree of files contained in a particular directory, which represents the root disk layout. A rootfs can be turned into either an image or an initrd. + + See the [rootfs creation](#rootfs-creation) section. + +- "Guest OS" (or "Guest Image") + + A "virtual disk" or "disk image" built from a rootfs. It contains a + filesystem that is used by the VM, in conjunction with a guest kernel, to + create an environment to host the container. Neither the guest OS nor the + guest kernel need to be the same as the host operating system. + + See the [image creation](#image-creation) section. + +- initrd (or "initramfs") + + A compressed `cpio(1)` archive, created from a rootfs which is loaded into memory and used as part of the Linux startup process. During startup, the kernel unpacks it into a special instance of a `tmpfs` that becomes the initial root filesystem. + + See the [initrd creation](#initrd-creation) section. + +- "Base OS" + + A particular version of a Linux distribution used to create a rootfs from. + +- dracut + + A guest OS build method where the building host is used as the Base OS. + For more information refer to the [dracut homepage](https://dracut.wiki.kernel.org/index.php/Main_Page). + +## Building + +The top-level `Makefile` contains an example of how to use the available components. +Set `DEBUG=true` to execute build scripts in debug mode. + +Two build methods are available, `distro` and `dracut`. +By default, the `distro` build method is used, and this creates a rootfs using +distro specific commands (e.g.: `debootstrap` for Debian or `yum` for CentOS). +The `dracut` build method uses the distro-agnostic tool `dracut` to obtain the same goal. + +By default components are run on the host system. However, some components +offer the ability to run from within a container (for ease of setup) by setting the +`USE_DOCKER=true` or `USE_PODMAN=true` variable. If both are set, `USE_DOCKER=true` +takes precedence over `USE_PODMAN=true`. + +For more detailed information, consult the documentation for a particular component. + +When invoking the appropriate make target as showed below, a single command is used +to generate an initrd or an image. This is what happens in details: +1. A rootfs is generated based on the specified target distribution. +2. The rootfs is provisioned with Kata-specific components and configuration files. +3. The rootfs is used as a base to generate an initrd or an image. + +When using the dracut build method however, the build sequence is different: +1. An overlay directory is populated with Kata-specific components. +2. dracut is instructed to merge the overlay directory with the required host-side +filesystem components to generate an initrd. +3. When generating an image, the initrd is extracted to obtain the base rootfs for +the image. + +### Rootfs creation + +This section shows how to build a basic rootfs using the default distribution. +For further details, see +[the rootfs builder documentation](rootfs-builder/README.md). + +#### Rootfs with systemd as init + +``` +$ sudo -E PATH=$PATH make USE_DOCKER=true rootfs +``` + +#### Rootfs with the agent as init + +``` +$ sudo -E PATH=$PATH make USE_DOCKER=true AGENT_INIT=yes rootfs +``` + +#### dracut based rootfs + +> **Note**: the dracut build method does not need a rootfs as a base for an image or initrd. +However, a rootfs can be generated by extracting the generated initrd. + +``` +$ sudo -E PATH=$PATH make BUILD_METHOD=dracut rootfs +``` + +### Image creation + +This section shows how to create an image from the already-created rootfs. For +further details, see +[the image builder documentation](image-builder/README.md). + +#### Image with systemd as init + +``` +$ sudo -E PATH=$PATH make USE_DOCKER=true image +``` + +#### Image with the agent as init + +``` +$ sudo -E PATH=$PATH make USE_DOCKER=true AGENT_INIT=yes image +``` + +#### dracut based image + +> Note: the dracut build method generates an image by first building an initrd, +and then using the rootfs extracted from it. + +``` +$ sudo -E PATH=$PATH make BUILD_METHOD=dracut image +``` + +### Initrd creation + +#### Rootfs based initrd + +Create an initrd from the already-created rootfs and with the agent acting as the init daemon +using: + +``` +$ sudo -E PATH=$PATH make AGENT_INIT=yes initrd +``` + +#### dracut based initrd + +Create an initrd using the dracut build method with: + +``` +$ sudo -E PATH=$PATH make BUILD_METHOD=dracut AGENT_INIT=yes initrd +``` + +For further details, +see [the initrd builder documentation](initrd-builder/README.md). + +### dracut options + +#### Add kernel modules + +If the initrd or image needs to contain kernel modules, this can be done by: + +1. Specify the name of the modules (as reported by `modinfo MODULE-NAME`) in +`dracut/dracut.conf.d/10-drivers.conf`. For example this file can contain: +``` +drivers="9p 9pnet 9pnet_virtio" +``` +2. Set the `DRACUT_KVERSION` make variable to the release name of the kernel that +is paired with the built image or initrd, using the `uname -r` format. For example: +``` +$ make BUILD_METHOD=dracut DRACUT_KVERSION=5.2.1-23-kata AGENT_INIT=yes initrd +``` + +## Testing + +``` +$ make test +``` + +For further details, see [the tests documentation](tests/README.md). + +## Platform-Distro Compatibility Matrix + +The following table illustrates what target architecture is supported for each +of the the osbuilder distributions. + +> Note: this table is not relevant for the dracut build method, since it supports +any Linux distribution and architecture where dracut is available. + +| |Alpine |CentOS |Clear Linux |Debian/Ubuntu |EulerOS |Fedora |openSUSE | +|-- |-- |-- |-- |-- |-- |-- |-- | +|**ARM64** |:heavy_check_mark:|:heavy_check_mark:| | |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| +|**PPC64le**|:heavy_check_mark:|:heavy_check_mark:| |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| +|**s390x** |:heavy_check_mark:| | |:heavy_check_mark:| |:heavy_check_mark:| | +|**x86_64** |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| diff --git a/tools/osbuilder/VERSION b/tools/osbuilder/VERSION new file mode 100644 index 0000000000..46d0bdd026 --- /dev/null +++ b/tools/osbuilder/VERSION @@ -0,0 +1 @@ +1.11.0-rc0 diff --git a/tools/osbuilder/dracut/Dockerfile.in b/tools/osbuilder/dracut/Dockerfile.in new file mode 100644 index 0000000000..9d3a7c75e4 --- /dev/null +++ b/tools/osbuilder/dracut/Dockerfile.in @@ -0,0 +1,38 @@ +# +# Copyright (c) 2019 SUSE LLC +# +# SPDX-License-Identifier: Apache-2.0 + +from opensuse/tumbleweed + +RUN zypper --non-interactive refresh; \ + zypper --non-interactive install --no-recommends --force-resolution \ + autoconf \ + automake \ + binutils \ + cmake \ + coreutils \ + cpio \ + curl \ + dracut \ + gcc \ + gcc-c++ \ + git-core \ + glibc-devel \ + glibc-devel-static \ + glibc-utils \ + libstdc++-devel \ + linux-glibc-devel \ + m4 \ + make \ + sed \ + tar \ + vim \ + which; \ + zypper --non-interactive clean --all; + + +# This will install the proper golang to build Kata components +@INSTALL_MUSL@ +@INSTALL_GO@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/dracut/dracut.conf.d/05-base.conf b/tools/osbuilder/dracut/dracut.conf.d/05-base.conf new file mode 100644 index 0000000000..1dd41c4a11 --- /dev/null +++ b/tools/osbuilder/dracut/dracut.conf.d/05-base.conf @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 SUSE LLC +# +# SPDX-License-Identifier: Apache-2.0 + +# Main dracut config for Kata Containers + +# do NOT combine early microcode with ramdisk +early_microcode="no" +# do NOT install only what's needed to boot the local host +hostonly="no" +# do NOT store the kernel command line arguments in the initramfs +hostonly_cmdline="no" +# create reproducible images +reproducible="yes" +# dracut modules to include (NOTE: these are NOT kernel modules) +dracutmodules="kernel-modules udev-rules syslog systemd" diff --git a/tools/osbuilder/dracut/dracut.conf.d/10-drivers.conf b/tools/osbuilder/dracut/dracut.conf.d/10-drivers.conf new file mode 100644 index 0000000000..121e858387 --- /dev/null +++ b/tools/osbuilder/dracut/dracut.conf.d/10-drivers.conf @@ -0,0 +1,9 @@ +# +# Copyright (c) 2019 SUSE LLC +# +# SPDX-License-Identifier: Apache-2.0 + +# Specify a space-separated set of kernel modules to copy from the host to +# the initramfs image. For example: +# drivers="9p 9pnet 9pnet_virtio" +drivers="" diff --git a/tools/osbuilder/image-builder/Dockerfile b/tools/osbuilder/image-builder/Dockerfile new file mode 100644 index 0000000000..c327922587 --- /dev/null +++ b/tools/osbuilder/image-builder/Dockerfile @@ -0,0 +1,10 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +From docker.io/fedora:latest + +RUN [ -n "$http_proxy" ] && sed -i '$ a proxy='$http_proxy /etc/dnf/dnf.conf ; true + +RUN dnf install -y qemu-img parted gdisk e2fsprogs gcc xfsprogs findutils diff --git a/tools/osbuilder/image-builder/README.md b/tools/osbuilder/image-builder/README.md new file mode 100644 index 0000000000..acfa24e20b --- /dev/null +++ b/tools/osbuilder/image-builder/README.md @@ -0,0 +1,29 @@ +* [Creating a guest OS image](#creating-a-guest-os-image) +* [Further information](#further-information) + +# Kata Containers image generation + +A Kata Containers disk image is generated using the `image_builder.sh` script. +This uses a rootfs directory created by the `rootfs-builder/rootfs.sh` script. + +## Creating a guest OS image + +To create a guest OS image run: + +``` +$ sudo ./image_builder.sh path/to/rootfs +``` + +Where `path/to/rootfs` is the directory populated by `rootfs.sh`. + +> **Note**: If you are building an image from an Alpine rootfs, see +> the important note [here](/rootfs-builder/README.md#rootfs-requirements). + +## Further information + +For more information about usage (including how to adjust the size of the +image), run: + +``` +$ ./image_builder.sh -h +``` diff --git a/tools/osbuilder/image-builder/image_builder.sh b/tools/osbuilder/image-builder/image_builder.sh new file mode 100755 index 0000000000..54f4c007d2 --- /dev/null +++ b/tools/osbuilder/image-builder/image_builder.sh @@ -0,0 +1,513 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2017-2019 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +set -e + +[ -n "${DEBUG}" ] && set -x + +DOCKER_RUNTIME=${DOCKER_RUNTIME:-runc} + +readonly script_name="${0##*/}" +readonly script_dir=$(dirname "$(readlink -f "$0")") +readonly lib_file="${script_dir}/../scripts/lib.sh" + +readonly ext4_format="ext4" +readonly xfs_format="xfs" + +# ext4: percentage of the filesystem which may only be allocated by privileged processes. +readonly reserved_blocks_percentage=3 + +# Where the rootfs starts in MB +readonly rootfs_start=1 + +# Where the rootfs ends in MB +readonly rootfs_end=-1 + +# DAX header size +# * NVDIMM driver reads the device namespace information from nvdimm namespace (4K offset). +# The MBR #1 + DAX metadata are saved in the first 2MB of the image. +readonly dax_header_sz=2 + +# DAX aligment +# * DAX huge pages [2]: 2MB alignment +# [2] - https://nvdimm.wiki.kernel.org/2mib_fs_dax +readonly dax_alignment=2 + +# The list of systemd units and files that are not needed in Kata Containers +readonly -a systemd_units=( + "systemd-coredump@" + "systemd-journald" + "systemd-journald-dev-log" + "systemd-journal-flush" + "systemd-random-seed" + "systemd-timesyncd" + "systemd-tmpfiles-setup" + "systemd-udevd" + "systemd-udevd-control" + "systemd-udevd-kernel" + "systemd-udev-trigger" + "systemd-update-utmp" +) + +readonly -a systemd_files=( + "systemd-bless-boot-generator" + "systemd-fstab-generator" + "systemd-getty-generator" + "systemd-gpt-auto-generator" + "systemd-tmpfiles-cleanup.timer" +) + +# Set a default value +AGENT_INIT=${AGENT_INIT:-no} + +# Align image to (size in MB) according to different architecture. +case "$(uname -m)" in + aarch64) readonly mem_boundary_mb=16 ;; + *) readonly mem_boundary_mb=128 ;; +esac + +# shellcheck source=../scripts/lib.sh +source "${lib_file}" + +usage() { + cat < + This script will create a Kata Containers image file of + an adequate size based on the directory. + +Options: + -h Show this help + -o path to generate image file ENV: IMAGE + -r Free space of the root partition in MB ENV: ROOT_FREE_SPACE + +Extra environment variables: + AGENT_BIN: Use it to change the expected agent binary name + AGENT_INIT: Use kata agent as init process + NSDAX_BIN: Use to specify path to pre-compiled 'nsdax' tool. + FS_TYPE: Filesystem type to use. Only xfs and ext4 are supported. + USE_DOCKER: If set will build image in a Docker Container (requries docker) + DEFAULT: not set + USE_PODMAN: If set and USE_DOCKER not set, will build image in a Podman Container (requries podman) + DEFAULT: not set + + +Following diagram shows how the resulting image will look like + + .-----------.----------.---------------.-----------. + | 0 - 512 B | 4 - 8 Kb | 2M - 2M+512B | 3M | + |-----------+----------+---------------+-----------+ + | MBR #1 | DAX | MBR #2 | Rootfs | + '-----------'----------'---------------'-----------+ + | | ^ | ^ + | '-data-' '--------' + | | + '--------rootfs-partition---------' + + +MBR: Master boot record. +DAX: Metadata required by the NVDIMM driver to enable DAX in the guest [1][2] (struct nd_pfn_sb). +Rootfs: partition that contains the root filesystem (/usr, /bin, ect). + +Kernels and hypervisors that support DAX/NVDIMM read the MBR #2, otherwise MBR #1 is read. + +[1] - https://github.com/kata-containers/osbuilder/blob/master/image-builder/nsdax.gpl.c +[2] - https://github.com/torvalds/linux/blob/master/drivers/nvdimm/pfn.h + +EOT +} + + +# build the image using container engine +build_with_container() { + local rootfs="$1" + local image="$2" + local fs_type="$3" + local block_size="$4" + local root_free_space="$5" + local agent_bin="$6" + local agent_init="$7" + local container_engine="$8" + local nsdax_bin="$9" + local container_image_name="image-builder-osbuilder" + local shared_files="" + + image_dir=$(readlink -f "$(dirname "${image}")") + image_name=$(basename "${image}") + + "${container_engine}" build \ + --build-arg http_proxy="${http_proxy}" \ + --build-arg https_proxy="${https_proxy}" \ + -t "${container_image_name}" "${script_dir}" + + readonly mke2fs_conf="/etc/mke2fs.conf" + if [ -f "${mke2fs_conf}" ]; then + shared_files+="-v ${mke2fs_conf}:${mke2fs_conf}:ro " + fi + + #Make sure we use a compatible runtime to build rootfs + # In case Clear Containers Runtime is installed we dont want to hit issue: + #https://github.com/clearcontainers/runtime/issues/828 + "${container_engine}" run \ + --rm \ + --runtime "${DOCKER_RUNTIME}" \ + --privileged \ + --env AGENT_BIN="${agent_bin}" \ + --env AGENT_INIT="${agent_init}" \ + --env FS_TYPE="${fs_type}" \ + --env BLOCK_SIZE="${block_size}" \ + --env ROOT_FREE_SPACE="${root_free_space}" \ + --env NSDAX_BIN="${nsdax_bin}" \ + --env DEBUG="${DEBUG}" \ + -v /dev:/dev \ + -v "${script_dir}":"/osbuilder" \ + -v "${script_dir}/../scripts":"/scripts" \ + -v "${rootfs}":"/rootfs" \ + -v "${image_dir}":"/image" \ + ${shared_files} \ + ${container_image_name} \ + bash "/osbuilder/${script_name}" -o "/image/${image_name}" /rootfs +} + +check_rootfs() { + local rootfs="${1}" + + [ -d "${rootfs}" ] || die "${rootfs} is not a directory" + + # The kata rootfs image expect init and kata-agent to be installed + init_path="/sbin/init" + init="${rootfs}${init_path}" + if [ ! -x "${init}" ] && [ ! -L "${init}" ]; then + error "${init_path} is not installed in ${rootfs}" + return 1 + fi + OK "init is installed" + + + systemd_path="/lib/systemd/systemd" + systemd="${rootfs}${systemd_path}" + + # check agent or systemd + case "${AGENT_INIT}" in + "no") + if [ ! -x "${systemd}" ] && [ ! -L "${systemd}" ]; then + error "${systemd_path} is not installed in ${rootfs}" + return 1 + fi + OK "init is systemd" + ;; + + "yes") + agent_path="/sbin/init" + agent="${rootfs}${agent_path}" + if [ ! -x "${agent}" ]; then + error "${agent_path} is not installed in ${rootfs}. Use AGENT_BIN env variable to change the expected agent binary name" + return 1 + fi + # checksum must be different to system + if [ -f "${systemd}" ] && cmp -s "${systemd}" "${agent}"; then + error "The agent is not the init process. ${agent_path} is systemd" + return 1 + fi + + OK "Agent installed" + ;; + + *) + error "Invalid value for AGENT_INIT: '${AGENT_INIT}'. Use to 'yes' or 'no'" + return 1 + ;; + esac + + return 0 +} + +calculate_required_disk_size() { + local rootfs="$1" + local fs_type="$2" + local block_size="$3" + + readonly rootfs_size_mb=$(du -B 1MB -s "${rootfs}" | awk '{print $1}') + readonly image="$(mktemp)" + readonly mount_dir="$(mktemp -d)" + readonly max_tries=20 + readonly increment=10 + + for i in $(seq 1 $max_tries); do + local img_size="$((rootfs_size_mb + (i * increment)))" + create_disk "${image}" "${img_size}" "${fs_type}" "${rootfs_start}" > /dev/null 2>&1 + if ! device="$(setup_loop_device "${image}")"; then + continue + fi + + if ! format_loop "${device}" "${block_size}" "${fs_type}" > /dev/null 2>&1 ; then + die "Could not format loop device: ${device}" + fi + mount "${device}p1" "${mount_dir}" + avail="$(df -BM --output=avail "${mount_dir}" | tail -n1 | sed 's/[M ]//g')" + umount "${mount_dir}" + losetup -d "${device}" + + if [ "${avail}" -gt "${rootfs_size_mb}" ]; then + rmdir "${mount_dir}" + rm -f "${image}" + echo "${img_size}" + return + fi + done + + + rmdir "${mount_dir}" + rm -f "${image}" + error "Could not calculate the required disk size" +} + +# Calculate image size based on the rootfs and free space +calculate_img_size() { + local rootfs="$1" + local root_free_space_mb="$2" + local fs_type="$3" + local block_size="$4" + + # rootfs start + DAX header size + rootfs end + local reserved_size_mb=$((rootfs_start + dax_header_sz + rootfs_end)) + + disk_size="$(calculate_required_disk_size "${rootfs}" "${fs_type}" "${block_size}")" + + img_size="$((disk_size + reserved_size_mb))" + if [ -n "${root_free_space_mb}" ]; then + img_size="$((img_size + root_free_space_mb))" + fi + + remaining="$((img_size % mem_boundary_mb))" + if [ "${remaining}" != "0" ]; then + img_size=$((img_size + mem_boundary_mb - remaining)) + fi + + echo "${img_size}" +} + +setup_loop_device() { + local image="$1" + + # Get the loop device bound to the image file (requires /dev mounted in the + # image build system and root privileges) + device=$(losetup -P -f --show "${image}") + + #Refresh partition table + partprobe -s "${device}" > /dev/null + # Poll for the block device p1 + for _ in $(seq 1 5); do + if [ -b "${device}p1" ]; then + echo "${device}" + return 0 + fi + sleep 1 + done + + error "File ${device}p1 is not a block device" + return 1 +} + +format_loop() { + local device="$1" + local block_size="$2" + local fs_type="$3" + + case "${fs_type}" in + "${ext4_format}") + mkfs.ext4 -q -F -b "${block_size}" "${device}p1" + info "Set filesystem reserved blocks percentage to ${reserved_blocks_percentage}%" + tune2fs -m "${reserved_blocks_percentage}" "${device}p1" + ;; + + "${xfs_format}") + mkfs.xfs -q -f -b size="${block_size}" "${device}p1" + ;; + + *) + error "Unsupported fs type: ${fs_type}" + return 1 + ;; + esac +} + +create_disk() { + local image="$1" + local img_size="$2" + local fs_type="$3" + local part_start="$4" + + info "Creating raw disk with size ${img_size}M" + qemu-img create -q -f raw "${image}" "${img_size}M" + OK "Image file created" + + # Kata runtime expect an image with just one partition + # The partition is the rootfs content + info "Creating partitions" + parted -s -a optimal "${image}" -- \ + mklabel msdos \ + mkpart primary "${fs_type}" "${part_start}"M "${rootfs_end}"M + + OK "Partitions created" +} + +create_rootfs_image() { + local rootfs="$1" + local image="$2" + local img_size="$3" + local fs_type="$4" + local block_size="$5" + + create_disk "${image}" "${img_size}" "${fs_type}" "${rootfs_start}" + + if ! device="$(setup_loop_device "${image}")"; then + die "Could not setup loop device" + fi + + if ! format_loop "${device}" "${block_size}" "${fs_type}"; then + die "Could not format loop device: ${device}" + fi + + info "Mounting root partition" + readonly mount_dir=$(mktemp -p ${TMPDIR:-/tmp} -d osbuilder-mount-dir.XXXX) + mount "${device}p1" "${mount_dir}" + OK "root partition mounted" + + info "Copying content from rootfs to root partition" + cp -a "${rootfs}"/* "${mount_dir}" + sync + OK "rootfs copied" + + info "Removing unneeded systemd services and sockets" + for u in "${systemd_units[@]}"; do + find "${mount_dir}" -type f \( \ + -name "${u}.service" -o \ + -name "${u}.socket" \) \ + -exec rm -f {} \; + done + + info "Removing unneeded systemd files" + for u in "${systemd_files[@]}"; do + find "${mount_dir}" -type f -name "${u}" -exec rm -f {} \; + done + + info "Creating empty machine-id to allow systemd to bind-mount it" + touch "${mount_dir}/etc/machine-id" + + info "Unmounting root partition" + umount "${mount_dir}" + OK "Root partition unmounted" + + if [ "${fs_type}" = "${ext4_format}" ]; then + fsck.ext4 -D -y "${device}p1" + fi + + losetup -d "${device}" + rmdir "${mount_dir}" +} + +set_dax_header() { + local image="$1" + local img_size="$2" + local fs_type="$3" + local nsdax_bin="$4" + + # rootfs start + DAX header size + local rootfs_offset=$((rootfs_start + dax_header_sz)) + local header_image="${image}.header" + local dax_image="${image}.dax" + rm -f "${dax_image}" "${header_image}" + + create_disk "${header_image}" "${img_size}" "${fs_type}" "${rootfs_offset}" + + dax_header_bytes=$((dax_header_sz * 1024 * 1024)) + dax_alignment_bytes=$((dax_alignment * 1024 * 1024)) + info "Set DAX metadata" + # Set metadata header + # Issue: https://github.com/kata-containers/osbuilder/issues/240 + if [ -z "${nsdax_bin}" ] ; then + nsdax_bin="${script_dir}/nsdax" + gcc -O2 "${script_dir}/nsdax.gpl.c" -o "${nsdax_bin}" + trap "rm ${nsdax_bin}" EXIT + fi + "${nsdax_bin}" "${header_image}" "${dax_header_bytes}" "${dax_alignment_bytes}" + sync + + touch "${dax_image}" + # Copy MBR #1 + DAX metadata + dd if="${header_image}" of="${dax_image}" bs="${dax_header_sz}M" count=1 + # Copy MBR #2 + Rootfs + dd if="${image}" of="${dax_image}" oflag=append conv=notrunc + # final image + mv "${dax_image}" "${image}" + sync + + rm -f "${dax_image}" "${header_image}" +} + +main() { + [ "$(id -u)" -eq 0 ] || die "$0: must be run as root" + + # variables that can be overwritten by environment variables + local agent_bin="${AGENT_BIN:-kata-agent}" + local agent_init="${AGENT_INIT:-no}" + local fs_type="${FS_TYPE:-${ext4_format}}" + local image="${IMAGE:-kata-containers.img}" + local block_size="${BLOCK_SIZE:-4096}" + local root_free_space="${ROOT_FREE_SPACE:-}" + local nsdax_bin="${NSDAX_BIN:-}" + + while getopts "ho:r:f:" opt + do + case "$opt" in + h) usage; return 0;; + o) image="${OPTARG}" ;; + r) root_free_space="${OPTARG}" ;; + f) fs_type="${OPTARG}" ;; + *) break ;; + esac + done + + shift $(( OPTIND - 1 )) + rootfs="$(readlink -f "$1")" + if [ -z "${rootfs}" ]; then + usage + exit 0 + fi + + local container_engine + if [ -n "${USE_DOCKER}" ]; then + container_engine="docker" + elif [ -n "${USE_PODMAN}" ]; then + container_engine="podman" + fi + + if [ -n "$container_engine" ]; then + build_with_container "${rootfs}" \ + "${image}" "${fs_type}" "${block_size}" \ + "${root_free_space}" "${agent_bin}" \ + "${agent_init}" "${container_engine}" \ + "${nsdax_bin}" + exit $? + fi + + if ! check_rootfs "${rootfs}" ; then + die "Invalid rootfs" + fi + + img_size=$(calculate_img_size "${rootfs}" "${root_free_space}" "${fs_type}" "${block_size}") + + # the first 2M are for the first MBR + NVDIMM metadata and were already + # consider in calculate_img_size + rootfs_img_size=$((img_size - dax_header_sz)) + create_rootfs_image "${rootfs}" "${image}" "${rootfs_img_size}" \ + "${fs_type}" "${block_size}" + + # insert at the beginning of the image the MBR + DAX header + set_dax_header "${image}" "${img_size}" "${fs_type}" "${nsdax_bin}" +} + +main "$@" diff --git a/tools/osbuilder/image-builder/nsdax.gpl.c b/tools/osbuilder/image-builder/nsdax.gpl.c new file mode 100644 index 0000000000..333f7804b8 --- /dev/null +++ b/tools/osbuilder/image-builder/nsdax.gpl.c @@ -0,0 +1,171 @@ +/* + * Copyright(c) 2013-2019 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define __KERNEL__ +#include +#include + +/* + Next types, definitions and functions were copied from kernel 4.19.24 source + code, specifically from nvdimm driver +*/ + +#define PFN_SIG_LEN 16 +#define PFN_SIG "NVDIMM_PFN_INFO" +#define SZ_4K 0x00001000 + +typedef __u16 u16; +typedef __u8 u8; +typedef __u64 u64; +typedef __u32 u32; + +enum nd_pfn_mode { + PFN_MODE_NONE, + PFN_MODE_RAM, + PFN_MODE_PMEM, +}; + +struct nd_pfn_sb { + u8 signature[PFN_SIG_LEN]; + u8 uuid[16]; + u8 parent_uuid[16]; + __le32 flags; + __le16 version_major; + __le16 version_minor; + __le64 dataoff; /* relative to namespace_base + start_pad */ + __le64 npfns; + __le32 mode; + /* minor-version-1 additions for section alignment */ + __le32 start_pad; + __le32 end_trunc; + /* minor-version-2 record the base alignment of the mapping */ + __le32 align; + u8 padding[4000]; + __le64 checksum; +}; + +struct nd_gen_sb { + char reserved[SZ_4K - 8]; + __le64 checksum; +}; + + +u64 nd_fletcher64(void *addr, size_t len, bool le) +{ + u32 *buf = addr; + u32 lo32 = 0; + u64 hi32 = 0; + int i; + + for (i = 0; i < len / sizeof(u32); i++) { + lo32 += le ? __le32_to_cpu((__le32) buf[i]) : buf[i]; + hi32 += lo32; + } + + return hi32 << 32 | lo32; +} + + +/* + * nd_sb_checksum: compute checksum for a generic info block + * + * Returns a fletcher64 checksum of everything in the given info block + * except the last field (since that's where the checksum lives). + */ +u64 nd_sb_checksum(struct nd_gen_sb *nd_gen_sb) +{ + u64 sum; + __le64 sum_save; + + sum_save = nd_gen_sb->checksum; + nd_gen_sb->checksum = 0; + sum = nd_fletcher64(nd_gen_sb, sizeof(*nd_gen_sb), 1); + nd_gen_sb->checksum = sum_save; + return sum; +} + + +void show_usage(const char* name) { + printf("Usage: %s IMAGE_FILE DATA_OFFSET ALIGNMENT\n", name); + printf("DATA_OFFSET and ALIGNMENT must be in bytes\n"); +} + +int main(int argc, char *argv[]) { + if (argc != 4) { + show_usage(argv[0]); + return -1; + } + + const char* img_path = argv[1]; + + char *ptr = NULL; + const long int data_offset = strtol(argv[2], &ptr, 10); + if (ptr == argv[2]) { + fprintf(stderr, "Couldn't convert string '%s' to int\n", argv[2]); + show_usage(argv[0]); + return -1; + } + + ptr = NULL; + const long int alignment = strtol(argv[3], &ptr, 10); + if (ptr == argv[3]) { + fprintf(stderr, "Couldn't convert string '%s' to int\n", argv[3]); + show_usage(argv[0]); + return -1; + } + + printf("Opening file '%s'\n", img_path); + int fd = open(img_path, O_WRONLY); + if (fd == -1) { + perror("open:"); + return -1; + } + + struct nd_pfn_sb sb = { 0 }; + + snprintf((char*)sb.signature, PFN_SIG_LEN, PFN_SIG); + sb.mode = PFN_MODE_RAM; + sb.align = alignment; + sb.dataoff = data_offset; + sb.version_minor = 2; + + // checksum must be calculated at the end + sb.checksum = nd_sb_checksum((struct nd_gen_sb*) &sb); + + // NVDIMM driver: SZ_4K is the namespace-relative starting offset + int ret = lseek(fd, SZ_4K, SEEK_SET); + if (ret == -1) { + perror("lseek: "); + close(fd); + return -1; + } + + printf("Writing metadata\n"); + ret = write(fd, &sb, sizeof(sb)); + if (ret == -1) { + perror("write: "); + } + + close(fd); + printf("OK!\n"); + + return 0; +} diff --git a/tools/osbuilder/initrd-builder/README.md b/tools/osbuilder/initrd-builder/README.md new file mode 100644 index 0000000000..66eee37eb8 --- /dev/null +++ b/tools/osbuilder/initrd-builder/README.md @@ -0,0 +1,25 @@ +* [Creating a guest OS initrd image](#creating-a-guest-os-initrd-image) +* [Further information](#further-information) + +# Kata Containers initrd image generation + +A Kata Containers initrd image is generated using the `initrd_builder.sh` script. +This script uses a rootfs directory created by the `rootfs-builder/rootfs.sh` script. + +## Creating a guest OS initrd image + +To create a guest OS initrd image run: + +``` +$ sudo ./initrd_builder.sh path/to/rootfs +``` + +The `rootfs.sh` script populates the `path/to/rootfs` directory. + +## Further information + +For more information on how to use the `initrd_builder.sh` script, run: + +``` +$ ./initrd_builder.sh -h +``` diff --git a/tools/osbuilder/initrd-builder/initrd_builder.sh b/tools/osbuilder/initrd-builder/initrd_builder.sh new file mode 100755 index 0000000000..5313502894 --- /dev/null +++ b/tools/osbuilder/initrd-builder/initrd_builder.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# +# Copyright (c) 2018 HyperHQ Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +set -e + +[ -n "$DEBUG" ] && set -x + +script_name="${0##*/}" +script_dir="$(dirname $(readlink -f $0))" + +lib_file="${script_dir}/../scripts/lib.sh" +source "$lib_file" + +INITRD_IMAGE="${INITRD_IMAGE:-kata-containers-initrd.img}" +AGENT_BIN=${AGENT_BIN:-kata-agent} +AGENT_INIT=${AGENT_INIT:-no} + +usage() +{ + error="${1:-0}" + cat < + This script creates a Kata Containers initrd image file based on the + directory. + +Options: + -h Show help + -o Set the path where the generated image file is stored. + DEFAULT: the path stored in the environment variable INITRD_IMAGE + +Extra environment variables: + AGENT_BIN: use it to change the expected agent binary name + DEFAULT: kata-agent + AGENT_INIT: use kata agent as init process + DEFAULT: no +EOT +exit "${error}" +} + +while getopts "ho:" opt +do + case "$opt" in + h) usage ;; + o) INITRD_IMAGE="${OPTARG}" ;; + esac +done + +shift $(( $OPTIND - 1 )) + +ROOTFS="$1" + + +[ -n "${ROOTFS}" ] || usage +[ -d "${ROOTFS}" ] || die "${ROOTFS} is not a directory" + +ROOTFS=$(readlink -f ${ROOTFS}) +IMAGE_DIR=$(dirname ${INITRD_IMAGE}) +IMAGE_DIR=$(readlink -f ${IMAGE_DIR}) +IMAGE_NAME=$(basename ${INITRD_IMAGE}) + +# The kata rootfs image expects init to be installed +init="${ROOTFS}/sbin/init" +[ -x "${init}" ] || [ -L ${init} ] || die "/sbin/init is not installed in ${ROOTFS}" +OK "init is installed" +[ "${AGENT_INIT}" == "yes" ] || [ -x "${ROOTFS}/usr/bin/${AGENT_BIN}" ] || \ + die "/usr/bin/${AGENT_BIN} is not installed in ${ROOTFS} + use AGENT_BIN env variable to change the expected agent binary name" +OK "Agent is installed" + +# initramfs expects /init +ln -sf /sbin/init "${ROOTFS}/init" + +info "Creating ${IMAGE_DIR}/${IMAGE_NAME} based on rootfs at ${ROOTFS}" +( cd "${ROOTFS}" && find . | cpio -H newc -o | gzip -9 ) > "${IMAGE_DIR}"/"${IMAGE_NAME}" diff --git a/tools/osbuilder/rootfs-builder/.gitignore b/tools/osbuilder/rootfs-builder/.gitignore new file mode 100644 index 0000000000..94143827ed --- /dev/null +++ b/tools/osbuilder/rootfs-builder/.gitignore @@ -0,0 +1 @@ +Dockerfile diff --git a/tools/osbuilder/rootfs-builder/README.md b/tools/osbuilder/rootfs-builder/README.md new file mode 100644 index 0000000000..9ceb1b7a24 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/README.md @@ -0,0 +1,212 @@ +* [Building a Guest OS rootfs for Kata Containers](#building-a-guest-os-rootfs-for-kata-containers) + * [Supported base OSs](#supported-base-oss) + * [Extra features](#extra-features) + * [Supported distributions list](#supported-distributions-list) + * [Generate Kata specific files](#generate-kata-specific-files) + * [Rootfs requirements](#rootfs-requirements) + * [Creating a rootfs](#creating-a-rootfs) + * [Creating a rootfs with kernel modules](#creating-a-rootfs-with-kernel-modules) + * [Build a rootfs using Docker](#build-a-rootfs-using-docker) + * [Adding support for a new guest OS](#adding-support-for-a-new-guest-os) + * [Create template files](#create-template-files) + * [Modify template files](#modify-template-files) + * [Expected rootfs directory content](#expected-rootfs-directory-content) + * [Optional - Customize the rootfs](#optional---customize-the-rootfs) + * [Adding extra packages](#adding-extra-packages) + * [Arbitrary rootfs changes](#arbitrary-rootfs-changes) + +# Building a Guest OS rootfs for Kata Containers + +The Kata Containers rootfs is created using the `rootfs.sh` script. + +## Supported base OSs + +The `rootfs.sh` script builds a rootfs based on a particular Linux\* +distribution. The script supports multiple distributions and can be extended +to add further ones. + +### Extra features + +#### Supported distributions list + +List the supported distributions by running the following: +``` +$ ./rootfs.sh -l +``` + +#### Generate Kata specific files +The `rootfs.sh` script can be used to populate a directory with only Kata specific files and +components, without creating a full usable rootfs. +This feature is used to create a rootfs based on a distribution not officially +supported by osbuilder, and when building an image using the dracut build method. + +To achieve this, simply invoke `rootfs.sh` without specifying a target rootfs, e.g.: +``` +$ mkdir kata-overlay +$ ./rootfs.sh -r "$PWD/kata-overlay" +``` + +## Rootfs requirements + +The rootfs must provide at least the following components: + +- [Kata agent](https://github.com/kata-containers/agent) + + Path: `/bin/kata-agent` - Kata Containers guest. + +- An `init` system (e.g. `systemd`) to start the Kata agent + when the guest OS boots. + + Path: `/sbin/init` - init binary called by the kernel. + +When the `AGENT_INIT` environment variable is set to `yes`, use Kata agent as `/sbin/init`. + +> **Note**: `AGENT_INIT=yes` **must** be used for the Alpine distribution +> since it does not use `systemd` as its init daemon. + +## Creating a rootfs + +To build a rootfs for your chosen distribution, run: + +``` +$ sudo ./rootfs.sh +``` + +## Creating a rootfs with kernel modules + +To build a rootfs with additional kernel modules, run: +``` +$ sudo KERNEL_MODULES_DIR=${kernel_mod_dir} ./rootfs.sh +``` +Where `kernel_mod_dir` points to the kernel modules directory to be put under the +`/lib/modules/` directory of the created rootfs. + +## Build a rootfs using Docker + +Depending on the base OS to build the rootfs guest OS, it is required some +specific programs that probably are not available or installed in the system +that will build the guest image. For this case `rootfs.sh` can use +a Docker\* container to build the rootfs. The following requirements +must be met: + +1. Docker 1.12+ installed. + +2. `runc` is configured as the default runtime. + + To check if `runc` is the default runtime: + + ``` + $ docker info | grep 'Default Runtime: runc' + ``` + + Note: + + This requirement is specific to the Clear Containers runtime. + See [issue](https://github.com/clearcontainers/runtime/issues/828) for + more information. + +3. Export `USE_DOCKER` variable. + + ``` + $ export USE_DOCKER=true + ``` + +4. Use `rootfs.sh`: + + Example: + ``` + $ export USE_DOCKER=true + $ # build guest O/S rootfs based on fedora + $ ./rootfs-builder/rootfs.sh -r "${PWD}/fedora_rootfs" fedora + $ # build image based rootfs created above + $ ./image-builder/image_builder.sh "${PWD}/fedora_rootfs" + ``` + +## Adding support for a new guest OS + +The `rootfs.sh` script will check for immediate sub-directories +containing the following expected files: + +- A `bash(1)` script called `config.sh` + + This represents the specific configuration for ``. It must + provide configuration specific variables for the user to modify as needed. + The `config.sh` file will be loaded before executing `build_rootfs()` to + provide all the needed configuration to the function. + + Path: `rootfs-builder//config.sh`. + +- (OPTIONAL) A `bash(1)` script called `rootfs_lib.sh` + + This file must contain a function called `build_rootfs()`, which must + receive the path to where the rootfs is created, as its first argument. + Normally, this file is needed if a new distro with a special requirement + is needed. This function will override the `build_rootfs()` function in + `scripts/lib.sh`. + + Path: `rootfs-builder//rootfs_lib.sh`. + +### Create template files + +To create a directory with the expected file structure run: + +``` +$ make -f template/Makefile ROOTFS_BASE_NAME=my_new_awesome_rootfs +``` + +After running the previous command, a new directory is created in +`rootfs-builder/my_new_awesome_rootfs/`. + + +To verify the directory can be used to build a rootfs, run `./rootfs.sh -h`. +Running this script shows `my_new_awesome_rootfs` as one of the options for +use. To use the new guest OS, follow the instructions in [Creating a rootfs](#creating-a-rootfs). + +### Modify template files + +After the new directory structure is created: + +- If needed, add configuration variables to + `rootfs-builder/my_new_awesome_rootfs/config.sh`. + +- Implement the stub `build_rootfs()` function from + `rootfs-builder/my_new_awesome_rootfs/rootfs_lib.sh`. + +### Expected rootfs directory content + +After the function `build_rootfs` is called, the script expects the +rootfs directory to contain `/sbin/init` and `/sbin/kata-agent` binaries. + +### Optional - Customize the rootfs + +For particular use cases developers might want to modify the guest OS. + +#### Adding extra packages + +To add additional packages, use one of the following methods: + +- Use the environment variable `EXTRA_PKGS` to provide a list of space-separated + packages to install. + + Note: + + The package names might vary among Linux distributions, the extra + package names must exist in the base OS flavor you use to build the + rootfs from. + + Example: + + ``` + $ EXTRA_PKGS="vim emacs" ./rootfs-builder/rootfs.sh -r ${PWD}/myrootfs fedora + ``` + +- Modify the variable `PACKAGES` in `rootfs-builder//config.sh`. + + This variable specifies the minimal set of packages needed. The + configuration file must use the package names from the distro for which they + were created. + +#### Arbitrary rootfs changes + +Once the rootfs directory is created, you can add and remove files as +needed. Changes affect the files included in the final guest image. diff --git a/tools/osbuilder/rootfs-builder/alpine/Dockerfile.in b/tools/osbuilder/rootfs-builder/alpine/Dockerfile.in new file mode 100644 index 0000000000..dba67a9fe4 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/alpine/Dockerfile.in @@ -0,0 +1,41 @@ +# +# Copyright (c) 2018 HyperHQ Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +From docker.io/golang:@GO_VERSION@-alpine + +RUN apk update && apk add \ + apk-tools-static \ + autoconf \ + automake \ + bash \ + binutils \ + cmake \ + coreutils \ + curl \ + g++ \ + gcc \ + git \ + libc-dev \ + libseccomp \ + libseccomp-dev \ + linux-headers \ + m4 \ + make \ + musl \ + musl-dev \ + tar \ + vim +# alpine doesn't support x86_64-unknown-linux-gnu +# It only support x86_64-unknown-linux-musl. Even worse, +# it doesn't support proc-macro, which is needed for serde_derive +# +# See issue: https://github.com/kata-containers/osbuilder/issues/386 +# -- FIXME +# +# Thus, we cannot build rust agent on alpine +# The way to use alpine is to generate rootfs or build +# go agent to get rootfs and then cp rust agent to rootfs. +# pity.. +# RUN ln -svf /usr/bin/gcc /bin/musl-gcc; ln -svf /usr/bin/g++ /bin/musl-g++ diff --git a/tools/osbuilder/rootfs-builder/alpine/config.sh b/tools/osbuilder/rootfs-builder/alpine/config.sh new file mode 100644 index 0000000000..65baeee0ac --- /dev/null +++ b/tools/osbuilder/rootfs-builder/alpine/config.sh @@ -0,0 +1,26 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +OS_NAME="Alpine" + +OS_VERSION=${OS_VERSION:-latest-stable} + +BASE_PACKAGES="alpine-base" + +# Alpine mirror to use +# See a list of mirrors at http://nl.alpinelinux.org/alpine/MIRRORS.txt +MIRROR=http://dl-5.alpinelinux.org/alpine + +# Mandatory Packages that must be installed +# - iptables: Need by Kata agent +PACKAGES="iptables" + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=kata-agent +# List of zero or more architectures to exclude from build, +# as reported by `uname -m` +ARCH_EXCLUDE_LIST=() + +[ "$SECCOMP" = "yes" ] && PACKAGES+=" libseccomp" || true diff --git a/tools/osbuilder/rootfs-builder/alpine/rootfs_lib.sh b/tools/osbuilder/rootfs-builder/alpine/rootfs_lib.sh new file mode 100644 index 0000000000..dd4c513aa4 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/alpine/rootfs_lib.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Copyright (c) 2018 HyperHQ Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +# - Arguments +# rootfs_dir=$1 +# +# - Optional environment variables +# +# EXTRA_PKGS: Variable to add extra PKGS provided by the user +# +# BIN_AGENT: Name of the Kata-Agent binary +# +# Any other configuration variable for a specific distro must be added +# and documented on its own config.sh +# +# - Expected result +# +# rootfs_dir populated with rootfs pkgs +# It must provide a binary in /sbin/init +build_rootfs() { + # Mandatory + local ROOTFS_DIR=$1 + + # In case of support EXTRA packages, use it to allow + # users add more packages to the base rootfs + local EXTRA_PKGS=${EXTRA_PKGS:-} + + # Populate ROOTFS_DIR + check_root + mkdir -p "${ROOTFS_DIR}" + + /sbin/apk.static \ + -X ${MIRROR}/${OS_VERSION}/main \ + -U \ + --allow-untrusted \ + --root ${ROOTFS_DIR}\ + --initdb add ${BASE_PACKAGES} ${EXTRA_PKGS} ${PACKAGES} + + mkdir -p ${ROOTFS_DIR}{/root,/etc/apk,/proc} + echo "${MIRROR}/${OS_VERSION}/main" > ${ROOTFS_DIR}/etc/apk/repositories +} diff --git a/tools/osbuilder/rootfs-builder/centos/Dockerfile.in b/tools/osbuilder/rootfs-builder/centos/Dockerfile.in new file mode 100644 index 0000000000..c7714ad18c --- /dev/null +++ b/tools/osbuilder/rootfs-builder/centos/Dockerfile.in @@ -0,0 +1,41 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +From docker.io/centos:@OS_VERSION@ + +@SET_PROXY@ + +RUN yum -y update && yum install -y \ + autoconf \ + automake \ + binutils \ + chrony \ + coreutils \ + curl \ + gcc \ + gcc-c++ \ + git \ + glibc-common \ + glibc-devel \ + glibc-headers \ + glibc-static \ + glibc-utils \ + libseccomp \ + libseccomp-devel \ + libstdc++-devel \ + libstdc++-static \ + m4 \ + make \ + sed \ + tar \ + vim \ + which + +# install cmake because centos7's cmake is too old +@INSTALL_CMAKE@ +@INSTALL_MUSL@ +# This will install the proper golang to build Kata components +@INSTALL_GO@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/rootfs-builder/centos/config.sh b/tools/osbuilder/rootfs-builder/centos/config.sh new file mode 100644 index 0000000000..8f80d1f3e9 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/centos/config.sh @@ -0,0 +1,38 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +OS_NAME="Centos" + +OS_VERSION=${OS_VERSION:-7} + +LOG_FILE="/var/log/yum-centos.log" + +MIRROR_LIST="http://mirrorlist.centos.org/?release=${OS_VERSION}&arch=${ARCH}&repo=os&container=container" + +# Aditional Repos +CENTOS_UPDATES_MIRROR_LIST="http://mirrorlist.centos.org/?release=${OS_VERSION}&arch=${ARCH}&repo=updates&container=container" + +CENTOS_EXTRAS_MIRROR_LIST="http://mirrorlist.centos.org/?release=${OS_VERSION}&arch=${ARCH}&repo=extras&container=container" + +CENTOS_PLUS_MIRROR_LIST="http://mirrorlist.centos.org/?release=${OS_VERSION}&arch=${ARCH}&repo=centosplus&container=container" + +GPG_KEY_URL="https://www.centos.org/keys/RPM-GPG-KEY-CentOS-7" + +GPG_KEY_FILE="RPM-GPG-KEY-CentOS-7" + +PACKAGES="iptables chrony" + +#Optional packages: +# systemd: An init system that will start kata-agent if kata-agent +# itself is not configured as init process. +[ "$AGENT_INIT" == "no" ] && PACKAGES+=" systemd" || true + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=systemd +# List of zero or more architectures to exclude from build, +# as reported by `uname -m` +ARCH_EXCLUDE_LIST=() + +[ "$SECCOMP" = "yes" ] && PACKAGES+=" libseccomp" || true diff --git a/tools/osbuilder/rootfs-builder/centos/config_aarch64.sh b/tools/osbuilder/rootfs-builder/centos/config_aarch64.sh new file mode 100644 index 0000000000..b4c6a2677f --- /dev/null +++ b/tools/osbuilder/rootfs-builder/centos/config_aarch64.sh @@ -0,0 +1,18 @@ +# +# Copyright (c) 2018 ARM Limited +# +# SPDX-License-Identifier: Apache-2.0 + +# Base Repos +BASE_URL="http://mirror.centos.org/altarch/${OS_VERSION}/os/${ARCH}/" + +# Additional Repos +CENTOS_UPDATES_URL="http://mirror.centos.org/altarch/${OS_VERSION}/updates/${ARCH}/" + +CENTOS_EXTRAS_URL="http://mirror.centos.org/altarch/${OS_VERSION}/extras/${ARCH}/" + +CENTOS_PLUS_URL="http://mirror.centos.org/altarch/${OS_VERSION}/centosplus/${ARCH}/" + +GPG_KEY_ARCH_URL="http://mirror.centos.org/altarch/7/os/aarch64/RPM-GPG-KEY-CentOS-7-aarch64" + +GPG_KEY_ARCH_FILE="RPM-GPG-KEY-CentOS-7-aarch64" diff --git a/tools/osbuilder/rootfs-builder/centos/config_ppc64le.sh b/tools/osbuilder/rootfs-builder/centos/config_ppc64le.sh new file mode 100644 index 0000000000..455911f1a4 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/centos/config_ppc64le.sh @@ -0,0 +1,18 @@ +# +# Copyright (c) 2018 IBM +# +# SPDX-License-Identifier: Apache-2.0 + +# Base Repos +BASE_URL="http://mirror.centos.org/altarch/${OS_VERSION}/os/${ARCH}/" + +# Additional Repos +CENTOS_UPDATES_URL="http://mirror.centos.org/altarch/${OS_VERSION}/updates/${ARCH}/" + +CENTOS_EXTRAS_URL="http://mirror.centos.org/altarch/${OS_VERSION}/extras/${ARCH}/" + +CENTOS_PLUS_URL="http://mirror.centos.org/altarch/${OS_VERSION}/centosplus/${ARCH}/" + +GPG_KEY_ARCH_URL="http://mirror.centos.org/altarch/7/os/ppc64le/RPM-GPG-KEY-CentOS-SIG-AltArch-7-ppc64le" + +GPG_KEY_ARCH_FILE="RPM-GPG-KEY-CentOS-SIG-AltArch-7-ppc64le" diff --git a/tools/osbuilder/rootfs-builder/clearlinux/Dockerfile.in b/tools/osbuilder/rootfs-builder/clearlinux/Dockerfile.in new file mode 100644 index 0000000000..949812541e --- /dev/null +++ b/tools/osbuilder/rootfs-builder/clearlinux/Dockerfile.in @@ -0,0 +1,43 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +From docker.io/fedora:30 + +@SET_PROXY@ + +RUN dnf -y update && dnf install -y \ + autoconf \ + automake \ + binutils \ + chrony \ + cmake \ + coreutils \ + curl \ + curl \ + gcc \ + gcc-c++ \ + git \ + glibc-common \ + glibc-devel \ + glibc-headers \ + glibc-static \ + glibc-utils \ + libseccomp \ + libseccomp-devel \ + libstdc++-devel \ + libstdc++-static \ + m4 \ + make \ + pkgconfig \ + sed \ + systemd \ + tar \ + vim \ + which + +# This will install the proper golang to build Kata components +@INSTALL_MUSL@ +@INSTALL_GO@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/rootfs-builder/clearlinux/config.sh b/tools/osbuilder/rootfs-builder/clearlinux/config.sh new file mode 100644 index 0000000000..c26cc049a4 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/clearlinux/config.sh @@ -0,0 +1,31 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +OS_NAME="Clear" +REPO_NAME="clear" + +OS_VERSION=${OS_VERSION:-latest} + +clr_url="https://download.clearlinux.org" + +# resolve version +[ "${OS_VERSION}" = "latest" ] && OS_VERSION=$(curl -sL "${clr_url}/latest") + +BASE_URL="${clr_url}/releases/${OS_VERSION}/${REPO_NAME}/${ARCH}/os/" + +PACKAGES="libudev0-shim kmod-bin" + +#Optional packages: +# systemd: An init system that will start kata-agent if kata-agent +# itself is not configured as init process. +[ "$AGENT_INIT" == "no" ] && PACKAGES+=" systemd chrony iptables-bin util-linux-bin" || true + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=systemd +# List of zero or more architectures to exclude from build, +# as reported by `uname -m` +ARCH_EXCLUDE_LIST=( aarch64 ppc64le s390x ) + +[ "$SECCOMP" = "yes" ] && PACKAGES+=" libseccomp" || true diff --git a/tools/osbuilder/rootfs-builder/debian/Dockerfile-aarch64.in b/tools/osbuilder/rootfs-builder/debian/Dockerfile-aarch64.in new file mode 100644 index 0000000000..e119d3599f --- /dev/null +++ b/tools/osbuilder/rootfs-builder/debian/Dockerfile-aarch64.in @@ -0,0 +1,35 @@ +# +# Copyright (c) 2020 ARM Limited +# +# SPDX-License-Identifier: Apache-2.0 + +# NOTE: OS_VERSION is set according to config.sh +from docker.io/debian:@OS_VERSION@ + +# RUN commands +RUN apt-get update && apt-get install -y \ + autoconf \ + automake \ + binutils \ + build-essential \ + chrony \ + cmake \ + coreutils \ + curl \ + debianutils \ + debootstrap \ + g++ \ + gcc \ + git \ + libc-dev \ + libstdc++-6-dev \ + m4 \ + make \ + sed \ + systemd \ + tar \ + vim +# This will install the proper golang to build Kata components +@INSTALL_GO@ +@INSTALL_MUSL@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/rootfs-builder/debian/Dockerfile.in b/tools/osbuilder/rootfs-builder/debian/Dockerfile.in new file mode 100644 index 0000000000..88629aefae --- /dev/null +++ b/tools/osbuilder/rootfs-builder/debian/Dockerfile.in @@ -0,0 +1,41 @@ +# +# Copyright (c) 2018 SUSE +# +# SPDX-License-Identifier: Apache-2.0 + +# NOTE: OS_VERSION is set according to config.sh +from docker.io/debian:@OS_VERSION@ + +# RUN commands +RUN apt-get update && apt-get --no-install-recommends install -y \ + apt-utils \ + autoconf \ + automake \ + binutils \ + build-essential \ + ca-certificates \ + chrony \ + cmake \ + coreutils \ + curl \ + debianutils \ + debootstrap \ + g++ \ + gcc \ + git \ + libc-dev \ + libstdc++-6-dev \ + m4 \ + make \ + musl \ + musl-dev \ + musl-tools \ + sed \ + systemd \ + tar \ + vim \ + wget + +# This will install the proper golang to build Kata components +@INSTALL_GO@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/rootfs-builder/debian/config.sh b/tools/osbuilder/rootfs-builder/debian/config.sh new file mode 100644 index 0000000000..38e2ee5b91 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/debian/config.sh @@ -0,0 +1,20 @@ +# +# Copyright (c) 2018 SUSE +# +# SPDX-License-Identifier: Apache-2.0 + +OS_VERSION=${OS_VERSION:-9.5} + +# Set OS_NAME to the desired debian "codename" +OS_NAME=${OS_NAME:-"stretch"} + +PACKAGES="systemd iptables init chrony kmod" + +# NOTE: Re-using ubuntu rootfs configuration, see 'ubuntu' folder for full content. +source $script_dir/ubuntu/$CONFIG_SH + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=systemd +# List of zero or more architectures to exclude from build, +# as reported by `uname -m` +ARCH_EXCLUDE_LIST=() diff --git a/tools/osbuilder/rootfs-builder/debian/rootfs_lib.sh b/tools/osbuilder/rootfs-builder/debian/rootfs_lib.sh new file mode 100644 index 0000000000..f6aae26364 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/debian/rootfs_lib.sh @@ -0,0 +1,7 @@ +# +# Copyright (c) 2018 SUSE +# +# SPDX-License-Identifier: Apache-2.0 + +# NOTE: Re-using ubuntu rootfs lib, see 'ubuntu' folder for details. +source ${script_dir}/ubuntu/$LIB_SH diff --git a/tools/osbuilder/rootfs-builder/euleros/Dockerfile.in b/tools/osbuilder/rootfs-builder/euleros/Dockerfile.in new file mode 100644 index 0000000000..b57dece7d1 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/euleros/Dockerfile.in @@ -0,0 +1,46 @@ +# +# Copyright (C) 2018 Huawei Technologies Co., Ltd +# +# SPDX-License-Identifier: Apache-2.0 + +FROM docker.io/euleros:@OS_VERSION@ + +@SET_PROXY@ + +RUN yum -y update && yum install -y \ + autoconf \ + automake \ + binutils \ + chrony \ + coreutils \ + curl \ + gcc \ + gcc-c++ \ + git \ + glibc-common \ + glibc-devel \ + glibc-headers \ + glibc-static \ + glibc-utils \ + libstdc++-devel \ + libstdc++-static \ + m4 \ + make \ + sed \ + tar \ + vim \ + which \ + yum + +# This will install the proper golang to build Kata components +@INSTALL_GO@ + +# several problems prevent us from building rust agent on euleros +# 1. There is no libstdc++.a. copy one from somewhere get through +# compilation +# 2. The kernel (3.10.x) is too old, kernel-headers pacakge +# has no vm_socket.h because kernel has no vsock support or +# vsock header files + +# We will disable rust agent build in rootfs.sh for euleros +# and alpine(musl cannot support proc-macro) diff --git a/tools/osbuilder/rootfs-builder/euleros/RPM-GPG-KEY-EulerOS b/tools/osbuilder/rootfs-builder/euleros/RPM-GPG-KEY-EulerOS new file mode 100644 index 0000000000..39495478dd --- /dev/null +++ b/tools/osbuilder/rootfs-builder/euleros/RPM-GPG-KEY-EulerOS @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBFhFFc8BEADu77vsD7rA1zCTreI9Ex9dIbWWR0Ntu4e7OL+VSIxXserWron2 +kTHagPIrDGtFqWTQgbt4tpjJ8vOAMzCADYq2eNRbEbUL/TOGfYk5Lgfo0P7F5Slr +dXNow2HrZhxehTwRSvseQg9Yrx2LVXDgr8wAMLldnkCSa0iyAE90ehDLOUaf2Lal +c99p+4tw8GhWP7C41pX4ywLrJ1FXodFTpg+I7p9EW5zt5mZhwX7NkhdoISnNAA6L +R5NA+6G8rCC1fdTGfqYPfNGrO9DBSZNfunWZsN+kYo4ac3GbZkdnh3LA2YCW4yiA +u5AoPv1UIkFMLh0KoJDxOORMkxI++3qFAIzShtMRAQencsM85bzdXNmk3VE+nY9V +J0BHCLMELtr/o6b+e5ak3qcG1sMFBEMn367/k6suIpTF5sEszQScWeqbhdeFmXt6 +mur2z6zDwwa5Y4n0x9Lsz50PxgkDrHXxeoLO5ByE8iTJqxhYSl0hb/bhSmBaYXnW +JiqtoLbYW/isgZ8OW414P2ZUwgByA9O4Tso37oEU69ycrxFVI63M5xUGkchI+HBo +VB9XZ7QzjU8SGoelj5YtjV7og974dcXC4NwUTnhJW3pd3MfiA3C96voCN/ozjzpg +uJGg0vzuTUcHAIMhujWPWCb0YN6fr5z+7Et8yqPv4qt3fgaxdVO5qQds1wARAQAB +tC5FdWxlck9TIChFdWxlck9TIDIuMCBTUDIpIDxFdWxlck9TQGh1YXdlaS5jb20+ +iQI/BBMBAgApBQJYRRXPAhsDBQkJZgGABwsJCAcDAgEGFQgCCQoLBBYCAwECHgEC +F4AACgkQYAMXvDgdesNPCxAAh7huw08/oFHpCSN9dYd/YlFfCs/+wb2KUqZZ2yIK +SSpmRmQiQdJRUiJly69WZL4H2NYCw3MQiV8Q433err3iQXMjumfl5hq2KplMgsAQ +sraOreJPeN1687rzEV9eDjuKV7btd2VaSyiMIaAFaWjoxl6E77x8ifNbXcnTHk+5 +39BCRn3WsSXbQKWolFEvwNr/SYzGIIdtmrlZSog/vAKPqzTsJDj/Qsf/0Uec1iCX +6pnZwMrQTlc+nnnAp9bMVla39uWGwyhhicTsokElm/4wD+OaLF2xz3gWk3l3fHjh +V8PtzhQxpHlyqR7pOvG5eun9VsBeWwH6TcHU1B+cPi4SmQcflvJCV/XCTTcK6Z1i +/35cmZdwCoDnM+Aadfywfoaliy5rnsvvMSljI+hw8gX3NACIDd2RBPmER3wknZw6 +bIpm0vtlaG1fcCio0kFo9CplLYbYHtx9Y7Icln3O3keODlR+rc8HI5X0YPkLa9Fs +mqP0fN2PGcEPm7CjoEdFdfOJn+1TvR7T1cnBiso5hLcMPtX8b6vzvIrFy5OKq90N +LYjgdn8LMmE6Gi/LA6yEBB958vGS5kAQI3HvCmw9vBeGdVZ+QXjmeVN6Vp9bEnBS +3oZbUXzo3CpeGxvj7+8s8j6MMvDLPLIdxXWi1ZTJkZFa+ElvZMG34SI/kSHHdSSO +gRG5Ag0EWEUVzwEQANBn+RzOAOl8OVPBtmDRIC/G7yssy7Q3ZGWUDIxs2NNk2oBH +9RsCm+vYeQkScloed+Cv6dkQvCPiFk+VtlLeSl0ugmvjNjZknuMhbgiC1ObR2SmV +uNpT3qMaNQQBJg0tJGU/1hLHDqjj2TGvj+WJDfzRoVie1dHq6bnogOErEXvKGmNZ +/cDuvmeURmFqx/+cwim2QFc95hcylBXRhnTnGblgxjzYXnXbIMvtCNz3Nnd1yT3P +9Z+h7Mwk746UEK2R1EgpVzZa9YF/mg2NRwBFuuJ4yP0MxmzP1AMgqQSp7XrMP6KG +6RbmDymrTHFTkP/lI3qZ1bgNB64bq/Eq1J3qgukEDN8JZKMiG+/vAg3lkPQwn3Uy +8IfBCqVrF0/dg+kJesgEMs6T+CsINWQ/SEPYHT/6LGytr+4MgDVqI1wxII8gBZzk +FHohleNRWvKKGLphECO7NwgrDFwWlIsT46d1Hga0uHNDSg1mczU2swYHD7/j1HQE +McByTVuzUjT/eAxmbN+DZ4cGBccqMP8RkZfBpalhB5lyjnIN2tMJ3y3yZrpmJkU1 +LaetdFqwycMmV7Mmi2dEdqumnmKhSZqyJ1ShuSm9pEBxahwJGdhtC6Id7iwzZ3uJ +53nhO7hvGC0gt3w0frX0TcvT7aFa4ZsgaJxUJy0MKDPZmv/3hYKpH/QkLiu7ABEB +AAGJAiUEGAECAA8FAlhFFc8CGwwFCQlmAYAACgkQYAMXvDgdesO+fw//bQImNhW0 +ZwG5FG7oP+KPgmma2+N/JnzemqEUzjRTIiEN4LCj8qvJ/aKYZJkfUcKvP4kpVW66 ++tlJ11Ie9Bnkqm3GdT1nkWDghzTK7/x6ktRwyuowmEYh01fW3bybB0RcQOJzGnMK +umnNzd4VUdMGwdbg/sQnKc6lMU9+hz/tCOU9Ok6Ps384gRXjmRQ+J9EFHq14kXtP +Xy584MD1+OBsPwlMViAAjV9L3pxtS1JoFplNPYogbBOKHdImS3dNOMLwV3dHAf1d +l0MqgMEabLBQusx2q7CUw4xBi5EJJtnos9bJvGSCplDyjlshDiY7wxcFLLb90VWs +TnJVbDswCjsdVi5x8eyPplygGxgt9Qg2XNYN5EgN9MLbmbC7Mi7oRf1E7QMLuuQ+ ++lkTb1rAe4YewwwAZHao4zGJelNXmSPN8u8s/zUrnFKG78qjLDZW9kGvkFpElOPj +KkgsSaTn8kbxWoyR9wKW56onTos6eMfhItLCFy5/oAD3sIp5aCsATuJZPSAtDKxw +1jzQRx4KOOYYrsS1qMd7gG151/QM15E56gdi+6gaeLcz8YQ2zcvxg5eabUDKp+bP +I47NsT6rLAhV5mTB0NneC//Yng7JJ0q0jkiJu49BQ1if6Pz8txDxBs4U3mvCw2rA +qSxRE/XMoebNx2CFQwFp7izDHwuG6uRRUQQ= +=3beT +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tools/osbuilder/rootfs-builder/euleros/config.sh b/tools/osbuilder/rootfs-builder/euleros/config.sh new file mode 100644 index 0000000000..d522e844b3 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/euleros/config.sh @@ -0,0 +1,29 @@ +# +# Copyright (C) 2018 Huawei Technologies Co., Ltd +# +# SPDX-License-Identifier: Apache-2.0 +OS_NAME="EulerOS" + +OS_VERSION=${OS_VERSION:-2.2} + +BASE_URL="http://developer.huawei.com/ict/site-euleros/euleros/repo/yum/${OS_VERSION}/os/${ARCH}/" + +GPG_KEY_FILE="RPM-GPG-KEY-EulerOS" + +PACKAGES="iptables chrony" + +#Optional packages: +# systemd: An init system that will start kata-agent if kata-agent +# itself is not configured as init process. +[ "$AGENT_INIT" == "no" ] && PACKAGES+=" systemd" || true + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=systemd +# List of zero or more architectures to exclude from build, +# as reported by `uname -m` +ARCH_EXCLUDE_LIST=( aarch64 ppc64le s390x ) +# Allow the build to fail without generating an error. +# For more info see: https://github.com/kata-containers/osbuilder/issues/190 +BUILD_CAN_FAIL=1 + +[ "$SECCOMP" = "yes" ] && PACKAGES+=" libseccomp" || true diff --git a/tools/osbuilder/rootfs-builder/fedora/Dockerfile.in b/tools/osbuilder/rootfs-builder/fedora/Dockerfile.in new file mode 100644 index 0000000000..b67203341f --- /dev/null +++ b/tools/osbuilder/rootfs-builder/fedora/Dockerfile.in @@ -0,0 +1,43 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +From docker.io/fedora:@OS_VERSION@ + +@SET_PROXY@ + +RUN dnf -y update && dnf install -y \ + autoconf \ + automake \ + binutils \ + chrony \ + cmake \ + coreutils \ + curl \ + gcc \ + gcc-c++ \ + git \ + glibc-common \ + glibc-devel \ + glibc-headers \ + glibc-static \ + glibc-utils \ + libseccomp \ + libseccomp-devel \ + libstdc++-devel \ + libstdc++-static \ + m4 \ + make \ + pkgconfig \ + redhat-release \ + sed \ + systemd \ + tar \ + vim \ + which + +# This will install the proper golang to build Kata components +@INSTALL_MUSL@ +@INSTALL_GO@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/rootfs-builder/fedora/config.sh b/tools/osbuilder/rootfs-builder/fedora/config.sh new file mode 100644 index 0000000000..4aff698d79 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/fedora/config.sh @@ -0,0 +1,23 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +OS_NAME="Fedora" + +OS_VERSION=${OS_VERSION:-30} + +MIRROR_LIST="https://mirrors.fedoraproject.org/metalink?repo=fedora-${OS_VERSION}&arch=\$basearch" + +PACKAGES="iptables chrony" + +#Optional packages: +# systemd: An init system that will start kata-agent if kata-agent +# itself is not configured as init process. +[ "$AGENT_INIT" == "no" ] && PACKAGES+=" systemd" || true + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=systemd +ARCH_EXCLUDE_LIST=() + +[ "$SECCOMP" = "yes" ] && PACKAGES+=" libseccomp" || true diff --git a/tools/osbuilder/rootfs-builder/fedora/config_aarch64.sh b/tools/osbuilder/rootfs-builder/fedora/config_aarch64.sh new file mode 100644 index 0000000000..42126f34c5 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/fedora/config_aarch64.sh @@ -0,0 +1,10 @@ +# +# Copyright (c) 2019 ARM Limited +# +# SPDX-License-Identifier: Apache-2.0 + +# image busybox will fail on fedora 30 rootfs image +# see https://github.com/kata-containers/osbuilder/issues/334 for detailed info +OS_VERSION="29" + +MIRROR_LIST="https://mirrors.fedoraproject.org/metalink?repo=fedora-${OS_VERSION}&arch=\$basearch" diff --git a/tools/osbuilder/rootfs-builder/rootfs.sh b/tools/osbuilder/rootfs-builder/rootfs.sh new file mode 100755 index 0000000000..3fdb5bd2da --- /dev/null +++ b/tools/osbuilder/rootfs-builder/rootfs.sh @@ -0,0 +1,686 @@ +#!/bin/bash +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +set -o errexit +set -o pipefail +set -o errtrace + +[ -n "$DEBUG" ] && set -x + +script_name="${0##*/}" +script_dir="$(dirname $(readlink -f $0))" +AGENT_VERSION=${AGENT_VERSION:-} +GO_AGENT_PKG=${GO_AGENT_PKG:-github.com/kata-containers/agent} +RUST_AGENT_PKG=${RUST_AGENT_PKG:-github.com/kata-containers/kata-containers} +RUST_AGENT=${RUST_AGENT:-no} +RUST_VERSION="null" +CMAKE_VERSION=${CMAKE_VERSION:-"null"} +MUSL_VERSION=${MUSL_VERSION:-"null"} +AGENT_BIN=${AGENT_BIN:-kata-agent} +AGENT_INIT=${AGENT_INIT:-no} +KERNEL_MODULES_DIR=${KERNEL_MODULES_DIR:-""} +OSBUILDER_VERSION="unknown" +DOCKER_RUNTIME=${DOCKER_RUNTIME:-runc} +GO_VERSION="null" +export GOPATH=${GOPATH:-${HOME}/go} + +lib_file="${script_dir}/../scripts/lib.sh" +source "$lib_file" + +handle_error() { + local exit_code="${?}" + local line_number="${1:-}" + echo "Failed at $line_number: ${BASH_COMMAND}" + exit "${exit_code}" + +} +trap 'handle_error $LINENO' ERR + +# Default architecture +ARCH=$(uname -m) + +# distro-specific config file +typeset -r CONFIG_SH="config.sh" + +# optional arch-specific config file +typeset -r CONFIG_ARCH_SH="config_${ARCH}.sh" + +# Name of an optional distro-specific file which, if it exists, must implement the +# build_rootfs() function. +typeset -r LIB_SH="rootfs_lib.sh" + +# rootfs distro name specified by the user +typeset distro= + +# Absolute path to the rootfs root folder +typeset ROOTFS_DIR + +# Absolute path in the rootfs to the "init" executable / symlink. +# Typically something like "${ROOTFS_DIR}/init +typeset init= + +#$1: Error code if want to exit different to 0 +usage() +{ + error="${1:-0}" + cat < Specify the agent version. Overrides the AGENT_VERSION + environment variable. + -h Show this help message. + -l List the supported Linux distributions and exit immediately. + -o Specify the version of osbuilder to embed in the rootfs + yaml description. + -r Specify the rootfs base directory. Overrides the ROOTFS_DIR + environment variable. + -t DISTRO Print the test configuration for DISTRO and exit + immediately. + +Environment Variables: +AGENT_BIN Name of the agent binary (used when running sanity checks on + the rootfs). + Default value: ${AGENT_BIN} + +AGENT_INIT When set to "yes", use ${AGENT_BIN} as init process in place + of systemd. + Default value: no + +RUST_AGENT When set to "yes", build kata-agent from kata-rust-agent instead of go agent + Default value: "no" + +RUST_AGENT_PKG URL of the Git repository hosting the agent package. + Default value: ${RUST_AGENT_PKG} + +AGENT_VERSION Version of the agent to include in the rootfs. + Default value: ${AGENT_VERSION:-} + +AGENT_SOURCE_BIN Path to the directory of agent binary. + If set, use the binary as agent but not build agent package. + Default value: + +DISTRO_REPO Use host repositories to install guest packages. + Default value: + +GO_AGENT_PKG URL of the Git repository hosting the agent package. + Default value: ${GO_AGENT_PKG} + +GRACEFUL_EXIT If set, and if the DISTRO configuration specifies a + non-empty BUILD_CAN_FAIL variable, do not return with an + error code in case any of the build step fails. + This is used when running CI jobs, to tolerate failures for + specific distributions. + Default value: + +KERNEL_MODULES_DIR Path to a directory containing kernel modules to include in + the rootfs. + Default value: + +ROOTFS_DIR Path to the directory that is populated with the rootfs. + Default value: <${script_name} path>/rootfs- + +USE_DOCKER If set, build the rootfs inside a container (requires + Docker). + Default value: + +USE_PODMAN If set and USE_DOCKER not set, then build the rootfs inside + a podman container (requires podman). + Default value: + +DOCKER_RUNTIME Docker runtime to use when USE_DOCKER is set. + Default value: runc + +Refer to the Platform-OS Compatibility Matrix for more details on the supported +architectures: +https://github.com/kata-containers/osbuilder#platform-distro-compatibility-matrix + +EOT +exit "${error}" +} + +get_distros() { + cdirs=$(find "${script_dir}" -maxdepth 1 -type d) + find ${cdirs} -maxdepth 1 -name "${CONFIG_SH}" -printf '%H\n' | while read dir; do + basename "${dir}" + done +} + +get_test_config() { + local -r distro="$1" + [ -z "$distro" ] && die "No distro name specified" + + local config="${script_dir}/${distro}/config.sh" + source ${config} + + echo -e "INIT_PROCESS:\t\t$INIT_PROCESS" + echo -e "ARCH_EXCLUDE_LIST:\t\t${ARCH_EXCLUDE_LIST[@]}" +} + +check_function_exist() +{ + function_name="$1" + [ "$(type -t ${function_name})" == "function" ] || die "${function_name} function was not defined" +} + +docker_extra_args() +{ + local args="" + + case "$1" in + ubuntu | debian) + # Requred to chroot + args+=" --cap-add SYS_CHROOT" + # debootstrap needs to create device nodes to properly function + args+=" --cap-add MKNOD" + ;& + suse) + # Required to mount inside a container + args+=" --cap-add SYS_ADMIN" + # When AppArmor is enabled, mounting inside a container is blocked with docker-default profile. + # See https://github.com/moby/moby/issues/16429 + args+=" --security-opt apparmor=unconfined" + ;; + *) + ;; + esac + + echo "$args" +} + +setup_agent_init() +{ + agent_bin="$1" + init_bin="$2" + + [ -z "$agent_bin" ] && die "need agent binary path" + [ -z "$init_bin" ] && die "need init bin path" + + info "Install $agent_bin as init process" + mv -f "${agent_bin}" ${init_bin} + OK "Agent is installed as init process" +} + +copy_kernel_modules() +{ + local module_dir="$1" + local rootfs_dir="$2" + + [ -z "$module_dir" ] && die "need module directory" + [ -z "$rootfs_dir" ] && die "need rootfs directory" + + local dest_dir="${rootfs_dir}/lib/modules" + + info "Copy kernel modules from ${KERNEL_MODULES_DIR}" + mkdir -p "${dest_dir}" + cp -a "${KERNEL_MODULES_DIR}" "${dest_dir}/" + OK "Kernel modules copied" +} + +error_handler() +{ + [ "$?" -eq 0 ] && return + + if [ -n "$GRACEFUL_EXIT" ] && [ -n "$BUILD_CAN_FAIL" ]; then + info "Detected a build error, but $distro is allowed to fail (BUILD_CAN_FAIL specified), so exiting sucessfully" + touch "$(dirname ${ROOTFS_DIR})/${distro}_fail" + exit 0 + fi +} + +# Compares two SEMVER-style versions passed as arguments, up to the MINOR version +# number. +# Returns a zero exit code if the version specified by the first argument is +# older OR equal than / to the version in the second argument, non-zero exit +# code otherwise. +compare_versions() +{ + typeset -i -a v1=($(echo "$1" | awk 'BEGIN {FS = "."} {print $1" "$2}')) + typeset -i -a v2=($(echo "$2" | awk 'BEGIN {FS = "."} {print $1" "$2}')) + + # Sanity check: first version can't be all zero + [ "${v1[0]}" -eq "0" ] && \ + [ "${v1[1]}" -eq "0" ] && \ + die "Failed to parse version number" + + # Major + [ "${v1[0]}" -gt "${v2[0]}" ] && { false; return; } + + # Minor + [ "${v1[0]}" -eq "${v2[0]}" ] && \ + [ "${v1[1]}" -gt "${v2[1]}" ] && { false; return; } + + true +} + +check_env_variables() +{ + # Fetch the first element from GOPATH as working directory + # as go get only works against the first item in the GOPATH + [ -z "$GOPATH" ] && die "GOPATH not set" + GOPATH_LOCAL="${GOPATH%%:*}" + + [ "$AGENT_INIT" == "yes" -o "$AGENT_INIT" == "no" ] || die "AGENT_INIT($AGENT_INIT) is invalid (must be yes or no)" + + if [ -z "${AGENT_SOURCE_BIN}" ]; then + [ "$RUST_AGENT" == "yes" -o "$RUST_AGENT" == "no" ] || die "RUST_AGENT($RUST_AGENT) is invalid (must be yes or no)" + fi + + [ -n "${KERNEL_MODULES_DIR}" ] && [ ! -d "${KERNEL_MODULES_DIR}" ] && die "KERNEL_MODULES_DIR defined but is not an existing directory" + + [ -n "${OSBUILDER_VERSION}" ] || die "need osbuilder version" +} + +# Builds a rootfs based on the distro name provided as argument +build_rootfs_distro() +{ + [ -n "${distro}" ] || usage 1 + distro_config_dir="${script_dir}/${distro}" + + # Source config.sh from distro + rootfs_config="${distro_config_dir}/${CONFIG_SH}" + source "${rootfs_config}" + + # Source arch-specific config file + rootfs_arch_config="${distro_config_dir}/${CONFIG_ARCH_SH}" + if [ -f "${rootfs_arch_config}" ]; then + source "${rootfs_arch_config}" + fi + + [ -d "${distro_config_dir}" ] || die "Not found configuration directory ${distro_config_dir}" + + if [ -z "$ROOTFS_DIR" ]; then + ROOTFS_DIR="${script_dir}/rootfs-${OS_NAME}" + fi + + if [ -e "${distro_config_dir}/${LIB_SH}" ];then + rootfs_lib="${distro_config_dir}/${LIB_SH}" + info "rootfs_lib.sh file found. Loading content" + source "${rootfs_lib}" + fi + + CONFIG_DIR=${distro_config_dir} + check_function_exist "build_rootfs" + + if [ -z "$INSIDE_CONTAINER" ] ; then + # Capture errors, but only outside of the docker container + trap error_handler ERR + fi + + mkdir -p ${ROOTFS_DIR} + + detect_go_version || + die "Could not detect the required Go version for AGENT_VERSION='${AGENT_VERSION:-master}'." + + echo "Required Go version: $GO_VERSION" + + # need to detect rustc's version too? + detect_rust_version || + die "Could not detect the required rust version for AGENT_VERSION='${AGENT_VERSION:-master}'." + + echo "Required rust version: $RUST_VERSION" + + detect_cmake_version || + die "Could not detect the required cmake version for AGENT_VERSION='${AGENT_VERSION:-master}'." + + echo "Required cmake version: $CMAKE_VERSION" + + detect_musl_version || + die "Could not detect the required musl version for AGENT_VERSION='${AGENT_VERSION:-master}'." + + echo "Required musl version: $MUSL_VERSION" + + if [ -z "${USE_DOCKER}" ] && [ -z "${USE_PODMAN}" ]; then + #Generate an error if the local Go version is too old + foundVersion=$(go version | sed -E "s/^.+([0-9]+\.[0-9]+\.[0-9]+).*$/\1/g") + + compare_versions "${GO_VERSION}" "${foundVersion}" || \ + die "Your Go version ${foundVersion} is older than the minimum expected Go version ${GO_VERSION}" + + if [ "${RUST_AGENT}" == "yes" ]; then + source "${HOME}/.cargo/env" + foundVersion=$(rustc --version | sed -E "s/^.+([0-9]+\.[0-9]+\.[0-9]+).*$/\1/g") + + compare_versions "${RUST_VERSION}" "${foundVersion}" || \ + die "Your rust version ${foundVersion} is older than the minimum expected rust version ${RUST_VERSION}" + + foundVersion=$(cmake --version | grep "[0-9]\+.[0-9]\+.[0-9]\+" | sed -E "s/^.+([0-9]+\.[0-9]+\.[0-9]+).*$/\1/g") + + fi + else + if [ -n "${USE_DOCKER}" ]; then + container_engine="docker" + elif [ -n "${USE_PODMAN}" ]; then + container_engine="podman" + fi + + image_name="${distro}-rootfs-osbuilder" + + # setup to install go or rust here + generate_dockerfile "${distro_config_dir}" + "$container_engine" build \ + --build-arg http_proxy="${http_proxy}" \ + --build-arg https_proxy="${https_proxy}" \ + -t "${image_name}" "${distro_config_dir}" + + # fake mapping if KERNEL_MODULES_DIR is unset + kernel_mod_dir=${KERNEL_MODULES_DIR:-${ROOTFS_DIR}} + + docker_run_args="" + docker_run_args+=" --rm" + docker_run_args+=" --runtime ${DOCKER_RUNTIME}" + + if [ -z "${AGENT_SOURCE_BIN}" ] ; then + if [ "$RUST_AGENT" == "no" ]; then + docker_run_args+=" --env GO_AGENT_PKG=${GO_AGENT_PKG}" + else + docker_run_args+=" --env RUST_AGENT_PKG=${RUST_AGENT_PKG}" + fi + docker_run_args+=" --env RUST_AGENT=${RUST_AGENT} -v ${GOPATH_LOCAL}:${GOPATH_LOCAL} --env GOPATH=${GOPATH_LOCAL}" + else + docker_run_args+=" --env AGENT_SOURCE_BIN=${AGENT_SOURCE_BIN}" + docker_run_args+=" -v ${AGENT_SOURCE_BIN}:${AGENT_SOURCE_BIN}" + fi + + docker_run_args+=" $(docker_extra_args $distro)" + + # Relabel volumes so SELinux allows access (see docker-run(1)) + if command -v selinuxenabled > /dev/null && selinuxenabled ; then + SRC_VOL=("${GOPATH_LOCAL}") + + for volume_dir in "${script_dir}" \ + "${ROOTFS_DIR}" \ + "${script_dir}/../scripts" \ + "${kernel_mod_dir}" \ + "${SRC_VOL[@]}"; do + chcon -Rt svirt_sandbox_file_t "$volume_dir" + done + fi + + #Make sure we use a compatible runtime to build rootfs + # In case Clear Containers Runtime is installed we dont want to hit issue: + #https://github.com/clearcontainers/runtime/issues/828 + "$container_engine" run \ + --env https_proxy="${https_proxy}" \ + --env http_proxy="${http_proxy}" \ + --env AGENT_VERSION="${AGENT_VERSION}" \ + --env ROOTFS_DIR="/rootfs" \ + --env AGENT_BIN="${AGENT_BIN}" \ + --env AGENT_INIT="${AGENT_INIT}" \ + --env KERNEL_MODULES_DIR="${KERNEL_MODULES_DIR}" \ + --env EXTRA_PKGS="${EXTRA_PKGS}" \ + --env OSBUILDER_VERSION="${OSBUILDER_VERSION}" \ + --env INSIDE_CONTAINER=1 \ + --env SECCOMP="${SECCOMP}" \ + --env DEBUG="${DEBUG}" \ + --env HOME="/root" \ + -v "${script_dir}":"/osbuilder" \ + -v "${ROOTFS_DIR}":"/rootfs" \ + -v "${script_dir}/../scripts":"/scripts" \ + -v "${kernel_mod_dir}":"${kernel_mod_dir}" \ + $docker_run_args \ + ${image_name} \ + bash /osbuilder/rootfs.sh "${distro}" + + exit $? + fi + + build_rootfs ${ROOTFS_DIR} +} + +# Used to create a minimal directory tree where the agent can be instaleld. +# This is used when a distro is not specified. +prepare_overlay() +{ + pushd "${ROOTFS_DIR}" > /dev/null + mkdir -p ./etc ./lib/systemd ./sbin ./var + + # This symlink hacking is mostly to make later rootfs + # validation work correctly for the dracut case. + # We skip this if /sbin/init exists in the rootfs, meaning + # we were passed a pre-populated rootfs directory + if [ ! -e ./sbin/init ]; then + ln -sf ./usr/lib/systemd/systemd ./init + ln -sf ../../init ./lib/systemd/systemd + ln -sf ../init ./sbin/init + fi + + # Kata systemd unit file + mkdir -p ./etc/systemd/system/basic.target.wants/ + ln -sf /usr/lib/systemd/system/kata-containers.target ./etc/systemd/system/basic.target.wants/kata-containers.target + popd > /dev/null +} + +# Setup an existing rootfs directory, based on the OPTIONAL distro name +# provided as argument +setup_rootfs() +{ + info "Create symlink to /tmp in /var to create private temporal directories with systemd" + pushd "${ROOTFS_DIR}" >> /dev/null + if [ "$PWD" != "/" ] ; then + rm -rf ./var/cache/ ./var/lib ./var/log ./var/tmp + fi + + ln -s ../tmp ./var/ + + # For some distros tmp.mount may not be installed by default in systemd paths + if ! [ -f "./etc/systemd/system/tmp.mount" ] && \ + ! [ -f "./usr/lib/systemd/system/tmp.mount" ] && + [ "$AGENT_INIT" != "yes" ]; then + local unitFile="./etc/systemd/system/tmp.mount" + info "Install tmp.mount in ./etc/systemd/system" + mkdir -p `dirname "$unitFile"` + cp ./usr/share/systemd/tmp.mount "$unitFile" || cat > "$unitFile" << EOT +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Temporary Directory (/tmp) +Documentation=man:hier(7) +Documentation=https://www.freedesktop.org/wiki/Software/systemd/APIFileSystems +ConditionPathIsSymbolicLink=!/tmp +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target +After=swap.target + +[Mount] +What=tmpfs +Where=/tmp +Type=tmpfs +Options=mode=1777,strictatime,nosuid,nodev +EOT + fi + + popd >> /dev/null + + [ -n "${KERNEL_MODULES_DIR}" ] && copy_kernel_modules ${KERNEL_MODULES_DIR} ${ROOTFS_DIR} + + info "Create ${ROOTFS_DIR}/etc" + mkdir -p "${ROOTFS_DIR}/etc" + + case "${distro}" in + "ubuntu" | "debian") + echo "I am ubuntu or debian" + chrony_conf_file="${ROOTFS_DIR}/etc/chrony/chrony.conf" + chrony_systemd_service="${ROOTFS_DIR}/lib/systemd/system/chrony.service" + ;; + *) + chrony_conf_file="${ROOTFS_DIR}/etc/chrony.conf" + chrony_systemd_service="${ROOTFS_DIR}/usr/lib/systemd/system/chronyd.service" + ;; + esac + + info "Configure chrony file ${chrony_conf_file}" + cat >> "${chrony_conf_file}" < + + + + SUSE + mvedovati@suse.com + openSUSE rootfs for Kata Containers guest vm + + + 1.0.0 + zypper + en_US + us + true + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/osbuilder/rootfs-builder/suse/install-packages.sh b/tools/osbuilder/rootfs-builder/suse/install-packages.sh new file mode 100644 index 0000000000..f26339b884 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/suse/install-packages.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2018 SUSE LLC +# +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +source config.sh + +removeRepos=(repo-non-oss repo-update-non-oss repo-oss repo-update) + +for r in ${removeRepos[@]}; do + zypper --non-interactive removerepo $r +done + +zypper --non-interactive addrepo ${SUSE_FULLURL_OSS} osbuilder-oss +zypper --non-interactive addrepo ${SUSE_FULLURL_UPDATE} osbuilder-update + + +# Workaround for zypper slowdowns observed when running inside +# a container: see https://github.com/openSUSE/zypper/pull/209 +# The fix is upstream but it will take a while before landing +# in Leap +ulimit -n 1024 +zypper --non-interactive refresh +zypper --non-interactive install --no-recommends --force-resolution \ + autoconf \ + automake \ + binutils \ + cmake \ + coreutils \ + curl \ + gcc \ + gcc-c++ \ + git \ + glibc-devel \ + glibc-devel-static \ + glibc-utils \ + libstdc++-devel \ + linux-glibc-devel \ + m4 \ + make \ + python3-kiwi \ + sed \ + tar \ + vim \ + which +zypper --non-interactive clean --all + diff --git a/tools/osbuilder/rootfs-builder/suse/rootfs_lib.sh b/tools/osbuilder/rootfs-builder/suse/rootfs_lib.sh new file mode 100644 index 0000000000..216f90a2f4 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/suse/rootfs_lib.sh @@ -0,0 +1,76 @@ +# +# Copyright (c) 2018 SUSE LLC +# +# SPDX-License-Identifier: Apache-2.0 + +# - Arguments +# rootfs_dir=$1 +# +# - Optional environment variables +# +# EXTRA_PKGS: Variable to add extra PKGS provided by the user +# +# BIN_AGENT: Name of the Kata-Agent binary +# +# REPO_URL: URL to distribution repository ( should be configured in +# config.sh file) +# +# Any other configuration variable for a specific distro must be added +# and documented on its own config.sh +# +# - Expected result +# +# rootfs_dir populated with rootfs pkgs +# It must provide a binary in /sbin/init +# +# Note: For some distros, the build_rootfs() function provided in scripts/lib.sh +# will suffice. If a new distro is introduced with a special requirement, +# then, a rootfs_builder//rootfs_lib.sh file should be created +# using this template. + +build_rootfs() { + # Mandatory + local ROOTFS_DIR=$1 + + #Name of the Kata-Agent binary + local BIN_AGENT=${BIN_AGENT} + + # In case of support EXTRA packages, use it to allow + # users add more packages to the base rootfs + local EXTRA_PKGS=${EXTRA_PKGS:-} + + #PATH where files this script is placed + #Use it to refer to files in the same directory + #Exmaple: ${CONFIG_DIR}/foo + local CONFIG_DIR=${CONFIG_DIR} + + # Populate ROOTFS_DIR + # Must provide /sbin/init and /bin/${BIN_AGENT} + if [ -e "$ROOTFS_DIR" ] && ! [ -z "$(ls -A $ROOTFS_DIR)" ]; then + echo "ERROR: $ROOTFS_DIR is not empty" + exit 1 + fi + + local addPackages="" + for p in $PACKAGES $EXTRA_PKGS; do + addPackages+=" --add-package=$p" + done + + # set-repo format: + # man kiwi::system::build for details + local setRepo=" --set-repo $REPO_URL,rpm-md,$OS_IDENTIFIER,99,false,false" + + # Workaround for zypper slowdowns observed when running inside + # a container: see https://github.com/openSUSE/zypper/pull/209 + # The fix is upstream but it will take a while before landing + # in Leap + ulimit -n 1024 + kiwi system prepare \ + --description $CONFIG_DIR \ + --allow-existing-root \ + --root $ROOTFS_DIR \ + $addPackages \ + $setRepo + install -d $ROOTFS_DIR/lib/systemd + ln -s /usr/lib/systemd/systemd $ROOTFS_DIR/lib/systemd/systemd +} diff --git a/tools/osbuilder/rootfs-builder/template/Dockerfile.template b/tools/osbuilder/rootfs-builder/template/Dockerfile.template new file mode 100644 index 0000000000..95a07deec3 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/template/Dockerfile.template @@ -0,0 +1,16 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +#@distro@: docker image to be used to create a rootfs +#@OS_VERSION@: Docker image version to build this dockerfile +from @distro@:@OS_VERSION@ + +# This dockerfile needs to provide all the componets need to build a rootfs +# Install any package need to create a rootfs (package manager, extra tools) + +# RUN commands + +# This will install the proper golang to build Kata components +@INSTALL_GO@ diff --git a/tools/osbuilder/rootfs-builder/template/Makefile b/tools/osbuilder/rootfs-builder/template/Makefile new file mode 100644 index 0000000000..a1c6892c62 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/template/Makefile @@ -0,0 +1,20 @@ +# Copyright (c) 2017 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# +# +MK_DIR :=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +## Default destdir is one level up where is rootfs.sh script +DESTDIR ?= "$(realpath $(MK_DIR)/../)/$(ROOTFS_BASE_NAME)" +all: +ifndef ROOTFS_BASE_NAME + $(error ROOTFS_BASE_NAME is not set, use $ make ROOTFS_BASE_NAME=new_supported_os) +endif + mkdir -p $(DESTDIR) + cp "$(MK_DIR)/rootfs_lib_template.sh" "$(DESTDIR)/rootfs_lib.sh" + cp "$(MK_DIR)/config_template.sh" "$(DESTDIR)/config.sh" + sed \ + -e "s|@distro@|$(ROOTFS_BASE_NAME)|g" \ + "$(MK_DIR)/Dockerfile.template" > $(DESTDIR)/Dockerfile.in + + diff --git a/tools/osbuilder/rootfs-builder/template/config_template.sh b/tools/osbuilder/rootfs-builder/template/config_template.sh new file mode 100644 index 0000000000..9e98863c92 --- /dev/null +++ b/tools/osbuilder/rootfs-builder/template/config_template.sh @@ -0,0 +1,22 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +# This is a configuration file add extra variables to +# be used by build_rootfs() from rootfs_lib.sh the variables will be +# loaded just before call the function. For more information see the +# rootfs-builder/README.md file. + +OS_VERSION=${OS_VERSION:-DEFAULT_VERSION} + +PACKAGES="systemd iptables udevlib.so" + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=systemd +# List of zero or more architectures to exclude from build, +# as reported by `uname -m` +ARCH_EXCLUDE_LIST=() +# [When uncommented,] Allow the build to fail without generating an error +# For more info see: https://github.com/kata-containers/osbuilder/issues/190 +#BUILD_CAN_FAIL=1 diff --git a/tools/osbuilder/rootfs-builder/template/rootfs_lib_template.sh b/tools/osbuilder/rootfs-builder/template/rootfs_lib_template.sh new file mode 100644 index 0000000000..49ad06407e --- /dev/null +++ b/tools/osbuilder/rootfs-builder/template/rootfs_lib_template.sh @@ -0,0 +1,49 @@ +# - Arguments +# rootfs_dir=$1 +# +# - Optional environment variables +# +# EXTRA_PKGS: Variable to add extra PKGS provided by the user +# +# BIN_AGENT: Name of the Kata-Agent binary +# +# REPO_URL: URL to distribution repository ( should be configured in +# config.sh file) +# +# Any other configuration variable for a specific distro must be added +# and documented on its own config.sh +# +# - Expected result +# +# rootfs_dir populated with rootfs pkgs +# It must provide a binary in /sbin/init +# +# Note: For some distros, the build_rootfs() function provided in scripts/lib.sh +# will suffice. If a new distro is introduced with a special requirement, +# then, a rootfs_builder//rootfs_lib.sh file should be created +# using this template. + +build_rootfs() { + # Mandatory + local ROOTFS_DIR=$1 + + #Name of the Kata-Agent binary + local BIN_AGENT=${BIN_AGENT} + + # In case of support EXTRA packages, use it to allow + # users add more packages to the base rootfs + local EXTRA_PKGS=${EXTRA_PKGS:-} + + #In case rootfs is created usign repositories allow user to modify + # the default URL + local REPO_URL=${REPO_URL:-YOUR_REPO} + + #PATH where files this script is placed + #Use it to refer to files in the same directory + #Exmaple: ${CONFIG_DIR}/foo + local CONFIG_DIR=${CONFIG_DIR} + + + # Populate ROOTFS_DIR + # Must provide /sbin/init and /bin/${BIN_AGENT} +} diff --git a/tools/osbuilder/rootfs-builder/ubuntu/Dockerfile-aarch64.in b/tools/osbuilder/rootfs-builder/ubuntu/Dockerfile-aarch64.in new file mode 100644 index 0000000000..13bb09743e --- /dev/null +++ b/tools/osbuilder/rootfs-builder/ubuntu/Dockerfile-aarch64.in @@ -0,0 +1,39 @@ +# +# Copyright (c) 2020 ARM Limited +# +# SPDX-License-Identifier: Apache-2.0 + +#ubuntu: docker image to be used to create a rootfs +#@OS_VERSION@: Docker image version to build this dockerfile +from docker.io/ubuntu:@OS_VERSION@ + +# This dockerfile needs to provide all the componets need to build a rootfs +# Install any package need to create a rootfs (package manager, extra tools) + +# RUN commands +RUN apt-get update && apt-get install -y \ + autoconf \ + automake \ + binutils \ + build-essential \ + chrony \ + cmake \ + coreutils \ + curl \ + debianutils \ + debootstrap \ + g++ \ + gcc \ + git \ + libc6-dev \ + libstdc++-8-dev \ + m4 \ + make \ + sed \ + systemd \ + tar \ + vim +# This will install the proper golang to build Kata components +@INSTALL_GO@ +@INSTALL_MUSL@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/rootfs-builder/ubuntu/Dockerfile.in b/tools/osbuilder/rootfs-builder/ubuntu/Dockerfile.in new file mode 100644 index 0000000000..a5da267a4e --- /dev/null +++ b/tools/osbuilder/rootfs-builder/ubuntu/Dockerfile.in @@ -0,0 +1,44 @@ +# +# Copyright (c) 2018 Yash Jain +# +# SPDX-License-Identifier: Apache-2.0 + +#ubuntu: docker image to be used to create a rootfs +#@OS_VERSION@: Docker image version to build this dockerfile +from docker.io/ubuntu:@OS_VERSION@ + +# This dockerfile needs to provide all the componets need to build a rootfs +# Install any package need to create a rootfs (package manager, extra tools) + +# RUN commands +RUN apt-get update && apt-get --no-install-recommends install -y \ + apt-utils \ + autoconf \ + automake \ + binutils \ + build-essential \ + ca-certificates \ + chrony \ + cmake \ + coreutils \ + curl \ + debianutils \ + debootstrap \ + g++ \ + gcc \ + git \ + libc6-dev \ + libstdc++-8-dev \ + m4 \ + make \ + musl \ + musl-dev \ + musl-tools \ + sed \ + systemd \ + tar \ + vim \ + wget +# This will install the proper golang to build Kata components +@INSTALL_GO@ +@INSTALL_RUST@ diff --git a/tools/osbuilder/rootfs-builder/ubuntu/config.sh b/tools/osbuilder/rootfs-builder/ubuntu/config.sh new file mode 100644 index 0000000000..20138d145c --- /dev/null +++ b/tools/osbuilder/rootfs-builder/ubuntu/config.sh @@ -0,0 +1,33 @@ +# This is a configuration file add extra variables to +# +# Copyright (c) 2018 Yash Jain +# +# SPDX-License-Identifier: Apache-2.0 +# be used by build_rootfs() from rootfs_lib.sh the variables will be +# loaded just before call the function. For more information see the +# rootfs-builder/README.md file. + +OS_VERSION=${OS_VERSION:-18.04} +# this should be ubuntu's codename eg bionic for 18.04 +OS_NAME=${OS_NAME:-"bionic"} + +# packages to be installed by default +PACKAGES="systemd iptables init chrony kmod" + +DEBOOTSTRAP=${PACKAGE_MANAGER:-"debootstrap"} + +case $(uname -m) in + x86_64) ARCHITECTURE="amd64";; + ppc64le) ARCHITECTURE="ppc64el";; + aarch64) ARCHITECTURE="arm64";; + s390x) ARCHITECTURE="s390x";; + (*) die "$(uname -m) not supported " +esac + +# Init process must be one of {systemd,kata-agent} +INIT_PROCESS=systemd +# List of zero or more architectures to exclude from build, +# as reported by `uname -m` +ARCH_EXCLUDE_LIST=() + +[ "$SECCOMP" = "yes" ] && PACKAGES+=" libseccomp2" || true diff --git a/tools/osbuilder/rootfs-builder/ubuntu/rootfs_lib.sh b/tools/osbuilder/rootfs-builder/ubuntu/rootfs_lib.sh new file mode 100644 index 0000000000..a012a5cc4c --- /dev/null +++ b/tools/osbuilder/rootfs-builder/ubuntu/rootfs_lib.sh @@ -0,0 +1,84 @@ +# - Arguments +# +# Copyright (c) 2018 Yash Jain +# +# SPDX-License-Identifier: Apache-2.0 +# +# +# rootfs_dir=$1 +# +# - Optional environment variables +# +# EXTRA_PKGS: Variable to add extra PKGS provided by the user +# +# BIN_AGENT: Name of the Kata-Agent binary +# +# REPO_URL: URL to distribution repository ( should be configured in +# config.sh file) +# +# Any other configuration variable for a specific distro must be added +# and documented on its own config.sh +# +# - Expected result +# +# rootfs_dir populated with rootfs pkgs +# It must provide a binary in /sbin/init +# +build_rootfs() { + # Mandatory + local ROOTFS_DIR=$1 + + # Name of the Kata-Agent binary + local BIN_AGENT=${BIN_AGENT} + + # In case of support EXTRA packages, use it to allow + # users to add more packages to the base rootfs + local EXTRA_PKGS=${EXTRA_PKGS:-} + + # In case rootfs is created using repositories allow user to modify + # the default URL + local REPO_URL=${REPO_URL:-YOUR_REPO} + + # PATH where files this script is placed + # Use it to refer to files in the same directory + # Example: ${CONFIG_DIR}/foo + local CONFIG_DIR=${CONFIG_DIR} + + + # Populate ROOTFS_DIR + # Must provide /sbin/init and /bin/${BIN_AGENT} + DEBOOTSTRAP="debootstrap" + check_root + mkdir -p "${ROOTFS_DIR}" + if [ -n "${PKG_MANAGER}" ]; then + info "debootstrap path provided by user: ${PKG_MANAGER}" + elif check_program $DEBOOTSTRAP ; then + PKG_MANAGER=$DEBOOTSTRAP + else + die "$DEBOOTSTRAP is not installed" + fi + # trim whitespace + PACKAGES=$(echo $PACKAGES |xargs ) + EXTRA_PKGS=$(echo $EXTRA_PKGS |xargs) + # add comma as debootstrap needs , separated package names. + # Don't change $PACKAGES in config.sh to include ',' + # This is done to maintain consistency + PACKAGES=$(echo $PACKAGES | sed -e 's/ /,/g' ) + EXTRA_PKGS=$(echo $EXTRA_PKGS | sed -e 's/ /,/g' ) + + # extra packages are added to packages and finally passed to debootstrap + if [ "${EXTRA_PKGS}" = "" ]; then + echo "no extra packages" + else + PACKAGES="${PACKAGES},${EXTRA_PKGS}" + fi + + ${PKG_MANAGER} --variant=minbase \ + --arch=${ARCHITECTURE}\ + --include="$PACKAGES" \ + ${OS_NAME} \ + ${ROOTFS_DIR} + + chroot $ROOTFS_DIR ln -s /lib/systemd/systemd /usr/lib/systemd/systemd +} + diff --git a/tools/osbuilder/scripts/install-yq.sh b/tools/osbuilder/scripts/install-yq.sh new file mode 100644 index 0000000000..f2bd8e6044 --- /dev/null +++ b/tools/osbuilder/scripts/install-yq.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 IBM +# +# SPDX-License-Identifier: Apache-2.0 +# + +# If we fail for any reason a message will be displayed +die() { + msg="$*" + echo "ERROR: $msg" >&2 + exit 1 +} + +# Install the yq yaml query package from the mikefarah github repo +# Install via binary download, as we may not have golang installed at this point +function install_yq() { + GOPATH=${GOPATH:-${HOME}/go} + local yq_path="${GOPATH}/bin/yq" + local yq_pkg="github.com/mikefarah/yq" + [ -x "${GOPATH}/bin/yq" ] && return + + read -r -a sysInfo <<< "$(uname -sm)" + + case "${sysInfo[0]}" in + "Linux" | "Darwin") + goos="${sysInfo[0],}" + ;; + "*") + die "OS ${sysInfo[0]} not supported" + ;; + esac + + case "${sysInfo[1]}" in + "aarch64") + goarch=arm64 + ;; + "ppc64le") + goarch=ppc64le + ;; + "x86_64") + goarch=amd64 + ;; + "s390x") + goarch=s390x + ;; + "*") + die "Arch ${sysInfo[1]} not supported" + ;; + esac + + mkdir -p "${GOPATH}/bin" + + # Check curl + if ! command -v "curl" >/dev/null; then + die "Please install curl" + fi + + local yq_version=2.3.0 + + local yq_url="https://${yq_pkg}/releases/download/${yq_version}/yq_${goos}_${goarch}" + curl -o "${yq_path}" -LSsf ${yq_url} + [ $? -ne 0 ] && die "Download ${yq_url} failed" + chmod +x ${yq_path} + + if ! command -v "${yq_path}" >/dev/null; then + die "Cannot not get ${yq_path} executable" + fi +} + +install_yq + diff --git a/tools/osbuilder/scripts/lib.sh b/tools/osbuilder/scripts/lib.sh new file mode 100644 index 0000000000..a599712cca --- /dev/null +++ b/tools/osbuilder/scripts/lib.sh @@ -0,0 +1,566 @@ +#!/bin/bash +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +set -e + +GO_AGENT_PKG=${GO_AGENT_PKG:-github.com/kata-containers/agent} +GO_RUNTIME_PKG=${GO_RUNTIME_PKG:-github.com/kata-containers/runtime} +RUST_AGENT_PKG=${RUST_AGENT_PKG:-github.com/kata-containers/kata-rust-agent} +CMAKE_VERSION=${CMAKE_VERSION:-"null"} +MUSL_VERSION=${MUSL_VERSION:-"null"} +#https://github.com/kata-containers/tests/blob/master/.ci/jenkins_job_build.sh +# Give preference to variable set by CI +KATA_BRANCH=${branch:-} +KATA_BRANCH=${KATA_BRANCH:-master} +yq_file="${script_dir}/../scripts/install-yq.sh" + +error() +{ + local msg="$*" + echo "ERROR: ${msg}" >&2 +} + +die() +{ + error "$*" + exit 1 +} + +OK() +{ + local msg="$*" + echo "[OK] ${msg}" >&2 +} + +info() +{ + local msg="$*" + echo "INFO: ${msg}" +} + +warning() +{ + local msg="$*" + echo "WARNING: ${msg}" +} + +check_program() +{ + type "$1" >/dev/null 2>&1 +} + +check_root() +{ + if [ "$(id -u)" != "0" ]; then + echo "Root is needed" + exit 1 + fi +} + +generate_dnf_config() +{ + REPO_NAME=${REPO_NAME:-"base"} + CACHE_DIR=${CACHE_DIR:-"/var/cache/dnf"} + cat > "${DNF_CONF}" << EOF +[main] +cachedir=${CACHE_DIR} +logfile=${LOG_FILE} +keepcache=0 +debuglevel=2 +exactarch=1 +obsoletes=1 +plugins=0 +installonly_limit=3 +reposdir=/root/mash +retries=5 +EOF + if [ "$BASE_URL" != "" ]; then + cat >> "${DNF_CONF}" << EOF + +[base] +name=${OS_NAME}-${OS_VERSION} ${REPO_NAME} +failovermethod=priority +baseurl=${BASE_URL} +enabled=1 +EOF + elif [ "$MIRROR_LIST" != "" ]; then + cat >> "${DNF_CONF}" << EOF + +[base] +name=${OS_NAME}-${OS_VERSION} ${REPO_NAME} +mirrorlist=${MIRROR_LIST} +enabled=1 +EOF + fi + + if [ -n "$GPG_KEY_URL" ]; then + if [ ! -f "${CONFIG_DIR}/${GPG_KEY_FILE}" ]; then + curl -L ${GPG_KEY_URL} -o ${CONFIG_DIR}/${GPG_KEY_FILE} + fi + cat >> "${DNF_CONF}" << EOF +gpgcheck=1 +gpgkey=file://${CONFIG_DIR}/${GPG_KEY_FILE} +EOF + fi + + if [ -n "$GPG_KEY_ARCH_URL" ]; then + if [ ! -f "${CONFIG_DIR}/${GPG_KEY_ARCH_FILE}" ]; then + curl -L ${GPG_KEY_ARCH_URL} -o ${CONFIG_DIR}/${GPG_KEY_ARCH_FILE} + fi + cat >> "${DNF_CONF}" << EOF + file://${CONFIG_DIR}/${GPG_KEY_ARCH_FILE} +EOF + fi + +} + +build_rootfs() +{ + # Mandatory + local ROOTFS_DIR="$1" + + [ -z "$ROOTFS_DIR" ] && die "need rootfs" + + # In case of support EXTRA packages, use it to allow + # users add more packages to the base rootfs + local EXTRA_PKGS=${EXTRA_PKGS:-""} + + #PATH where files this script is placed + #Use it to refer to files in the same directory + #Exmaple: ${CONFIG_DIR}/foo + #local CONFIG_DIR=${CONFIG_DIR} + + check_root + if [ ! -f "${DNF_CONF}" ] && [ -z "${DISTRO_REPO}" ] ; then + DNF_CONF="./kata-${OS_NAME}-dnf.conf" + generate_dnf_config + fi + mkdir -p "${ROOTFS_DIR}" + if [ -n "${PKG_MANAGER}" ]; then + info "DNF path provided by user: ${PKG_MANAGER}" + elif check_program "dnf"; then + PKG_MANAGER="dnf" + elif check_program "yum" ; then + PKG_MANAGER="yum" + else + die "neither yum nor dnf is installed" + fi + + DNF="${PKG_MANAGER} -y --installroot=${ROOTFS_DIR} --noplugins" + if [ -n "${DNF_CONF}" ] ; then + DNF="${DNF} --config=${DNF_CONF}" + else + DNF="${DNF} --releasever=${OS_VERSION}" + fi + $DNF install ${EXTRA_PKGS} ${PACKAGES} +} + +# Create a YAML metadata file inside the rootfs. +# +# This provides useful information about the rootfs than can be interrogated +# once the rootfs has been converted into a image/initrd. +create_summary_file() +{ + local -r rootfs_dir="$1" + + [ -z "$rootfs_dir" ] && die "need rootfs" + + local -r file_dir="/var/lib/osbuilder" + local -r dir="${rootfs_dir}${file_dir}" + + local -r filename="osbuilder.yaml" + local file="${dir}/${filename}" + + local -r now=$(date -u -d@${SOURCE_DATE_EPOCH:-$(date +%s.%N)} '+%Y-%m-%dT%T.%N%zZ') + + # sanitise package lists + PACKAGES=$(echo "$PACKAGES"|tr ' ' '\n'|sort -u|tr '\n' ' ') + EXTRA_PKGS=$(echo "$EXTRA_PKGS"|tr ' ' '\n'|sort -u|tr '\n' ' ') + + local -r packages=$(for pkg in ${PACKAGES}; do echo " - \"${pkg}\""; done) + local -r extra=$(for pkg in ${EXTRA_PKGS}; do echo " - \"${pkg}\""; done) + + mkdir -p "$dir" + + # Semantic version of the summary file format. + # + # XXX: Increment every time the format of the summary file changes! + local -r format_version="0.0.2" + + local -r osbuilder_url="https://github.com/kata-containers/osbuilder" + + local agent="${AGENT_DEST}" + [ "$AGENT_INIT" = yes ] && agent="${init}" + + local agent_version + if [ "${RUST_AGENT}" == "no" ]; then + agent_version=$("$agent" --version|awk '{print $NF}') + else + local -r agentdir="${GOPATH}/src/${RUST_AGENT_PKG}/src/agent" + agent_version=$(cat ${agentdir}/VERSION) + fi + + local REAL_AGENT_PKG + + if [ "$RUST_AGENT" == "no" ]; then + REAL_AGENT_PKG=${GO_AGENT_PKG} + else + REAL_AGENT_PKG=${RUST_AGENT_PKG} + fi + + cat >"$file"<<-EOT + --- + osbuilder: + url: "${osbuilder_url}" + version: "${OSBUILDER_VERSION}" + rootfs-creation-time: "${now}" + description: "osbuilder rootfs" + file-format-version: "${format_version}" + architecture: "${ARCH}" + base-distro: + name: "${OS_NAME}" + version: "${OS_VERSION}" + packages: + default: +${packages} + extra: +${extra} + agent: + url: "https://${REAL_AGENT_PKG}" + name: "${AGENT_BIN}" + version: "${agent_version}" + agent-is-init-daemon: "${AGENT_INIT}" +EOT + + local rootfs_file="${file_dir}/$(basename "${file}")" + info "Created summary file '${rootfs_file}' inside rootfs" +} + +# generate_dockerfile takes as only argument a path. It expects a Dockerfile.in +# Dockerfile template to be present in that path, and will generate a usable +# Dockerfile replacing the '@PLACEHOLDER@' in that Dockerfile +generate_dockerfile() +{ + dir="$1" + [ -d "${dir}" ] || die "${dir}: not a directory" + + local architecture=$(uname -m) + local rustarch=${architecture} + local muslarch=${architecture} + case "$(uname -m)" in + "ppc64le") + goarch=ppc64le + rustarch=powerpc64le + muslarch=powerpc64 + ;; + + "aarch64") + goarch=arm64 + ;; + "s390x") + goarch=s390x + ;; + + *) + goarch=amd64 + ;; + esac + + [ -n "${http_proxy:-}" ] && readonly set_proxy="RUN sed -i '$ a proxy="${http_proxy:-}"' /etc/dnf/dnf.conf /etc/yum.conf; true" + + curlOptions=("-OL") + [ -n "${http_proxy:-}" ] && curlOptions+=("-x ${http_proxy:-}") + + readonly install_go=" +RUN cd /tmp ; curl ${curlOptions[@]} https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${goarch}.tar.gz +RUN tar -C /usr/ -xzf /tmp/go${GO_VERSION}.linux-${goarch}.tar.gz +ENV GOROOT=/usr/go +ENV PATH=\$PATH:\$GOROOT/bin:\$GOPATH/bin +" + + # Rust agent + # rust installer should set path apropiately, just in case + local cmake_file="cmake-${CMAKE_VERSION}.tar.gz" + local cmake_dir="cmake-${CMAKE_VERSION}" + readonly install_cmake=" +RUN pushd /root; \ + curl -sLO https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${cmake_file}; \ + tar -zxf ${cmake_file}; \ + cd ${cmake_dir}; \ + ./bootstrap > /dev/null 2>\&1; \ + make > /dev/null 2>\&1; \ + make install > /dev/null 2>\&1; \ + popd +" + # install musl for compiling rust-agent + install_musl= + if [ "${muslarch}" == "aarch64" ]; then + local musl_tar="${muslarch}-linux-musl-native.tgz" + local musl_dir="${muslarch}-linux-musl-native" + install_musl=" +RUN cd /tmp; \ + curl -sLO https://musl.cc/${musl_tar}; tar -zxf ${musl_tar}; \ + mkdir -p /usr/local/musl/; \ + cp -r ${musl_dir}/* /usr/local/musl/ +ENV PATH=\$PATH:/usr/local/musl/bin +RUN ln -sf /usr/local/musl/bin/g++ /usr/bin/g++ +" + else + local musl_tar="musl-${MUSL_VERSION}.tar.gz" + local musl_dir="musl-${MUSL_VERSION}" + install_musl=" +RUN pushd /root; \ + curl -sLO https://www.musl-libc.org/releases/${musl_tar}; tar -zxf ${musl_tar}; \ + cd ${musl_dir}; \ + sed -i \"s/^ARCH = .*/ARCH = ${muslarch}/g\" dist/config.mak; \ + ./configure > /dev/null 2>\&1; \ + make > /dev/null 2>\&1; \ + make install > /dev/null 2>\&1; \ + echo \"/usr/local/musl/lib\" > /etc/ld-musl-${muslarch}.path; \ + popd +ENV PATH=\$PATH:/usr/local/musl/bin +" + fi + + readonly install_rust=" +RUN curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSLf --output /tmp/rust-init; \ + chmod a+x /tmp/rust-init; \ + export http_proxy=${http_proxy:-}; \ + export https_proxy=${http_proxy:-}; \ + /tmp/rust-init -y +RUN . /root/.cargo/env; \ + export http_proxy=${http_proxy:-}; \ + export https_proxy=${http_proxy:-}; \ + cargo install cargo-when; \ + rustup toolchain install ${RUST_VERSION}; \ + rustup default ${RUST_VERSION}; \ + rustup target install ${rustarch}-unknown-linux-musl +RUN ln -sf /usr/bin/g++ /bin/musl-g++ +" + # rust agent still need go to build + # because grpc-sys need go to build + pushd ${dir} + dockerfile_template="Dockerfile.in" + dockerfile_arch_template="Dockerfile-${architecture}.in" + # if arch-specific docker file exists, swap the univesal one with it. + if [ -f "${dockerfile_arch_template}" ]; then + dockerfile_template="${dockerfile_arch_template}" + else + [ -f "${dockerfile_template}" ] || die "${dockerfile_template}: file not found" + fi + + # powerpc have no musl target, don't setup rust enviroment + # since we cannot static link agent. Besides, there is + # also long double representation problem when building musl-libc + if [ "${architecture}" == "ppc64le" ] || [ "${architecture}" == "s390x" ]; then + sed \ + -e "s|@GO_VERSION@|${GO_VERSION}|g" \ + -e "s|@OS_VERSION@|${OS_VERSION:-}|g" \ + -e "s|@INSTALL_CMAKE@||g" \ + -e "s|@INSTALL_MUSL@||g" \ + -e "s|@INSTALL_GO@|${install_go//$'\n'/\\n}|g" \ + -e "s|@INSTALL_RUST@||g" \ + -e "s|@SET_PROXY@|${set_proxy:-}|g" \ + ${dockerfile_template} > Dockerfile + else + sed \ + -e "s|@GO_VERSION@|${GO_VERSION}|g" \ + -e "s|@OS_VERSION@|${OS_VERSION:-}|g" \ + -e "s|@INSTALL_CMAKE@|${install_cmake//$'\n'/\\n}|g" \ + -e "s|@INSTALL_MUSL@|${install_musl//$'\n'/\\n}|g" \ + -e "s|@INSTALL_GO@|${install_go//$'\n'/\\n}|g" \ + -e "s|@INSTALL_RUST@|${install_rust//$'\n'/\\n}|g" \ + -e "s|@SET_PROXY@|${set_proxy:-}|g" \ + ${dockerfile_template} > Dockerfile + fi + popd +} + +detect_go_version() +{ + info "Detecting agent go version" + typeset yq=$(command -v yq || command -v ${GOPATH}/bin/yq || echo "${GOPATH}/bin/yq") + if [ ! -f "$yq" ]; then + source "$yq_file" + fi + + local runtimeRevision="" + + # Detect runtime revision by fetching the agent's VERSION file + local runtime_version_url="https://raw.githubusercontent.com/kata-containers/agent/${AGENT_VERSION:-master}/VERSION" + info "Detecting runtime version using ${runtime_version_url}" + + if runtimeRevision="$(curl -fsSL ${runtime_version_url})"; then + [ -n "${runtimeRevision}" ] || die "failed to get agent version" + typeset -r runtimeVersionsURL="https://raw.githubusercontent.com/kata-containers/runtime/${runtimeRevision}/versions.yaml" + info "Getting golang version from ${runtimeVersionsURL}" + # This may fail if we are a kata bump. + if GO_VERSION="$(curl -fsSL "$runtimeVersionsURL" | $yq r - "languages.golang.version")"; then + [ "$GO_VERSION" != "null" ] + return 0 + fi + fi + + info "Agent version has not match with a runtime version, assumming it is a PR" + local kata_runtime_pkg_dir="${GOPATH}/src/${GO_RUNTIME_PKG}" + if [ ! -d "${kata_runtime_pkg_dir}" ];then + info "There is not runtime repository in filesystem (${kata_runtime_pkg_dir})" + local runtime_versions_url="https://raw.githubusercontent.com/kata-containers/runtime/${KATA_BRANCH}/versions.yaml" + info "Get versions file from ${runtime_versions_url}" + GO_VERSION="$(curl -fsSL "${runtime_versions_url}" | $yq r - "languages.golang.version")" + if [ "$?" == "0" ] && [ "$GO_VERSION" != "null" ]; then + return 0 + fi + + return 1 + fi + + local kata_versions_file="${kata_runtime_pkg_dir}/versions.yaml" + info "Get Go version from ${kata_versions_file}" + GO_VERSION="$(cat "${kata_versions_file}" | $yq r - "languages.golang.version")" + + [ "$?" == "0" ] && [ "$GO_VERSION" != "null" ] +} + +detect_rust_version() +{ + info "Detecting agent rust version" + typeset -r yq=$(command -v yq || command -v ${GOPATH}/bin/yq || echo "${GOPATH}/bin/yq") + if [ ! -f "$yq" ]; then + source "$yq_file" + fi + + local runtimeRevision="" + + # Detect runtime revision by fetching the agent's VERSION file + local runtime_version_url="https://raw.githubusercontent.com/kata-containers/agent/${AGENT_VERSION:-master}/VERSION" + info "Detecting runtime version using ${runtime_version_url}" + + if runtimeRevision="$(curl -fsSL ${runtime_version_url})"; then + [ -n "${runtimeRevision}" ] || die "failed to get agent version" + typeset -r runtimeVersionsURL="https://raw.githubusercontent.com/kata-containers/runtime/${runtimeRevision}/versions.yaml" + info "Getting rust version from ${runtimeVersionsURL}" + # This may fail if we are a kata bump. + if RUST_VERSION="$(curl -fsSL "$runtimeVersionsURL" | $yq r - "languages.rust.version")"; then + [ "$RUST_VERSION" != "null" ] + return 0 + fi + fi + + info "Agent version has not match with a runtime version, assumming it is a PR" + local kata_runtime_pkg_dir="${GOPATH}/src/${GO_RUNTIME_PKG}" + if [ ! -d "${kata_runtime_pkg_dir}" ];then + info "There is not runtime repository in filesystem (${kata_runtime_pkg_dir})" + local runtime_versions_url="https://raw.githubusercontent.com/kata-containers/runtime/${KATA_BRANCH}/versions.yaml" + info "Get versions file from ${runtime_versions_url}" + RUST_VERSION="$(curl -fsSL "${runtime_versions_url}" | $yq r - "languages.rust.version")" + if [ "$?" == "0" ] && [ "$RUST_VERSION" != "null" ]; then + return 0 + fi + + return 1 + fi + + local kata_versions_file="${kata_runtime_pkg_dir}/versions.yaml" + info "Get rust version from ${kata_versions_file}" + RUST_VERSION="$(cat "${kata_versions_file}" | $yq r - "languages.rust.version")" + + [ "$?" == "0" ] && [ "$RUST_VERSION" != "null" ] +} + +detect_cmake_version() +{ + info "Detecting cmake version" + + typeset -r yq=$(command -v yq || command -v ${GOPATH}/bin/yq || echo "${GOPATH}/bin/yq") + if [ ! -f "$yq" ]; then + source "$yq_file" + fi + + local runtimeRevision="" + + # Detect runtime revision by fetching the agent's VERSION file + local runtime_version_url="https://raw.githubusercontent.com/kata-containers/agent/${AGENT_VERSION:-master}/VERSION" + info "Detecting runtime version using ${runtime_version_url}" + + if runtimeRevision="$(curl -fsSL ${runtime_version_url})"; then + [ -n "${runtimeRevision}" ] || die "failed to get agent version" + typeset -r runtimeVersionsURL="https://raw.githubusercontent.com/kata-containers/runtime/${runtimeRevision}/versions.yaml" + info "Getting cmake version from ${runtimeVersionsURL}" + # This may fail if we are a kata bump. + if CMAKE_VERSION="$(curl -fsSL "$runtimeVersionsURL" | $yq r - "externals.cmake.version")"; then + [ "$CMAKE_VERSION" != "null" ] + return 0 + fi + fi + + info "Agent version has not match with a runtime version, assumming it is a PR" + local kata_runtime_pkg_dir="${GOPATH}/src/${GO_RUNTIME_PKG}" + if [ ! -d "${kata_runtime_pkg_dir}" ];then + info "There is not runtime repository in filesystem (${kata_runtime_pkg_dir})" + local runtime_versions_url="https://raw.githubusercontent.com/kata-containers/runtime/${KATA_BRANCH}/versions.yaml" + info "Get versions file from ${runtime_versions_url}" + CMAKE_VERSION="$(curl -fsSL "${runtime_versions_url}" | $yq r - "externals.cmake.version")" + if [ "$?" == "0" ] && [ "$CMAKE_VERSION" != "null" ]; then + return 0 + fi + + return 1 + fi + + local kata_versions_file="${kata_runtime_pkg_dir}/versions.yaml" + info "Get cmake version from ${kata_versions_file}" + CMAKE_VERSION="$(cat "${kata_versions_file}" | $yq r - "externals.cmake.version")" + + [ "$?" == "0" ] && [ "$CMAKE_VERSION" != "null" ] +} + +detect_musl_version() +{ + info "Detecting musl version" + + typeset -r yq=$(command -v yq || command -v ${GOPATH}/bin/yq || echo "${GOPATH}/bin/yq") + if [ ! -f "$yq" ]; then + source "$yq_file" + fi + + local runtimeRevision="" + + # Detect runtime revision by fetching the agent's VERSION file + local runtime_version_url="https://raw.githubusercontent.com/kata-containers/agent/${AGENT_VERSION:-master}/VERSION" + info "Detecting runtime version using ${runtime_version_url}" + + if runtimeRevision="$(curl -fsSL ${runtime_version_url})"; then + [ -n "${runtimeRevision}" ] || die "failed to get agent version" + typeset -r runtimeVersionsURL="https://raw.githubusercontent.com/kata-containers/runtime/${runtimeRevision}/versions.yaml" + info "Getting musl version from ${runtimeVersionsURL}" + # This may fail if we are a kata bump. + if MUSL_VERSION="$(curl -fsSL "$runtimeVersionsURL" | $yq r - "externals.musl.version")"; then + [ "$MUSL_VERSION" != "null" ] + return 0 + fi + fi + + info "Agent version has not match with a runtime version, assumming it is a PR" + local kata_runtime_pkg_dir="${GOPATH}/src/${GO_RUNTIME_PKG}" + if [ ! -d "${kata_runtime_pkg_dir}" ];then + info "There is not runtime repository in filesystem (${kata_runtime_pkg_dir})" + local runtime_versions_url="https://raw.githubusercontent.com/kata-containers/runtime/${KATA_BRANCH}/versions.yaml" + info "Get versions file from ${runtime_versions_url}" + MUSL_VERSION="$(curl -fsSL "${runtime_versions_url}" | $yq r - "externals.musl.version")" + if [ "$?" == "0" ] && [ "$MUSL_VERSION" != "null" ]; then + return 0 + fi + + return 1 + fi + + local kata_versions_file="${kata_runtime_pkg_dir}/versions.yaml" + info "Get musl version from ${kata_versions_file}" + MUSL_VERSION="$(cat "${kata_versions_file}" | $yq r - "externals.musl.version")" + + [ "$?" == "0" ] && [ "$MUSL_VERSION" != "null" ] +} diff --git a/tools/osbuilder/tests/README.md b/tools/osbuilder/tests/README.md new file mode 100644 index 0000000000..7fb2e3b8aa --- /dev/null +++ b/tools/osbuilder/tests/README.md @@ -0,0 +1,32 @@ +* [Run the osbuilder tests](#run-the-osbuilder-tests) +* [Further information](#further-information) + +## Run the osbuilder tests + +osbuilder provides a test script that creates all rootfs disk images and +initrd images for all supported distributions and then tests them to ensure a +Kata Container can be created with each. + +Before the build phase, the test script installs the Docker container manager +and all the Kata components required to run test containers. Individual tests +will also alter host `kata-runtime` and `docker` service configuration as needed. + +All host config editing can be skipped by setting the environment variable +`KATA_DEV_MODE` to a non-empty value. In this mode, image/initrd targets +will be built but not runtime tested; If your host is configured to have +`kata-runtime` set as the default docker runtime, you will need to switch +to a runtime like `runc`/`crun` so the `docker build` test commands work +correctly. + +``` +$ ./test_images.sh +``` + +## Further information + +The test script provides various options to modify the way it runs. For full +details: + +``` +$ ./test_images.sh -h +``` diff --git a/tools/osbuilder/tests/test_config.sh b/tools/osbuilder/tests/test_config.sh new file mode 100644 index 0000000000..3a5279bbde --- /dev/null +++ b/tools/osbuilder/tests/test_config.sh @@ -0,0 +1,68 @@ +# +# Copyright (c) 2018 SUSE LLC +# +# SPDX-License-Identifier: Apache-2.0 + +# List of distros not to test, when running all tests with test_images.sh +typeset -a skipWhenTestingAll +typeset -a distros +arch="$(uname -m)" +sdir="${BASH_SOURCE[0]%/*}" +for distro in $(${sdir}/../rootfs-builder/rootfs.sh -l); do + distros+=("${distro}") +done +test_distros=() +test_distros+=("clearlinux") +test_distros+=("ubuntu") + +skipForRustDistros=() +skipForRustDistros+=("alpine") +skipForRustDistros+=("euleros") + +skipForRustArch=() +skipForRustArch+=("ppc64le") +skipForRustArch+=("s390x") + +distro_in_set() { + local d=$1 + shift + local dt + for dt in "$@"; do + if [ "${dt}" == "${d}" ]; then + return 0 + fi + done + return 1 +} + +if [ -n "${CI:-}" ]; then + # CI tests may timeout with euleros, see: + # https://github.com/kata-containers/osbuilder/issues/46" + # Since too many distros timeout for now, we only test clearlinux and ubuntu. We can enable other distros when we fix timeout problem. + for distro in "${distros[@]}"; do + if distro_in_set "${distro}" "${test_distros[@]}"; then + continue + fi + skipWhenTestingAll+=("${distro}") + done + + if [ "${RUST_AGENT:-}" == "yes" ]; then + # add skipForRustDistros to skipWhenTestingAll if it is not + for td in "${skipForRustDistros[@]}"; do + if distro_in_set "${td}" "${skipWhenTestingAll[@]}"; then + continue + fi + # not found in skipWhenTestingAll, add to it + skipWhenTestingAll+=("${td}") + done + + if distro_in_set "${arch}" "${skipForRustArch[@]}"; then + for distro in "${test_distros[@]}"; do + if distro_in_set "${distro}" "${skipWhenTestingAll[@]}"; then + continue + fi + skipWhenTestingAll+=("${distro}") + done + fi + fi +fi diff --git a/tools/osbuilder/tests/test_images.sh b/tools/osbuilder/tests/test_images.sh new file mode 100755 index 0000000000..a3a7757bbb --- /dev/null +++ b/tools/osbuilder/tests/test_images.sh @@ -0,0 +1,750 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2018 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +set -o errexit +set -o nounset +set -o pipefail +[ -n "${DEBUG:-}" ] && set -o xtrace + +readonly script_dir="$(dirname $(readlink -f $0))" +readonly script_name=${0##*/} +readonly project_dir="$(dirname ${script_dir})" +readonly tmp_dir=$(mktemp -t -d osbuilder-test.XXXXXXX) +readonly tmp_rootfs="${tmp_dir}/rootfs-osbuilder" +readonly images_dir="${tmp_dir}/images" +readonly osbuilder_file="/var/lib/osbuilder/osbuilder.yaml" +readonly docker_image="busybox" +readonly systemd_docker_config_file="/etc/systemd/system/docker.service.d/kata-containers.conf" +readonly sysconfig_docker_config_file="/etc/sysconfig/docker" +readonly tests_repo="github.com/kata-containers/tests" +readonly tests_repo_dir="${project_dir}/../tests" +readonly mgr="${tests_repo_dir}/cmd/kata-manager/kata-manager.sh" +readonly test_config=${script_dir}/test_config.sh +readonly rootfs_builder=${project_dir}/rootfs-builder/rootfs.sh +readonly DOCKER_RUNTIME=${DOCKER_RUNTIME:-runc} +readonly RUNTIME=${RUNTIME:-kata-runtime} +readonly MACHINE_TYPE=`uname -m` +readonly CI=${CI:-} +readonly KATA_HYPERVISOR="${KATA_HYPERVISOR:-}" +readonly KATA_DEV_MODE="${KATA_DEV_MODE:-}" +readonly ci_results_dir="/var/osbuilder/tests" +readonly dracut_dir=${project_dir}/dracut + +build_images=1 +build_initrds=1 +typeset -a distrosSystemd distrosAgent +distrosSystemd=() +distrosAgent=() +# Hashes used to keep track of image sizes. +# - Key: name of distro. +# - Value: colon-separated roots and image sizes ("${rootfs_size}:${image_size}"). +typeset -A built_images +typeset -A built_initrds + +# If set, show the reason why a container using the built images/initrds could +# not be started. Needed only after all images/initrd built successfully +typeset -A showKataRunFailure= + +source ${test_config} +source "${project_dir}/scripts/lib.sh" + +usage() +{ + cat <] + +Options: + -h | --help # Show usage. + --list # List all distros that can be tested. + --test-images-only # Only run images tests for the list of distros under test. + --test-initrds-only # Only run initrds tests for the list of distros under test. + +Commands: +help : Show usage. + + +When is specified, tests are run only for the specified . +Otherwise, tests are run on all distros. + +$(basename ${test_config}) includes a list of distros to exclude from testing, +depending on the detected test environment. However, when a is specified, +distro exclusion based on $(basename ${test_config}) is not enforced. +EOT +} + +# Add an entry to the specified stats file +add_to_stats_file() +{ + local statsfile="$1" + local name="$2" + local entry="$3" + local entry_type="$4" + + local rootfs_size_bytes + local rootfs_size_mb + + local image_size_bytes + local image_size_mb + + rootfs_size_bytes=$(echo "$entry"|cut -d: -f1) + image_size_bytes=$(echo "$entry"|cut -d: -f2) + + rootfs_size_mb=$(bc <<< "scale=2; ${rootfs_size_bytes} / 2^20") + image_size_mb=$(bc <<< "scale=2; ${image_size_bytes} / 2^20") + + printf '%12.12s\t%10.10s\t%12.12s\t%10.10s\t%-8.8s\t%-20.20s\n' \ + "${image_size_bytes}" \ + "${image_size_mb}" \ + "${rootfs_size_bytes}" \ + "${rootfs_size_mb}" \ + "${entry_type}" \ + "${name}" >> "$statsfile" +} + +# Show the sizes of all the generated initrds and images +show_stats() +{ + local name + local sizes + + local tmpfile=$(mktemp) + + # images + for name in "${!built_images[@]}" + do + sizes=${built_images[$name]} + add_to_stats_file "$tmpfile" "$name" "$sizes" 'image' + done + + # initrds + if [ "$KATA_HYPERVISOR" != "firecracker" ]; then + for name in "${!built_initrds[@]}" + do + sizes=${built_initrds[$name]} + add_to_stats_file "$tmpfile" "$name" "$sizes" 'initrd' + done + fi + + info "Image and rootfs sizes (in bytes and MB), smallest image first:" + echo + + printf '%12.12s\t%10.10s\t%12.12s\t%10.10s\t%-8.8s\t%-20.20s\n' \ + "image-bytes" \ + "image-MB" \ + "rootfs-bytes" \ + "rootfs-MB" \ + "Type" \ + "Name" + + sort -k1,1n -k3,3n "$tmpfile" + + rm -f "${tmpfile}" +} + + +# Run a kata-manager.sh command +run_mgr() +{ + [ -n "${KATA_DEV_MODE:-}" ] && return + silent_run $mgr $* +} + + +exit_handler() +{ + if [ "$?" -eq 0 ] + then + info "tests passed successfully - cleaning up" + + # Rootfs and images are owned by root + sudo -E rm -rf "${tmp_rootfs}" + sudo -E rm -rf "${images_dir}" + + rm -rf "${tmp_dir}" + + # Restore the default image in config file + [ -n "${TRAVIS:-}" ] || run_mgr configure-image + + return + fi + + info "ERROR: test failed" + + # The test failed so dump what we can + if [ -d "${tmp_rootfs}" ]; then + info "rootfs:" + sudo -E ls -l "${tmp_rootfs}" >&2 + sudo -E rm -rf "${tmp_rootfs}" + else + info "no rootfs created" + # If no rootfs are created, no need to dump other info + return + fi + + if [ -d "${images_dir}" ]; then + info "images:" + sudo -E ls -l "${images_dir}" >&2 + sudo -E rm -rf "${images_dir}" + else + info "no images created" + # If no images are created, no need to dump other info + return + fi + + if [ -z "${showKataRunFailure}" ]; then + # Restore the default image in config file + run_mgr configure-image + return + fi + + info "local runtime config:" + cat /etc/kata-containers/configuration.toml >&2 + + info "main runtime config:" + cat /usr/share/defaults/kata-containers/configuration.toml >&2 + + info "collect script output:" + sudo -E kata-collect-data.sh >&2 + + info "processes:" + sudo -E ps -efwww | egrep "docker|kata" >&2 + + # Restore the default image in config file + run_mgr configure-image +} + +die() +{ + msg="$*" + echo "ERROR: $msg" >&2 + exit 1 +} + +info() +{ + s="$*" + echo -en "INFO: $s\n" >&2 +} + +debug() +{ + [ -z "${TEST_DEBUG:-}" ] && return + s="$*" + echo -e "DBG: $s" >&2 +} + +# Run a command in silent mode using chronic. +# The command output is printed only if the command fails +silent_run() +{ + typeset -a commandLine=("$@") + info "running: ${commandLine[@]}" + if [ -z "${DEBUG:-}" ]; then + chronic "${commandLine[@]}" + else + "${commandLine[@]}" + fi +} + + +set_runtime() +{ + local name="$1" + + [ -z "$name" ] && die "need name" + + [ -n "${KATA_DEV_MODE}" ] && return + + # Travis doesn't support VT-x + [ -n "${TRAVIS:-}" ] && return + + if [ "$KATA_HYPERVISOR" != "firecracker" ]; then + if [ -f "$sysconfig_docker_config_file" ]; then + docker_config_file="$sysconfig_docker_config_file" + sed_script="s|^( *DOCKER_OPTS=.+--default-runtime[= ] *)[^ \"]+(.*\"$)|\1${name}\2|g" + else + docker_config_file="$systemd_docker_config_file" + sed_script="s/--default-runtime[= ][^ ]*/--default-runtime=${name}/g" + fi + + sudo -E sed -i -E "$sed_script" "$docker_config_file" + sudo -E systemctl daemon-reload + sudo -E systemctl restart docker + fi +} + +setup() +{ + mkdir -p "${images_dir}" + + if [ -n "$CI" ]; then + sudo -E rm -rf ${ci_results_dir} + sudo -E mkdir -p ${ci_results_dir} + fi + + # Travis doesn't support VT-x + [ -n "${TRAVIS:-}" ] && return + + [ ! -d "${tests_repo_dir}" ] && git clone "https://${tests_repo}" "${tests_repo_dir}" + + if [ -z "${KATA_DEV_MODE}" ]; then + mkdir -p /etc/kata-containers/ + sudo cp -a /usr/share/defaults/kata-containers/configuration.toml /etc/kata-containers/configuration.toml + else + info "Running with KATA_DEV_MODE set, skipping installation of docker and kata packages" + fi + run_mgr enable-debug + + # "docker build" does not work with a VM-based runtime, and + # also does not accept a --runtime option, so our only + # option is to overwrite the system docker default runtime + set_runtime "${DOCKER_RUNTIME}" +} + +# Fetches the distros test configuration from the distro-specific config.sh file. +# $1 : only fetch configuration for the distro with name $1. When not specified, +# fetch configuration for all distros. +get_distros_config() +{ + local distro="$1" + local distrosList + local -A distroCfg=(\ + [INIT_PROCESS]=\ + [ARCH_EXCLUDE_LIST]=\ + ) + + if [ -n "$distro" ]; then + distrosList=("$distro") + # When specifying a single distro name, skip does not apply + skipWhenTestingAll=() + else + distrosList=($(make list-distros)) + fi + + for d in ${distrosList[@]:-}; do + debug "Getting config for distro $d" + distroPattern="\<${d}\>" + if [[ "${skipWhenTestingAll[@]:-}" =~ $distroPattern ]]; then + info "Skipping distro $d as specified by $(basename ${test_config})" + continue + fi + + tmpfile=$(mktemp /tmp/osbuilder-$d-config.XXX) + ${rootfs_builder} -t $d > $tmpfile + # Get value of all keys in distroCfg + for k in ${!distroCfg[@]}; do + distroCfg[$k]="$(awk -v cfgKey=$k 'BEGIN{FS=":\t+"}{if ($1 == cfgKey) print $2}' $tmpfile)" + debug "distroCfg[$k]=${distroCfg[$k]}" + done + rm -f $tmpfile + + machinePattern="\<${MACHINE_TYPE}\>" + if [[ "${distroCfg[ARCH_EXCLUDE_LIST]}" =~ $machinePattern ]]; then + info "Skipping distro $d on architecture $MACHINE_TYPE" + continue + fi + + case "${distroCfg[INIT_PROCESS]}" in + systemd) distrosSystemd+=($d) ;; + kata-agent) distrosAgent+=($d) ;; + *) die "Invalid init process specified for distro $d: \"${distroCfg[INIT_PROCESS]}\"" ;; + esac + done +} + +create_container() +{ + # If KATA_DEV_MODE is set, we don't have any way to point kata-runtime + # at the image/initrd to boot, so there's nothing to do + [ -n "${KATA_DEV_MODE}" ] && return + + out=$(mktemp) + + local file="/proc/version" + + # Create a container using the runtime under test which displays a + # file that is expected to exist. + docker run --rm -i --runtime "${RUNTIME}" "$docker_image" cat "${file}" > "$out" + + info "contents of docker image ${docker_image} container file '${file}':" + cat "${out}" >&2 + + [ -s "$out" ] + rm -f "$out" +} + +install_image_create_container() +{ + local file="$1" + + [ -z "$file" ] && die "need file" + [ ! -e "$file" ] && die "file does not exist: $file" + + # Travis doesn't support VT-x + [ -n "${TRAVIS:-}" ] && return + + showKataRunFailure=1 + run_mgr reset-config + if [ "${RUST_AGENT:-}" = "yes" ]; then + run_mgr enable-vsock + fi + run_mgr configure-image "$file" + create_container + showKataRunFailure= +} + +install_initrd_create_container() +{ + local file="$1" + + [ -z "$file" ] && die "need file" + [ ! -e "$file" ] && die "file does not exist: $file" + + # Travis doesn't support VT-x + [ -n "${TRAVIS:-}" ] && return + + showKataRunFailure=1 + run_mgr reset-config + if [ "${RUST_AGENT:-}" = "yes" ]; then + run_mgr enable-vsock + fi + run_mgr configure-initrd "$file" + create_container + showKataRunFailure= +} + +# Displays a list of distros which can be tested +list_distros() +{ + tr " " "\n" <<< "${distrosSystemd[@]:-} ${distrosAgent[@]:-}" | sort +} + +# +# Calls the `GNU make` utility with the set of passed arguments. +# Arguments can either be make targets or make variables assignments (in the form of VARIABLE=) +# +call_make() { + targetType=$1 + shift + makeVars=() + makeTargets=() + # Split args between make variable and targets + for t in $@; do + # RE to match a make variable assignment + pattern="^\w+\=" + if [[ "$t" =~ $pattern ]]; then + makeVars+=("$t") + else + makeTargets+=($targetType-$t) + fi + done + + # Set a default make target + [ "${#makeTargets[@]}" = "0" ] && makeTargets+=($targetType) + + makeJobs= + if [ -z "$CI" ]; then + ((makeJobs=$(nproc) / 2)) + fi + + # When calling make, do not use the silent_run wrapper, pass the + # OSBUILDER_USE_CHRONIC instead. + # In this way running make in parallel mode will, in case of failure, just + # show the print out of the single target failing. + makeVars+=(OSBUILDER_USE_CHRONIC=1) + + info "Starting make with \n\ + # of // jobs: ${makeJobs:-[unlimited]} \n\ + targets: ${makeTargets[@]} \n\ + variables: ${makeVars[@]}" + + sudo -E make -j $makeJobs ${makeTargets[@]} ${makeVars[@]} +} + +make_rootfs() { + call_make rootfs $@ +} + +make_image() { + call_make image $@ +} + +make_initrd() { + call_make initrd $@ +} + +get_rootfs_size() { + [ $# -ne 1 ] && die "get_rootfs_size: wrong number of arguments" + + local rootfs_dir=$1 + ! [ -d "$rootfs_dir" ] && die "$rootfs_dir is not a valid rootfs path" + + sudo -E du -sb "${rootfs_dir}" | awk '{print $1}' +} + + +show_rootfs_metadata() { + [ $# -ne 1 ] && die "show_rootfs_metadata: wrong number of arguments" + local rootfs_path=$1 + local osbuilder_file_fullpath="${rootfs_path}/${osbuilder_file}" + yamllint "${osbuilder_file_fullpath}" + + info "osbuilder metadata file for $d:" + cat "${osbuilder_file_fullpath}" >&2 +} + +# Create an image and/or initrd for the available distributions, +# then test each by configuring the runtime and creating a container. +# +# When passing the name of a distribution, tests are run against that +# distribution only. +# +# Parameters: +# +# 1: distro name. +# +test_distros() +{ + local distro="$1" + get_distros_config "$distro" + local commonMakeVars=( \ + USE_DOCKER=true \ + DOCKER_RUNTIME="${DOCKER_RUNTIME}" \ + ROOTFS_BUILD_DEST="$tmp_rootfs" \ + IMAGES_BUILD_DEST="$images_dir" \ + DEBUG=1 ) + + # If a distro was specified, filter out the distro list to only include that distro + if [ -n "$distro" ]; then + pattern="\<$distro\>" + if [[ "${distrosAgent[@]:-}" =~ $pattern ]]; then + distrosAgent=($distro) + distrosSystemd=() + elif [[ "${distrosSystemd[@]:-}" =~ $pattern ]]; then + distrosSystemd=($distro) + distrosAgent=() + build_initrds= + else + die "Not a valid distro: $distro" + fi + + info "Running tests for distro: $distro" + + else + info "Running tests for all distros" + # Graceful exit allowed for selected distros, but only when testing all distros + commonMakeVars+=(GRACEFUL_EXIT=1) + fi + + # distro with systemd as init -> normal rootfs image + # distro with kata-agent as init -> normal rootfs image AND initrd image + + # If user does not need rootfs images, then do not build systemd rootfses + [ -z "$build_images" ] && distrosSystemd=() + + # Build systemd and agent rootfs with 2 separate jobs + bgJobs=() + + if [ ${#distrosSystemd[@]} -gt 0 ]; then + info "building rootfses with systemd as init: ${distrosSystemd[@]}" + make_rootfs ${commonMakeVars[@]} "${distrosSystemd[@]}" & + bgJobs+=($!) + fi + + if [ "$KATA_HYPERVISOR" != "firecracker" ]; then + if [ ${#distrosAgent[@]} -gt 0 ]; then + info "building all rootfses with kata-agent as init" + make_rootfs ${commonMakeVars[@]} AGENT_INIT=yes "${distrosAgent[@]}" & + bgJobs+=($!) + fi + fi + + # Check for build failures (`wait` remembers up to CHILD_MAX bg processes exit status) + for j in ${bgJobs[@]}; do + if ! wait $j; then + info "Background rootfs build job failed:" + #find completed an uncompleted jobs checking for the rootfs marker + local marker=$(make print-ROOTFS_MARKER_SUFFIX) + [ -z "$marker" ] && die "Invalid rootfs marker" + typeset -a completed=($(find ${tmp_rootfs} -name ".*${marker}" -exec basename {} \; | sed -E "s/\.(.+)${marker}/\1/")) + for d in "${distrosSystemd[@]:-}" "${distrosAgent[@]:-}"; do + if [[ "${completed[@]}" =~ $d ]]; then + info "- $d : completed" + else + info "- $d : failed" + fi + done + die "rootfs build failed" + fi + done + + # TODO: once support for rootfs images with kata-agent as init is in place, + # uncomment the following line +# for d in ${distrosSystemd[@]} ${distrosAgent[@]}; do + for d in ${distrosSystemd[@]:-}; do + local rootfs_path="${tmp_rootfs}/${d}_rootfs" + local image_path="${images_dir}/kata-containers-image-$d.img" + local rootfs_size=$(get_rootfs_size "$rootfs_path") + + # Skip failed distros + if [ -e "${tmp_rootfs}/${d}_fail" ]; then + info "Building rootfs for ${d} failed, not creating an image" + [ -n "$CI" ] && sudo -E touch "${ci_results_dir}/${d}_fail" + continue + fi + + show_rootfs_metadata "$rootfs_path" + info "Making rootfs image for ${d}" + make_image ${commonMakeVars[@]} $d + local image_size=$(stat -c "%s" "${image_path}") + + built_images["${d}"]="${rootfs_size}:${image_size}" + info "Creating container for ${d}" + install_image_create_container $image_path + done + + for d in ${distrosAgent[@]:-}; do + local rootfs_path="${tmp_rootfs}/${d}_rootfs" + local initrd_path="${images_dir}/kata-containers-initrd-$d.img" + local rootfs_size=$(get_rootfs_size "$rootfs_path") + + # Skip failed distros + if [ -e "${tmp_rootfs}/${d}_fail" ]; then + info "Building rootfs for ${d} failed, not creating an initrd" + [ -n "$CI" ] && touch "${ci_results_dir}/${d}_fail" + continue + fi + + + if [ "$KATA_HYPERVISOR" != "firecracker" ]; then + info "Making initrd image for ${d}" + make_initrd ${commonMakeVars[@]} AGENT_INIT=yes $d + local initrd_size=$(stat -c "%s" "${initrd_path}") + + built_initrds["${d}"]="${rootfs_size}:${initrd_size}" + info "Creating container for ${d}" + install_initrd_create_container $initrd_path + fi + done + + show_stats +} + +test_dracut() +{ + local initrd_path="${images_dir}/kata-containers-initrd-dracut.img" + local image_path="${images_dir}/kata-containers-image-dracut.img" + local rootfs_path="${tmp_rootfs}/dracut_rootfs" + local overlay_path="${tmp_rootfs}/dracut_overlay" + + detect_go_version || + die "Could not detect the required Go version for AGENT_VERSION='${AGENT_VERSION:-master}'." + detect_rust_version || + die "Could not detect the required rust version for AGENT_VERSION='${AGENT_VERSION:-master}'." + detect_cmake_version || + die "Could not detect the required cmake version for AGENT_VERSION='${AGENT_VERSION:-master}'." + detect_musl_version || + die "Could not detect the required musl version for AGENT_VERSION='${AGENT_VERSION:-master}'." + + generate_dockerfile ${dracut_dir} + info "Creating container for dracut" + silent_run docker build -t dracut-test-osbuilder ${dracut_dir} + + typeset -a dockerRunArgs=(\ + --rm \ + --runtime="${DOCKER_RUNTIME}" \ + -v "${project_dir}":"${project_dir}" \ + -v "${tmp_dir}":"${tmp_dir}" \ + -v /etc/localtime:/etc/localtime:ro \ + dracut-test-osbuilder \ + ) + + typeset -a makeVars=(\ + BUILD_METHOD=dracut \ + TARGET_INITRD="${initrd_path}" \ + TARGET_IMAGE=${image_path} \ + TARGET_ROOTFS=${rootfs_path} \ + DRACUT_OVERLAY_DIR="${overlay_path}" \ + USE_DOCKER=1 \ + DOCKER_RUNTIME="${DOCKER_RUNTIME}" \ + ) + + info "Making image for dracut inside a container" + silent_run docker run ${dockerRunArgs[@]} make -C ${project_dir} ${makeVars[@]} rootfs + make_image ${makeVars[@]} + local image_size=$(stat -c "%s" "${image_path}") + local rootfs_size=$(get_rootfs_size "$rootfs_path") + built_images["dracut"]="${rootfs_size}:${image_size}" + info "Creating container for dracut" + install_image_create_container $image_path + + if [ "$KATA_HYPERVISOR" != "firecracker" ]; then + info "Making initrd for dracut inside a container" + silent_run docker run ${dockerRunArgs[@]} make -C ${project_dir} ${makeVars[@]} AGENT_INIT=yes clean initrd + local initrd_size=$(stat -c "%s" "${initrd_path}") + built_initrds["dracut"]="${rootfs_size}:${initrd_size}" + install_initrd_create_container $initrd_path + fi +} + +main() +{ + local args=$(getopt \ + -n "$script_name" \ + -a \ + --options="h" \ + --longoptions="help distro: list test-images-only test-initrds-only" \ + -- "$@") + + eval set -- "$args" + [ $? -ne 0 ] && { usage >&2; exit 1; } + + local distro= + + while [ $# -gt 1 ] + do + case "$1" in + -h|--help) usage; exit 0 ;; + + --list) list_distros; exit 0;; + + --test-images-only) + build_initrds= + ;; + + --test-initrds-only) + build_images= + ;; + + --) shift; break ;; + esac + + shift + done + + # Consume getopt cruft + [ "$1" = "--" ] && shift + + case "${1:-}" in + help) usage; exit 0;; + + *) distro="${1:-}";; + esac + + trap exit_handler EXIT ERR + setup + + # Run only if distro is not dracut + [ "${distro:-}" != "dracut" ] && test_distros "$distro" + # Run if distro is empty or it is dracut + [ -z "$distro" ] || [ "$distro" = "dracut" ] && test_dracut + + # We shouldn't really need a message like this but the CI can fail in + # mysterious ways so make it clear! + info "all tests finished successfully" +} + +main "$@"