diff --git a/bib/cmd/bootc-image-builder/image.go b/bib/cmd/bootc-image-builder/image.go index b82852f6..d5e78684 100644 --- a/bib/cmd/bootc-image-builder/image.go +++ b/bib/cmd/bootc-image-builder/image.go @@ -39,7 +39,8 @@ const DEFAULT_SIZE = uint64(10 * GibiByte) type ManifestConfig struct { // OCI image path (without the transport, that is always docker://) - Imgref string + Imgref string + BuildImgref string ImageTypes imagetypes.ImageTypes @@ -57,7 +58,8 @@ type ManifestConfig struct { DistroDefPaths []string // Extracted information about the source container image - SourceInfo *source.Info + SourceInfo *source.Info + BuildSourceInfo *source.Info // RootFSType specifies the filesystem type for the root partition RootFSType string @@ -323,17 +325,22 @@ func manifestForDiskImage(c *ManifestConfig, rng *rand.Rand) (*manifest.Manifest Name: c.Imgref, Local: true, } + buildContainerSource := container.SourceSpec{ + Source: c.BuildImgref, + Name: c.BuildImgref, + Local: true, + } var customizations *blueprint.Customizations if c.Config != nil { customizations = c.Config.Customizations } - img := image.NewBootcDiskImage(containerSource) + img := image.NewBootcDiskImage(containerSource, buildContainerSource) img.Users = users.UsersFromBP(customizations.GetUsers()) img.Groups = users.GroupsFromBP(customizations.GetGroups()) - // TODO: get from the bootc container instead of hardcoding it - img.SELinux = "targeted" + img.SELinux = c.SourceInfo.SELinuxPolicy + img.BuildSELinux = c.BuildSourceInfo.SELinuxPolicy img.KernelOptionsAppend = []string{ "rw", @@ -411,7 +418,9 @@ func manifestForDiskImage(c *ManifestConfig, rng *rand.Rand) (*manifest.Manifest mf.Distro = manifest.DISTRO_FEDORA runner := &runner.Linux{} - if err := img.InstantiateManifestFromContainers(&mf, []container.SourceSpec{containerSource}, runner, rng); err != nil { + if err := img.InstantiateManifestFromContainers(&mf, + []container.SourceSpec{containerSource}, + []container.SourceSpec{buildContainerSource}, runner, rng); err != nil { return nil, err } diff --git a/bib/cmd/bootc-image-builder/main.go b/bib/cmd/bootc-image-builder/main.go index 7d35a6f3..6ad2d318 100644 --- a/bib/cmd/bootc-image-builder/main.go +++ b/bib/cmd/bootc-image-builder/main.go @@ -203,6 +203,7 @@ func manifestFromCobra(cmd *cobra.Command, args []string, pbar progress.Progress rpmCacheRoot, _ := cmd.Flags().GetString("rpmmd") targetArch, _ := cmd.Flags().GetString("target-arch") rootFs, _ := cmd.Flags().GetString("rootfs") + buildImgref, _ := cmd.Flags().GetString("build-container") useLibrepo, _ := cmd.Flags().GetBool("use-librepo") // If --local was given, warn in the case of --local or --local=true (true is the default), error in the case of --local=false @@ -297,26 +298,55 @@ func manifestFromCobra(cmd *cobra.Command, args []string, pbar progress.Progress return nil, nil, err } + buildContainer := container + buildSourceinfo := sourceinfo + startedBuildContainer := false + defer func() { + if startedBuildContainer { + if err := buildContainer.Stop(); err != nil { + logrus.Warnf("error stopping container: %v", err) + } + } + }() + + if buildImgref != "" { + buildContainer, err = podman_container.New(buildImgref) + if err != nil { + return nil, nil, err + } + startedBuildContainer = true + + // Gather some data from the containers distro + buildSourceinfo, err = source.LoadInfo(buildContainer.Root()) + if err != nil { + return nil, nil, err + } + } else { + buildImgref = imgref + } + // This is needed just for RHEL and RHSM in most cases, but let's run it every time in case // the image has some non-standard dnf plugins. - if err := container.InitDNF(); err != nil { + if err := buildContainer.InitDNF(); err != nil { return nil, nil, err } - solver, err := container.NewContainerSolver(rpmCacheRoot, cntArch, sourceinfo) + solver, err := buildContainer.NewContainerSolver(rpmCacheRoot, cntArch, sourceinfo) if err != nil { return nil, nil, err } manifestConfig := &ManifestConfig{ - Architecture: cntArch, - Config: config, - ImageTypes: imageTypes, - Imgref: imgref, - RootfsMinsize: cntSize * containerSizeToDiskSizeMultiplier, - DistroDefPaths: distroDefPaths, - SourceInfo: sourceinfo, - RootFSType: rootfsType, - UseLibrepo: useLibrepo, + Architecture: cntArch, + Config: config, + ImageTypes: imageTypes, + Imgref: imgref, + BuildImgref: buildImgref, + RootfsMinsize: cntSize * containerSizeToDiskSizeMultiplier, + DistroDefPaths: distroDefPaths, + SourceInfo: sourceinfo, + BuildSourceInfo: buildSourceinfo, + RootFSType: rootfsType, + UseLibrepo: useLibrepo, } manifest, repos, err := makeManifest(manifestConfig, solver, rpmCacheRoot) @@ -656,6 +686,7 @@ func buildCobraCmdline() (*cobra.Command, error) { } manifestCmd.Flags().String("rpmmd", "/rpmmd", "rpm metadata cache directory") manifestCmd.Flags().String("target-arch", "", "build for the given target architecture (experimental)") + manifestCmd.Flags().String("build-container", "", "Use a custom container for the image build") manifestCmd.Flags().StringArray("type", []string{"qcow2"}, fmt.Sprintf("image types to build [%s]", imagetypes.Available())) manifestCmd.Flags().Bool("local", true, "DEPRECATED: --local is now the default behavior, make sure to pull the container image before running bootc-image-builder") if err := manifestCmd.Flags().MarkHidden("local"); err != nil { diff --git a/bib/internal/source/source.go b/bib/internal/source/source.go index f8492d81..a9d17344 100644 --- a/bib/internal/source/source.go +++ b/bib/internal/source/source.go @@ -1,6 +1,8 @@ package source import ( + "bufio" + "errors" "fmt" "os" "path" @@ -21,8 +23,9 @@ type OSRelease struct { } type Info struct { - OSRelease OSRelease - UEFIVendor string + OSRelease OSRelease + UEFIVendor string + SELinuxPolicy string } func validateOSRelease(osrelease map[string]string) error { @@ -58,6 +61,36 @@ func uefiVendor(root string) (string, error) { return "", fmt.Errorf("cannot find UEFI vendor in %s", bootupdEfiDir) } +func readSelinuxPolicy(root string) (string, error) { + configPath := "etc/selinux/config" + f, err := os.Open(path.Join(root, configPath)) + if err != nil { + return "", fmt.Errorf("cannot read selinux config %s: %w", configPath, err) + } + policy := "" + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) == 0 { + continue + } + if strings.HasPrefix(line, "#") { + continue + } + + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + return "", errors.New("selinux config: invalid input") + } + key := strings.TrimSpace(parts[0]) + if key == "SELINUXTYPE" { + policy = strings.TrimSpace(parts[1]) + } + } + + return policy, nil +} + func LoadInfo(root string) (*Info, error) { osrelease, err := distro.ReadOSReleaseFromTree(root) if err != nil { @@ -71,6 +104,12 @@ func LoadInfo(root string) (*Info, error) { if err != nil { logrus.Debugf("cannot read UEFI vendor: %v, setting it to none", err) } + + selinuxPolicy, err := readSelinuxPolicy(root) + if err != nil { + logrus.Debugf("cannot read selinux policy: %v, setting it to none", err) + } + var idLike []string if osrelease["ID_LIKE"] != "" { idLike = strings.Split(osrelease["ID_LIKE"], " ") @@ -86,6 +125,7 @@ func LoadInfo(root string) (*Info, error) { IDLike: idLike, }, - UEFIVendor: vendor, + UEFIVendor: vendor, + SELinuxPolicy: selinuxPolicy, }, nil }