PUNGI(1)
๐ธ PUNGI ๐ธ
okay bestie, welcome to the manual page for pungi โ literally the coolest thing in section 1 of your man pages. Pungi 4.14.0, periodt. ๐
Contents:
โจ ABOUT PUNGI โจ
[image: Pungi Logo] [image]
Pungi is a distribution compose tool. ๐
Composes are release snapshots that contain release deliverables such as:
- installation trees
- RPMs
- repodata
- comps
- (bootable) ISOs
- kickstart trees
- anaconda images
- images for PXE boot
๐ ๏ธ Tool overview
Pungi is not one single program, bestie โ it's a whole squad of separate executables, all backed by a common library. it's giving girl group energy fr fr.
The main entry-point is the pungi-koji script. It loads the compose configuration and kicks off the process. Composing itself is done in phases. Each phase is responsible for generating some artifacts on disk and updating the compose object that is threaded through all the phases. ๐งต
And here's the gag: Pungi itself does not actually do that much. Most of the actual work is delegated to separate executables. Pungi just makes sure that all the commands are invoked in the appropriate order and with correct arguments. It also moves the artifacts to correct locations. it's the manager, not the intern. ๐
The executable name pungi-koji comes from the fact that most of those separate executables submit tasks to Koji that does the actual work in an auditable way.
However unlike doing everything manually in Koji, Pungi will make sure you are building all images from the same package set, and will produce even deliverables that Koji can not create like YUM repos and installer ISOs. no cap, that part is iconic. ๐ฅ
๐ Links
- Upstream GIT:
https://forge.fedoraproject.org/pungi/pungi - Issue tracker:
https://forge.fedoraproject.org/pungi/pungi/issues
๐ Origin of name
The name Pungi comes from the instrument used to charm snakes. Anaconda being the software Pungi was manipulating, and anaconda being a snake, led to the referential naming. ๐ช๐
The first name, which was suggested by Seth Vidal, was FIST, Fedora Installation <Something> Tool. That name was quickly discarded and replaced with Pungi. honestly? good call.
There was also a bit of an inside joke that when said aloud, it could sound like punji, which is a sharpened stick at the bottom of a trap. Kind of like softwareโฆ ๐
๐ฅ PHASES ๐ฅ
Each invocation of pungi-koji consists of a set of phases. [image: phase diagram] [image]
Most of the phases run sequentially (left-to-right in the diagram), but there are use cases where multiple phases run in parallel. This happens for phases whose main point is to wait for a Koji task to finish. โณ
๐ฑ Init
The first phase to ever run. Can not be skipped. It prepares the comps files for variants (by filtering out groups and packages that should not be there). See Processing comps files for details about how this is done.
๐ฆ RPM Repo Mapping
This phase generates compose/metadata/rpm-repo-mapping.yaml, the mapping of which RPM should go to which repository.
rpm_repo_mapping config option is required. ๐ซถ
๐๏ธ Pkgset
This phase loads a set of packages that should be composed. It has two separate results: it prepares repos with packages in work/ directory (one per arch) for further processing, and it returns a data structure with mapping of packages to architectures.
๐ฅพ Buildinstall
Spawns a bunch of threads, each of which runs the lorax command. The commands create boot.iso and other boot configuration files. The image is finally linked into the compose/ directory as netinstall media.
The created images are also needed for creating live media or other images in later phases.
With lorax this phase runs one task per variant.arch combination.
NOTE: Ifboot_iso_methodis set,boot.isoproduction is handled by the BootIso phase instead and Buildinstall is skipped. See below.
๐ฅฟ BootIso
An alternative to Buildinstall for producing boot.iso images. Instead of running lorax locally, this phase submits a Koji task (either imageBuilderBuild or kiwiBuild, depending on boot_iso_method) and downloads the resulting ISO.
After downloading, the phase populates the os tree with boot infrastructure extracted from the ISO (isolinux/, EFI/, images/) using xorriso, writes a .treeinfo for metadata.py, and hardlinks boot.iso into the iso/ directory as a standalone deliverable.
This phase runs in the essentials group alongside Buildinstall. Exactly one of the two should be active per compose โ setting both buildinstall_method and boot_iso_method is a configuration error. Setting neither means no boot.iso is produced and ISOs will not be bootable. ๐ซฃ
The phase is controlled by boot_iso_method and supports two backends:
imagebuilderโ uses Image Builder via theimageBuilderBuildKoji task type. Per-variant configuration is read fromboot_iso_imagebuilder.kiwiโ uses Kiwi via thekiwiBuildKoji task type. Per-variant configuration is read fromboot_iso_kiwi.
๐งบ Gather
This phase uses data collected by pkgset phase and figures out what packages should be in each variant. The basic mapping can come from comps file, a JSON mapping or additional_packages config option. This inputs can then be enriched by adding all dependencies. See Gathering packages for details.
Once the mapping is finalized, the packages are linked to appropriate places and the rpms.json manifest is created. โจ
๐ ExtraFiles
This phase collects extra files from the configuration and copies them to the compose directory. The files are described by a JSON file in the compose subtree where the files are copied. This metadata is meant to be distributed with the data (on ISO images).
๐ช Createrepo
This phase creates RPM repositories for each variant.arch tree. It is actually reading the rpms.json manifest to figure out which packages should be included.
๐ณ OSTree
Updates an ostree repository with a new commit with packages from the compose. The repository lives outside of the compose and is updated immediately. If the compose fails in a later stage, the commit will not be reverted. โ ๏ธ like, that's a real warning bestie, no take-backs.
Implementation wise, this phase runs rpm-ostree command in Koji runroot (to allow running on different arches).
๐ฟ Createiso
Generates ISO files and accumulates enough metadata to be able to create image.json manifest. The file is however not created in this phase, instead it is dumped in the pungi-koji script itself.
The files include a repository with all RPMs from the variant. There will be multiple images if the packages do not fit on a single image.
The image will be bootable if buildinstall or boot_iso phase is enabled and the packages fit on a single image.
There can also be images with source repositories. These are never bootable. ๐ โ๏ธ
๐ง ExtraIsos
This phase is very similar to createiso, except it combines content from multiple variants onto a single image. Packages, repodata and extra files from each configured variant are put into a subdirectory. Additional extra files can be put into top level of the image. The image will be bootable if the main variant is bootable (i.e. buildinstall or boot_iso produced a boot.iso for it).
๐บ LiveMedia
Creates media in Koji with koji spin-livemedia command. When the media are finished, the images are copied into the compose/ directory and metadata for images is updated.
๐ผ๏ธ ImageBuild
This phase wraps up koji image-build. It also updates the metadata ultimately responsible for images.json manifest.
๐ฅ KiwiBuild
Similarly to image build, this phases creates a koji kiwiBuild task. In the background it uses Kiwi to create images.
๐๏ธ OSBuild
Similarly to image build, this phases creates a koji osbuild task. In the background it uses OSBuild Composer to create images.
๐ TriggerKonflux
This phase initiates a CI/CD workflow in Konflux. It uses a Jinja2 template to dynamically generate a single PipelineRun resource, renders the final YAML manifest, and applies it to the OpenShift/Kubernetes cluster. main character energy honestly.
NOTE: For this phase to work it requires from some extra configuration on the environtment.
๐งฑ ImageBuilder
Similarly to image build, this phases creates a koji imageBuilderBuild task. In the background it uses image-builder to create images.
๐พ OSTreeInstaller
Creates bootable media that carry an ostree repository as a payload. These images are created by running lorax with special templates. Again it runs in Koji runroot.
๐ Repoclosure
Run repoclosure on each repository. By default errors are only reported in the log, the compose will still be considered a success. The actual error has to be looked up in the compose logs directory. Configuration allows customizing this. ๐ต๏ธโ๏ธ
๐งฎ ImageChecksum
Responsible for generating checksums for the images. The checksums are stored in image manifest as well as files on disk. The list of images to be processed is obtained from the image manifest. This way all images will get the same checksums irrespective of the phase that created them. so organized. so consistent. we love to see it. ๐
๐งช Test
This phase is supposed to run some sanity checks on the finished compose.
The only test is to check all images listed the metadata and verify that they look sane. For ISO files headers are checked to verify the format is correct, and for bootable media a check is run to verify they have properties that allow booting.
๐ CONFIG FILE FORMAT ๐
okay so the config file parser? that's not Pungi's own code, it's provided by kobo. so if the syntax is acting up, that's where it lives bestie ๐
The file is written in a Python-like format. Basically it's just a whole sequence of variables, each one with a value assigned to it, like:
variable = value
The variable names have to follow the same convention as Python code: start with a letter and from there only letters, digits and underscores are allowed. no vibes, just rules ๐
Values can be an integer, a float, a boolean (True or False), a string, or None. And strings must be wrapped in either single or double quotes โ pick your fighter, both work โจ
Complex types are supported too, and yes that includes the fun ones:
A list is enclosed in square brackets, and the items are separated with commas. There can be a comma after the last item as well (trailing comma is valid, she's allowed to be extra):
a_list = [1,
2,
3,
]
A tuple works exactly like a list, but it's enclosed in parenthesis:
a_tuple = (1, "one")
A dictionary is wrapped in brackets, and it's made of key: value pairs separated by commas. The keys can only be formed from basic types (int, float, string):
a_dict = {
'foo': 'bar',
1: None
}
Also the value assigned to a variable can be taken from another variable. yes, referencing, we love to see it:
one = 1
another = one
And anything on a line after a # symbol is ignored and functions as a comment. go off, # ๐ฆ
๐ง Importing other files ๐ง
You can include another configuration file. The files are looked up relative to the currently processed file.
The general structure of import is:
from FILENAME import WHAT
The FILENAME should be just the base name of the file without extension (which must be .conf). And WHAT can either be a comma separated list of variables, or *.
# Opens constants.conf and brings PI and E into current scope.
from constants import PI, E
# Opens common.conf and brings everything defined in that file into current
# file as well.
from common import *
NOTE: ๐ Pungi will copy the configuration file given on command line into thelogs/directory. Only this single file will be copied, not any included ones. (Copying included files requires a fix inkobolibrary.)
The JSON-formatted dump of configuration is correct though. ๐
๐ Formatting strings ๐
String interpolation is available as well. It uses a %-encoded format. See Python documentation for more details.
joined = "%s %s" % (var_a, var_b)
a_dict = {
"fst": 1,
"snd": 2,
}
another = "%(fst)s %(snd)s" % a_dict
๐ CONFIGURATION ๐
Please read productmd documentation for terminology and other release and compose related details. iykyk ๐
โจ Minimal Config Example โจ
here's the whole vibe in one go, bestie:
# RELEASE
release_name = "Fedora"
release_short = "Fedora"
release_version = "23"
# GENERAL SETTINGS
comps_file = "comps-f23.xml"
variants_file = "variants-f23.xml"
# KOJI
koji_profile = "koji"
runroot = False
# PKGSET
sigkeys = [None]
pkgset_source = "koji"
pkgset_koji_tag = "f23"
# GATHER
gather_method = "deps"
greedy_method = "build"
check_deps = False
# BUILDINSTALL
buildinstall_method = "lorax"
๐ Release ๐
The following mandatory options describe a release. no skipping these, they're the main character energy of the whole config ๐ฅ
Options
release_name[mandatory]โ (str) โ release name ๐release_short[mandatory]โ (str) โ release short name, without spaces and special charactersrelease_version[mandatory]โ (str) โ release version โญrelease_type="ga"โ (str) โ release type, for examplega,updatesorupdates-testing. See the list of all valid values in productmd documentation.release_internal=Falseโ (bool) โ whether the compose is meant for public consumption ๐treeinfo_versionโ (str) Version to display in.treeinfofiles. If not configured, the value fromrelease_versionwill be used.
Example
release_name = "Fedora"
release_short = "Fedora"
release_version = "23"
# release_type = "ga"
๐ซถ Base Product ๐ซถ
Base product options are optional, and honestly we only need them if we're composing a layered product that's built on another (base) product. so like, only if that's your situation, bestie โจ
Options
base_product_nameโ (str) โ base product namebase_product_shortโ (str) โ base product short name, without spaces and special charactersbase_product_versionโ (str) โ base product major versionbase_product_type="ga"โ (str) โ base product type,"ga","updates"etc., for the full list see the documentation of productmd.
Example
release_name = "RPM Fusion"
release_short = "rf"
release_version = "23.0"
base_product_name = "Fedora"
base_product_short = "Fedora"
base_product_version = "23"
๐ธ General Settings ๐ธ
Options
comps_file[mandatory]โ (scm_dict,strorNone) โ reference to comps XML file with installation groups ๐variants_file[mandatory]โ (scm_dictorstr) โ reference to variants XML file that defines release variants and architecturesmodule_defaults_dir[optional]โ (scm_dictorstr) โ reference the module defaults directory containingmodulemd-defaultsYAML documents. Files relevant for modules included in the compose will be embedded in the generated repodata and available for DNF.
module_defaults_dir = {
"scm": "git",
"repo": "https://pagure.io/releng/fedora-module-defaults.git",
"dir": ".",
}
failable_deliverables[optional]โ (list) โ list which deliverables on which variant and architecture can fail and not abort the whole compose. This only applies tobuildinstallandisoparts. All other artifacts can be configured in their respective part of configuration.
Please note that * as a wildcard matches all architectures but src. ๐ญ
comps_filter_environments[optional]โ (bool) โ When set toFalse, the comps files for variants will not have their environments filtered to match the variant.tree_archesโ ([str]) โ list of architectures which should be included; if undefined, all architectures fromvariants.xmlwill be includedtree_variantsโ ([str]) โ list of variants which should be included; if undefined, all variants fromvariants.xmlwill be includedrepoclosure_strictnessโ (list) โ variant/arch mapping describing how repoclosure should run. Possible values are:offโ do not run repoclosurelenientโ (default) run repoclosure and write results to logs, but detected errors are only reported in logsfatalโ abort compose when any issue is detected ๐
When multiple blocks in the mapping match a variant/arch combination, the last value will win.
repoclosure_backendโ (str) โ Select which tool should be used to run repoclosure over created repositories. By defaultyumis used, but you can switch todnf. Please note that whendnfis used, the build dependencies check is skipped. On Python 3, only thednfbackend is available.
See also: the gather_backend setting for Pungi's gather phase.
cts_urlโ (str) โ URL to Compose Tracking Service. If defined, Pungi will add the compose to Compose Tracking Service and get the compose ID from it. For examplehttps://cts.localhost.tld/cts_keytabโ (str) โ Path to Kerberos keytab which will be used for Compose Tracking Service Kerberos authentication. If not defined, the default Kerberos principal is used. ๐cts_oidc_token_urlโ (str) โ URL to the OIDC token endpoint. For examplehttps://oidc.example.com/openid-connect/token. This option can be overridden by the environment variableCTS_OIDC_TOKEN_URL.cts_oidc_client_idโ (str) โ OIDC client ID. This option can be overridden by the environment variableCTS_OIDC_CLIENT_ID. Note that environment variableCTS_OIDC_CLIENT_SECRETmust be configured with the corresponding client secret to authenticate to CTS via OIDC.compose_typeโ (str) โ Allows to set default compose type. Type set via a command-line option overwrites this.mbs_api_urlโ (str) โ URL to Module Build Service (MBS) API. For examplehttps://mbs.example.com/module-build-service/2. This is required bypkgset_scratch_modules. โญ
Example
comps_file = {
"scm": "git",
"repo": "https://git.fedorahosted.org/git/comps.git",
"branch": None,
"file": "comps-f23.xml.in",
}
variants_file = {
"scm": "git",
"repo": "https://pagure.io/pungi-fedora.git ",
"branch": None,
"file": "variants-fedora.xml",
}
failable_deliverables = [
('^.*$', {
# Buildinstall can fail on any variant and any arch
'*': ['buildinstall'],
'src': ['buildinstall'],
# Nothing on i386 blocks the compose
'i386': ['buildinstall', 'iso', 'live'],
})
]
tree_arches = ["x86_64"]
tree_variants = ["Server"]
repoclosure_strictness = [
# Make repoclosure failures fatal for compose on all variants โฆ
('^.*$', {'*': 'fatal'}),
# โฆ except for Everything where it should not run at all.
('^Everything$', {'*': 'off'})
]
๐ฆ Image Naming ๐ฆ
Both the image name and the volume id are generated based on the configuration. And since the volume id is limited to 32 characters, there are more settings available to help out. The process for generating volume id is to get a list of possible formats and try them sequentially until one fits in the length limit. If substitutions are configured, each attempted volume id will be modified by it.
For layered products, the candidate formats are first image_volid_layered_product_formats followed by image_volid_formats. Otherwise, only image_volid_formats are tried.
If no format matches the length limit, an error will be reported and compose aborted. ๐
Options
There a couple common format specifiers available for both the options:
compose_idrelease_shortversiondaterespintypetype_suffixlabellabel_major_versionvariantarchdisc_typeimage_name_format(optional) โ astror adict, bestie. It's Python's format string acting as the glow-up template for image names โจ. You can also hand it a dict that maps variant UID regexes โ format string. Just know the patterns should not overlap โ if they do, it's literally undefined which one gets picked, and nobody wants that chaotic energy.
This format gets used by some of the image-generating phases. Right now that means createiso, buildinstall and ostree_installer ๐.
Extra keys you get to play with:
disc_numsuffix
image_volid_formats(optional) โ alistof format strings for generating the volume id ๐ .
The extra keys available here are:
base_product_shortbase_product_version
image_volid_layered_product_formats(optional) โ alistof format strings for generating volume id for layered products. Same keys available asimage_volid_formats, no cap ๐ซถ.restricted_volid = Falseโ abool. New versions of lorax replace all non-alphanumerical characters with dashes (underscores are preserved, icon behaviour tbh). This option makes Pungi mimic that same behaviour ๐ธ.volume_id_substitutions(optional) โ adict, a mapping of string replacements used to shorten the volume id โ๏ธ.disc_types(optional) โ adict, a mapping for customizing thedisc_typeused in image names ๐.
Available keys:
bootโ forboot.isoimages created inbuildinstallorboot_isophasedvdโ for images created bycreateisophaseostreeโ for ostree installer images
Default values are the same as the keys. Periodt.
Example
# Image name respecting Fedora's image naming policy
image_name_format = "%(release_short)s-%(variant)s-%(disc_type)s-%(arch)s-%(version)s%(suffix)s"
# Use the same format for volume id
image_volid_formats = [
"%(release_short)s-%(variant)s-%(disc_type)s-%(arch)s-%(version)s"
]
# No special handling for layered products, use same format as for regular images
image_volid_layered_product_formats = []
# Replace "Cloud" with "C" in volume id etc.
volume_id_substitutions = {
'Cloud': 'C',
'Alpha': 'A',
'Beta': 'B',
'TC': 'T',
}
disc_types = {
'boot': 'netinst',
'dvd': 'DVD',
}
Git URLs ๐ง
Okay so in multiple places the config needs a URL of a Git repository to download some file from. That URL gets passed on to Koji. And you can totally specify which commit to use with this syntax:
git://git.example.com/git/repo-name.git?#<rev_spec>
The <rev_spec> pattern can be swapped out for an actual commit SHA, a tag name, HEAD to say "use the tip of the default branch", or origin/<branch_name> to use the tip of an arbitrary branch ๐ซ.
If the URL specifies a branch or HEAD, Pungi will replace it with the actual commit SHA. That then shows up in Koji tasks later and helps with tracing exactly which inputs were used. Lowkey genius for debugging ๐.
NOTE: The origin must be specified because of the way Koji works with the repository. It clones the repo, then switches to the requested state with git reset --hard REF. Since no local branches are created, you need the full specification including the name of the remote. No shortcuts, bestie ๐ญ.
Createrepo Settings ๐
Options
createrepo_checksumโstr, specify checksum type for createrepo. Expected values:sha512,sha256,sha1. Defaults tosha256๐ .createrepo_c = Trueโbool, usecreaterepo_c(True) or the legacycreaterepo(False). We love a modern upgrade โจ.createrepo_deltas = Falseโlist, generate delta RPMs against an older compose. This needs to be used together with the--old-composescommand line argument. The value should be a mapping of variants and architectures that should enable creating delta RPMs. Source and debuginfo repos never have deltas, sorry babes ๐ซ.createrepo_use_xz = Falseโbool, whether to pass--xzto the createrepo command. This causes the SQLite databases to be compressed with xz ๐๏ธ.createrepo_num_threadsโint, how many concurrent createrepo process to run. The default is to use one thread per CPU available on the machine ๐ฅ.createrepo_num_workersโint, how many concurrent createrepo workers to run. Value defaults to3.createrepo_databaseโbool, whether to create SQLite database as part of the repodata. This is only useful as an optimization for clients using Yum to consume to the repo. Default value depends on gather backend. For DNF it's turned off, for Yum the default isTrue.createrepo_extra_argsโ[str], a list of extra arguments passed on to thecreaterepoorcreaterepo_cexecutable. This could be useful for enabling zchunk generation and pointing it to correct dictionaries ๐.createrepo_extra_modulemdโdict, a mapping of variant UID to an scm dict. If specified, it should point to a directory with extra module metadata YAML files that will be added to the repository for this variant. The cloned files should be split into subdirectories for each architecture of the variant ๐๏ธ.createrepo_enable_cache = Trueโbool, whether to use--cachediroption of createrepo. It will cache and reuse checksum vaules to speed up createrepo phase. The cache dir is located at/var/cache/pungi/createrepo_c/$release_short-$uide.g./var/cache/pungi/createrepo_c/Fedora-1000โก.product_id = Noneโscm_dict. If specified, it should point to a directory with certificates*<variant_uid>-<arch>-*.pem. Pungi will copy each certificate file into the relevant Yum repositories as a productid file in the repodata directories. The purpose of these productid files is to expose the product data to subscription-manager.subscription-managerincludes a "product-id" Yum plugin that can read these productid certificate files from each Yum repository ๐ชช.product_id_allow_missing = Falseโbool. Whenproduct_idis used and a certificate for some variant and architecture is missing, Pungi will exit with an error by default. When you set this option toTrue, Pungi will ignore the missing certificate and simply log a warning message ๐.product_id_allow_name_prefix = Trueโbool. Allow arbitrary prefix for the certificate file name (see leading*in the pattern above). Setting this option toFalsewill make the pattern more strict by requiring the file name to start directly with variant name ๐ฏ.
Example
createrepo_checksum = "sha"
createrepo_deltas = [
# All arches for Everything should have deltas.
('^Everything$', {'*': True}),
# Also Server.x86_64 should have them (but not on other arches).
('^Server$', {'x86_64': True}),
]
createrepo_extra_modulemd = {
"Server": {
"scm": "git",
"repo": "https://example.com/extra-server-modulemd.git",
"dir": ".",
# The directory should have this layout. Each architecture for the
# variant should be included (even if the directory is empty.
# .
# โโโ aarch64
# โ โโโ some-file.yaml
# โ โ ...
# โโโ x86_64
}
}
Package Set Settings ๐ฆ
Options
sigkeysโ[str or None], a priority list of signing key IDs. These key IDs match the key IDs for the builds in Koji. Pungi will choose signed packages according to the order of the key IDs that you specify here. Use one single key in this list to ensure that all RPMs are signed by one key. If the list includes an empty string orNone, Pungi will allow unsigned packages. If the list only includesNone, Pungi will use all unsigned packages ๐.pkgset_source[mandatory] โstr. It's"koji"(any koji instance) or"repos"(arbitrary yum repositories). You literally cannot skip this one, bestie ๐จ.pkgset_koji_tagโstr|[str], tag(s) to read package set from. This option can be omitted for modular composes.pkgset_koji_buildsโstr|[str], extra build(s) to include in a package set, defined as NVRs.pkgset_koji_scratch_tasksโstr|[str], RPM scratch build task(s) to include in a package set, defined as task IDs. This option can be used only whencompose_typeis set totest. The RPM still needs to have a higher NVR than any other RPM with the same name coming from other sources in order to appear in the resulting compose ๐.pkgset_koji_module_tagโstr|[str], tags to read module from. This option works similarly to listing tags in variants XML. If tags are specified and variants XML specifies some modules via NSVC (or part of), only modules matching that list will be used (and taken from the tag). Inheritance is used automatically ๐.pkgset_koji_module_buildsโdict. A mapping of variants to extra module builds to include in a package set:{variant: [N:S:V:C]}.pkgset_koji_inherit = Trueโbool, inherit builds from parent tags. We can turn it off only if we have all builds tagged in a single tag ๐.pkgset_koji_inherit_modules = Falseโbool, the same as above, but this only applies to modular tags. This option applies to the content tags that contain the RPMs.pkgset_reposโdict. A mapping of architectures to repositories with RPMs:{arch: [repo]}. Only use whenpkgset_source = "repos"๐ซถ.pkgset_scratch_modulesโdict. A mapping of variants to scratch module builds:{variant: [N:S:V:C]}. Requiresmbs_api_url.pkgset_exclusive_arch_considers_noarch = Trueโbool. If a package includesnoarchin itsExclusiveArchtag, it will be included in all architectures sincenoarchis compatible with everything. Set this option toFalseto ignorenoarchinExclusiveArchand always consider only binary architectures ๐ง.pkgset_inherit_exclusive_arch_to_noarch = Trueโbool. When set toTrue, the value ofExclusiveArchorExcludeArchwill be copied from source rpm to all itsnoarchpackages. That will than limit which architectures thenoarchpackages can be included in ๐.pkgset_allow_reuse = Trueโ (bool) bestie, when this isTruePungi will try to reuse pkgset data from the old composes you named in--old-composes. Turn it on and your new composes get faster because Pungi skips recalculating the pkgset data from Koji. BUT ๐ โ if you block or unblock a package in Koji (say) between composes, then Pungi might not respect those changes in your new compose. iykyk.signed_packages_retries = 0โ (int) okay so in automated workflows you might kick off a compose before Koji has completely written all the signed packages to disk ๐ญ. In that case you want Pungi to wait for the package to show up in Koji's storage. This option controls how many times Pungi will retry looking for the signed copy.signed_packages_wait = 30โ (int) interval in seconds for how long to wait between attempts to find signed packages. This one only makes sense whensigned_packages_retriesis set higher than0. no cap.
Example
sigkeys = [None]
pkgset_source = "koji"
pkgset_koji_tag = "f23"
๐ Buildinstall Settings ๐
The script or process that creates bootable images with the Anaconda installer is, historically, called buildinstall. periodt.
Options
buildinstall_methodโ (str)"lorax"(f16+, rhel7+). that's the whole menu bestie.lorax_optionsโ (list) special options passed on to lorax.
Format: [(variant_uid_regex, {arch|*: {option: name}})].
Recognized options are:
bugurlโ str (defaultNone)nomacbootโ bool (defaultTrue)noupgradeโ bool (defaultTrue)add_templateโ [str] (default empty)add_arch_templateโ [str] (default empty)add_template_varโ [str] (default empty)add_arch_template_varโ [str] (default empty)rootfs_sizeโ [int] (default empty)versionโ [str] (default fromtreeinfo_versionorrelease_version) โ used as--versionand--releaseargument on the lorax command linedracut_argsโ [[str]] (default empty) override arguments for dracut. โ ๏ธ please note that if this option is used, lorax will not use any other arguments, so you have to provide a full list and can not just add something. like, the whole thing, bestie.skip_brandingโ bool (defaultFalse)squashfs_onlyโ bool (defaultFalse) pass the--squashfs_onlyto Lorax.configuration_fileโ (scm_dict) (default empty) pass the specified configuration file to Lorax using the-coption.rootfs_typeโ string (default empty) pass the--rootfs-typeoption to Lorax with the provided value. If not specified, no type is specified to Lorax, which will choose whatever default it is configured with.sharedirโ string (default empty) Specifies the directory containing Lorax templates and runtime files. If set, this value is passed to Lorax using the--sharediroption. If omitted, Lorax falls back to its built-in default:/usr/share/lorax/.workdirโ string (default empty) Specifies the working directory. If set, this value is passed to Lorax using the--workdiroption. If omitted, Lorax falls back to its built-in default:/var/tmp/lorax.
lorax_extra_sourcesโ (list) a variant/arch mapping with urls for extra source repositories added to Lorax command line. Either one repo or a list can be specified. your call ๐lorax_use_koji_plugin = Falseโ (bool) When set toTrue, the Kojipungi_buildinstalltask will be used to execute Lorax instead of runroot. ๐จ Use only if the Koji instance has thepungi_buildinstallplugin installed.buildinstall_kickstartโ (scm_dict) If specified, this kickstart file will be copied into each file and pointed to in boot configuration.buildinstall_topdirโ (str) Full path to top directory where the runroot buildinstall Koji tasks output should be stored. This is useful in situation when the Pungi compose is not generated on the same storage as the Koji task is running on. In this case, Pungi can provide input repository for runroot task using HTTP and set the output directory for this task tobuildinstall_topdir. Once the runroot task finishes, Pungi will copy the results of runroot tasks to the compose working directory. she's doing the most, honestly ๐ซถbuildinstall_skipโ (list) mapping that defines which variants and arches to skip during buildinstall; format:[(variant_uid_regex, {arch|*: True})]. This is only supported for lorax.buildinstall_allow_reuse = Falseโ (bool) When set toTrue, Pungi will try to reuse buildinstall results from old compose specified by--old-composes.buildinstall_packagesโ (list) Additional packages to be installed in the runroot environment where lorax will run to create installer. Format:[(variant_uid_regex, {arch|*: [package_globs]})].
Example
buildinstall_method = "lorax"
# Enables macboot on x86_64 for all variants and builds upgrade images
# everywhere.
lorax_options = [
("^.*$", {
"x86_64": {
"nomacboot": False,
"sharedir": "/path/to/custom/share/lorax/",
"workdir": "/path/to/work/directory/"
}
"*": {
"noupgrade": False,
"sharedir": "/path/to/custom/share/lorax/"
}
})
]
# Don't run buildinstall phase for Modular variant
buildinstall_skip = [
('^Modular', {
'*': True
})
]
# Add another repository for lorax to install packages from
lorax_extra_sources = [
('^Simple$', {
'*': 'https://example.com/repo/$basearch/',
})
]
# Additional packages to be installed in the Koji runroot environment where
# lorax will run.
buildinstall_packages = [
('^Simple$', {
'*': ['dummy-package'],
})
]
NOTE: โจ It is advised to run buildinstall (lorax) in koji, i.e. with runroot enabled for clean build environments, better logging, etc. do it bestie.
WARNING: ๐จ Lorax installs RPMs into a chroot. This involves running %post scriptlets and they frequently run executables in the chroot. If we're composing for multiple architectures, we must use runroot for this reason. not optional, fr fr.
๐ฆ Boot ISO Settings ๐ฆ
An alternative to buildinstall for producing boot.iso images. Instead of running lorax via Koji runroot, the boot_iso phase submits a Koji task (imageBuilderBuild or kiwiBuild) and downloads the resulting ISO. iconic behaviour, truly.
Setting both buildinstall_method and boot_iso_method is a configuration error โ exactly one boot.iso producer is allowed per compose. Setting neither means no boot.iso is produced and ISOs will not be bootable. ๐ pick a lane.
Options
boot_iso_methodโ (str)"imagebuilder"or"kiwi". Selects which backend theboot_isophase uses to produceboot.iso. When unset, the phase is skipped entirely. Mutually exclusive withbuildinstall_method.boot_iso_skipโ (list) mapping that defines which variants and arches to skip duringboot_iso; format:[(variant_uid_regex, {arch|*: True})]. Same schema asbuildinstall_skip. twins ๐boot_iso_imagebuilderโ (dict) per-variant configuration for the Image Builder backend; format:{variant_uid_regex: {...}}. Only used whenboot_iso_method = "imagebuilder".
Required keys in the configuration dict:
nameโ (str) name of the Koji package.
Optional keys:
targetโ (str) Koji build target. Falls back toboot_iso_imagebuilder_target, thenglobal_target.versionโ (str) version string. Falls back toboot_iso_imagebuilder_version, then the compose image version.releaseโ (str) release string.reposโ (str|[str]) additional repos, specified by URL or variant UID. The variant repo is added automatically.typesโ ([str]) image types to request from Image Builder. Defaults to["iso"].seedโ (int) seed for reproducible builds (requireskoji-image-builder >= 7).blueprintโ (dict) Image Builder blueprint for customizations.
boot_iso_kiwiโ (dict) per-variant configuration for the Kiwi backend; format:{variant_uid_regex: {...}}. Only used whenboot_iso_method = "kiwi".
Required keys in the configuration dict:
kiwi_profileโ (str) Kiwi profile to select from the description.
Optional keys:
description_scmโ (str) SCM URL for the Kiwi description repository. Falls back toboot_iso_kiwi_description_scm.description_pathโ (str) path to the Kiwi description file within the SCM repo. Falls back toboot_iso_kiwi_description_path.targetโ (str) Koji build target. Falls back toboot_iso_kiwi_target, thenglobal_target.versionโ (str) version string. Falls back toboot_iso_kiwi_version, then the compose image version.releaseโ (str) release string. Falls back toboot_iso_kiwi_release.reposโ (str|[str]) additional repos, specified by URL or variant UID. The variant repo is added automatically.
Example
# Use Image Builder to produce boot.iso
boot_iso_method = "imagebuilder"
boot_iso_skip = [
('^Everything$', {
'i386': True,
}),
]
boot_iso_imagebuilder = {
'^Server$': [
{
'name': 'Fedora-Server-boot',
'target': 'f45-image-builder',
},
],
}
# Or use Kiwi to produce boot.iso
boot_iso_method = "kiwi"
boot_iso_kiwi = {
'^Server$': [
{
'kiwi_profile': 'Boot-ISO',
'target': 'f45-kiwi',
'description_scm': 'git+https://pagure.io/kiwi-descriptions.git?#HEAD',
'description_path': 'Fedora-Boot.kiwi',
},
],
}
โจ Gather Settings โจ
Options ๐
gather_methodโ [mandatory] ๐ (str|dict) โ bestie this one is literally the whole personality of the gather phase. Options aredeps,nodepsandhybrid. It says whether and how package deps get pulled in.
So the config can be one value for all variants, OR if you do it per-variant it can be either a plain string hybrid, or a dictionary mapping source type โ a value of deps or nodeps. ๐ซถ
โ ๏ธ Make sure only one regex matches each variant, ok? Because there is no guarantee which value gets used if multiple match. That's chaos behavior and we do not stan. Also: every source you use has to have a configured method unless you're doing hybrid solving.
gather_fulltree = Falseโ (bool) ๐ธ Set it toTrueand all RPMs built from an SRPM get included, always. Only use this whengather_method = "deps".gather_selfhosting = Falseโ (bool) ๐ฆ Set it toTrueand Pungi will build a self-hosting tree by following build dependencies. Again: only use whengather_method = "deps".gather_allow_reuse = Falseโ (bool) โญ Set it toTrueand Pungi will try to reuse gather results from old composes specified by--old-composes. Recycle era fr fr โป๏ธgreedy_method = noneโ (str) ๐ This controls how package requirements get satisfied when a particularRequireshas multiple candidates. Pick your fighter:noneโ the best package is selected to satisfy the dependency and only that one is pulled into the composeallโ packages that provide the symbol are pulled inbuildโ the best package is selected, and then all packages from the same build that provide the symbol are pulled in
NOTE: okay let's set the scene. A package in the compose hasRequires: foo. There are three packages withProvides: foo:pkg-a,pkg-b-provider-1andpkg-b-provider-2. Thepkg-b-*packages are built from the same source package. Best match determinespkg-b-provider-1as the best matching package. So: - Withgreedy_method = "none"onlypkg-b-provider-1will be pulled in. - Withgreedy_method = "all"all three packages will be pulled in. - Withgreedy_method = "build"pkg-b-provider-1andpkg-b-provider-2will be pulled in.
gather_backendโ (str) ๐ฅ This changes the entire codebase doing dependency solving, so yeah it can change the result in unpredictable ways. Iconic but unhinged.
On Python 2, the choice is between yum or dnf and defaults to yum. On Python 3 dnf is the only option and default.
Particularly the multilib work is performed differently by using python-multilib library. Please refer to the multilib option to see the differences.
See also: the repoclosure_backend setting for Pungi's repoclosure phase.
multilibโ (list) ๐ mapping of variant regexes and arches to list of multilib methods.
Available methods are:
noneโ no package matches this methodallโ all packages match this methodruntimeโ packages that install some shared object file (*.so.*) will match.develโ packages whose name ends with-develor--staticsuffix will be matched. Whendnfis used, this method automatically enablesruntimemethod as well. Withyumbackend this method also uses a hardcoded blacklist and whitelist.kernelโ packages providingkernelorkernel-develmatch this method (only inyumbackend)yabootโ onlyyabootpackage onppcarch matches this (only inyumbackend)
additional_packagesโ (list) ๐ additional packages to be included in a variant and architecture; format:[(variant_uid_regex, {arch|*: [package_globs]})]
In contrast to the comps_file setting, the additional_packages setting merely adds the list of packages to the compose. When a package is in a comps group, it is visible to users via dnf groupinstall and Anaconda's Groups selection, but additional_packages does not affect DNF groups. Not the same thing bestie, don't get it twisted ๐ซถ
The packages specified here are matched against RPM names, not any other provides in the package nor the name of source package. Shell globbing is used, so wildcards are possible. The package can be specified as name only or name.arch.
With dnf gathering backend, you can specify a debuginfo package to be included. This is meant to include a package if autodetection does not get it. If you add a debuginfo package that does not have anything else from the same build included in the compose, the sources will not be pulled in.
If you list a package in additional_packages but Pungi cannot find it (for example, it's not available in the Koji tag), Pungi will log a warning in the โworkโ or โlogsโ directories and continue without aborting. She's forgiving like that. ๐
Example: this configuration will add all packages in a Koji tag to an โEverythingโ variant:
additional_packages = [
('^Everything$', {
'*': [
'*',
],
})
]
filter_packagesโ (list) ๐ packages to be excluded from a variant and architecture; format:[(variant_uid_regex, {arch|*: [package_globs]})]
See additional_packages for details about package specification. (Same rules, opposite energy.)
filter_modulesโ (list) ๐ธ modules to be excluded from a variant and architecture; format:[(variant_uid_regex, {arch|*: [name:stream]})]
Both name and stream can use shell-style globs. If stream is omitted, all streams are removed. โจ
This option only applies to modules taken from Koji tags, not modules explicitly listed in variants XML without any tags.
filter_system_release_packagesโ (bool) โญ for each variant, figure out the best system release package and filter out all others. This will not work if a variant needs more than one system release package. In such a case, set this option toFalse.gather_prepopulate = Noneโ (scm_dict) ๐ If specified, you can use this to add additional packages. The format of the file pointed to by this option is a JSON mapping{variant_uid: {arch: {build: [package]}}}. Packages added through this option can not be removed byfilter_packages. She said what she said.multilib_blacklistโ (dict) ๐ multilib blacklist; format:{arch|*: [package_globs]}.
See additional_packages for details about package specification.
multilib_whitelistโ (dict) ๐ multilib blacklist; format:{arch|*: [package_names]}. The whitelist must contain exact package names; there are no wildcards or pattern matching. Exact name or nothing, periodt.gather_lookaside_repos = []โ (list) ๐ฆ lookaside repositories used for package gathering; format:[(variant_uid_regex, {arch|*: [repo_urls]})]
The repo_urls are passed to the depsolver, which can use packages in the repos for satisfying dependencies, but the packages themselves are not pulled into the compose. The repo_urls can contain $basearch variable, which will be substituted with proper value by the depsolver.
โ ๏ธ Therepo_urlsare used by repoclosure too, but it can't parse$basearchcurrently and that will cause the Repoclosure phase to crash.repoclosure_strictnessoption could be used to stop running repoclosure.
Please note that * as a wildcard matches all architectures but src.
hashed_directories = Falseโ (bool) ๐ put packages into โhashedโ directories, for examplePackages/k/kernel-4.0.4-301.fc22.x86_64.rpmcheck_deps = Trueโ (bool) ๐ฅ Set toFalseif you don't want the compose to abort when some package has broken dependencies. Live your life, bestie.require_all_comps_packages = Falseโ (bool) ๐ Set toTrueto abort compose when a package mentioned in comps file can not be found in the package set. When disabled (the default), such cases are still reported as warnings in the log.
With dnf gather backend, this option will abort the compose on any missing package no matter if it's listed in comps, additional_packages or prepopulate file. No mercy mode. ๐ญ
gather_source_mappingโ (str) โจ JSON mapping with initial packages for the compose. The value should be a path to JSON file with following mapping:{variant: {arch: {rpm_name: [rpm_arch|None]}}}. Relative paths are interpreted relative to the location of main config file.gather_profiler = Falseโ (bool) ๐ When set toTruethe gather tool will produce additional performance profiling information at the end of its logs. Only takes effect whengather_backend = "dnf".variant_as_lookasideโ (list) ๐ง a variant/variant mapping that tells one or more variants in compose has other variant(s) in compose as a lookaside. Only top level variants are supported (not addons/layered products). Format:[(variant_uid, variant_uid)]
Example ๐
gather_method = "deps"
greedy_method = "build"
check_deps = False
hashed_directories = True
gather_method = {
"^Everything$": {
"comps": "deps" # traditional content defined by comps groups
},
"^Modular$": {
"module": "nodeps" # Modules do not need dependencies
},
"^Mixed$": { # Mixed content in one variant
"comps": "deps",
"module": "nodeps"
}
"^OtherMixed$": "hybrid", # Using hybrid depsolver
}
additional_packages = [
# bz#123456
('^(Workstation|Server)$', {
'*': [
'grub2',
'kernel',
],
}),
]
filter_packages = [
# bz#111222
('^.*$', {
'*': [
'kernel-doc',
],
}),
]
multilib = [
('^Server$', {
'x86_64': ['devel', 'runtime']
})
]
multilib_blacklist = {
"*": [
"gcc",
],
}
multilib_whitelist = {
"*": [
"alsa-plugins-*",
],
}
# gather_lookaside_repos = [
# ('^.*$', {
# '*': [
# "https://dl.fedoraproject.org/pub/fedora/linux/releases/22/Everything/$basearch/os/",
# ],
# 'x86_64': [
# "https://dl.fedoraproject.org/pub/fedora/linux/releases/22/Everything/source/SRPMS/",
# ]
# }),
# ]
NOTE: bestie, listen โ it's good practice to attach bug/ticket numbers toadditional_packages,filter_packages,multilib_blacklistandmultilib_whitelistso you can track your decisions later. future you will be so grateful, no cap ๐
Koji Settings ๐
Options
koji_profileโ (str) the koji profile name. this is literally how Pungi knows how to talk to the Koji instance you picked ๐. go read Koji's own docs about profiles for the full tea on setting up your Koji client profile. in the examples the profile name is"koji", which points at Fedora'skoji.fedoraproject.org.koji_cacheโ (str) the koji cache directory. setting this makes Pungi download packages over HTTP into a cache, and that cache gets used instead of the Koji profile'stopdirsetting. peep "Getting data from koji" for the deets on this behavior โจglobal_runroot_methodโ (str) the global runroot method to use. ifrunroot_methodis set per Pungi phase using a dictionary, then <em>this</em> option defines the default runroot method for any phase that isn't mentioned in therunroot_methoddictionary.runroot_methodโ (str*|*dict) the runroot method to use. it can further specify the runroot method in case the runroot is set toTrue.
available methods are:
- local โ runroot tasks are run locally
- koji โ runroot tasks are run in Koji
- openssh โ runroot tasks are run on a remote machine connected using OpenSSH. the
runroot_ssh_hostnamesfor each architecture must be set, and the user under which Pungi runs must be configured to login asrunroot_ssh_usernameusing the SSH key. ๐
the runroot method can also be set per Pungi phase using the dictionary with the phase name as key and the runroot method as value. the default runroot method is, in this case, defined by the global_runroot_method option. iykyk ๐
Example
global_runroot_method = "koji"
runroot_method = {
"createiso": "local"
}
runroot_channelโ (str) name of koji channelrunroot_tagโ (str) name of koji build tag used for runrootrunroot_weightsโ (dict) customize task weights for various runroot tasks. the values in the mapping should be integers, and the keys can be picked from this list ๐. by default no weight is assigned and Koji picks the default one according to policy.buildinstallcreateisoostreeostree_installer
Example
koji_profile = "koji"
runroot_channel = "runroot"
runroot_tag = "f23-build"
Runroot "openssh" method settings ๐ฆ
Options
runroot_ssh_usernameโ (str) for the openssh runroot method, this configures the username used to log into the remote machine to run the runroot task. defaults to"root". ๐ธrunroot_ssh_hostnamesโ (dict) for the openssh runroot method, this defines the hostname for each architecture on which the runroot task should be running. format:{"x86_64": "runroot-x86-64.localhost.tld", ...}runroot_ssh_init_templateโ (str) [optional] โ for the openssh runroot method, this defines the command to initialize the runroot task on the remote machine. this command is executed as the first command for each runroot task executed.
the command can print a string which is then available as {runroot_key} for other SSH commands. this string might be used to keep the context across different SSH commands executed for a single runroot task. ๐งต
the goal of this command is setting up the environment for the real runroot commands. for example: preparing the unique mock environment, mounting the desired file-systems, โฆ
the command string can contain the following variables, which are replaced by the real values before executing the init command:
{runroot_tag}โ tag to initialize the runroot environment from.
when not set, no init command is executed. ๐
runroot_ssh_install_packages_templateโ (str) [optional] โ for the openssh runroot method, this defines the template for the command to install the packages requested to run the runroot task.
the template string can contain the following variables, which are replaced by the real values before executing the install command:
{runroot_key}โ replaced with the string returned byrunroot_ssh_init_templateif used. this can be used to keep track of the context of SSH commands belonging to a single runroot task.{packages}โ white-list separated list of packages to install.
example (the {runroot_key} is expected to be set to a mock config file using the runroot_ssh_init_template command.): "mock -r {runroot_key} --install {packages}"
when not set, no command to install packages on the remote machine is executed.
runroot_ssh_run_templateโ (str) [optional] โ for the openssh runroot method, this defines the template for the main runroot command.
the template string can contain the following variables, which are replaced by the real values before executing the install command:
{runroot_key}โ replaced with the string returned byrunroot_ssh_init_templateif used. this can be used to keep track of the context of SSH commands belonging to a single runroot task.{command}โ command to run.
example (the {runroot_key} is expected to be set to a mock config file using the runroot_ssh_init_template command.): "mock -r {runroot_key} chroot -- {command}"
when not set, the runroot command is run directly. โก
Extra Files Settings ๐
Options
extra_filesโ (list) references to external files to be placed in theos/directory and media; format:[(variant_uid_regex, {arch|*: [scm_dict]})]. see "Exporting files from SCM" for details. if the dict specifies atargetkey, an additional subdirectory will be used. ๐
Example
extra_files = [
('^.*$', {
'*': [
# GPG keys
{
"scm": "rpm",
"repo": "fedora-repos",
"branch": None,
"file": [
"/etc/pki/rpm-gpg/RPM-GPG-KEY-22-fedora",
],
"target": "",
},
# GPL
{
"scm": "git",
"repo": "https://pagure.io/pungi-fedora",
"branch": None,
"file": [
"GPL",
],
"target": "",
},
],
}),
]
Extra Files Metadata
if extra files are specified, a metadata file, extra_files.json, is placed in the os/ directory and media. the checksums generated are determined by the media_checksums option. this metadata file is in the format:
{
"header": {"version": "1.0},
"data": [
{
"file": "GPL",
"checksums": {
"sha256": "8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643"
},
"size": 18092
},
{
"file": "release-notes/notes.html",
"checksums": {
"sha256": "82b1ba8db522aadf101dca6404235fba179e559b95ea24ff39ee1e5d9a53bdcb"
},
"size": 1120
}
]
}
CreateISO Settings ๐ฅ
Options
createiso_skip = Falseโ (list) mapping that defines which variants and arches to skip during createiso; format:[(variant_uid_regex, {arch|*: True})]createiso_max_size(DEPRECATED) โ (list) this option has been removed ๐ size checks are no longer performed.
format: [(variant_uid_regex, {arch|*: number})]
createiso_max_size_is_strict(DEPRECATED) โ (list) this option has also been removed ๐ size checks are no longer performed.
format: [(variant_uid_regex, {arch|*: bool})]
create_jigdo = Falseโ (bool) controls the creation of jigdo from ISO โจcreate_optional_isos = Falseโ (bool) when set toTrue, ISOs will be created even for optional variants. by default only variants with typevariantorlayered-productwill get ISOs. ๐createiso_break_hardlinks = Falseโ (bool) when set toTrue, all files that should go on the ISO and have a hardlink will be first copied into a staging directory. this should work around a bug ingenisoimageincluding an incorrect link count in the image, but it comes at the cost of having to copy a potentially significant amount of data. ๐ญ
the staging directory is deleted when the ISO is successfully created. in that case, the same task to create the ISO will not be re-runnable.
createiso_use_xorrisofs = Falseโ (bool) when set toTrue, usexorrisofsfor creating ISOs instead ofgenisoimage. ๐iso_size = 4700000000โ (int|str) size of the ISO image. the value should either be an integer meaning size in bytes, or it can be a string withk,M,Gsuffix (using multiples of 1024).iso_levelโ (int|list) [optional] โ set the ISO9660 conformance level. this is either a global single value (a number from1to4), or a variant/arch mapping.split_iso_reserve = 10MiBโ (int|str) how much free space should be left on each disk. the format is the same as for theiso_sizeoption. ๐iso_hfs_ppc64le_compatible = Trueโ (bool) bestie, when you flip this toFalse, the Apple/HFS compatibility gets turned all the way off for ppc64le ISOs. This one only makes sense for bootable products, and it does its thing to images produced in thecreateisoandextra_isosphases. ๐
NOTE: Source architecture needs to be listed explicitly. Excluding '*' applies only on binary arches. Also, Jigdo causes a significant increase of time to ISO creation. Like, significant significant. โณ
Example
createiso_skip = [
('^Workstation$', {
'*': True,
'src': True
}),
]
Automatic generation of version and release โจ
Version and release values for certain artifacts can be generated automatically based on release version, compose label, date, type and respin. Which is honestly such a slay โ it lets you shorten the config and keep it the same for multiple uses instead of copy-pasting the same numbers forever. ๐
Here's the whole vibe check in list form:
- Compose ID
F-Rawhide-20170406.n.0โ Label-, VersionRawhide, Date20170406, Respin0, Release20170406.n.0 - Compose ID
F-26-20170329.1โ LabelAlpha-1.6, Version26_Alpha, Date20170329, Respin1, Release1.6 - Compose ID
F-Atomic-25-20170407.0โ LabelRC-20170407.0, Version25, Date20170407, Respin0, Release20170407.0 - Compose ID
F-Atomic-25-20170407.0โ Label-, Version25, Date20170407, Respin0, Release20170407.0
All non-RC milestones from the label get appended to the version. For the release, either the label is used, or the date, type and respin. That's the whole rule, fr fr. ๐ซถ
Common options for Live Media and Image Build ๐
Okay so all images can have ksurl, version, release and target specified. And since that creates a ton of duplication (like, not me writing the same target forty times), there are global options you can use instead. Main character energy: the fallback chain. ๐
For each of the phases, if the option is not specified for a particular deliverable, an option named <PHASE_NAME>_<OPTION> is checked. If that isn't specified either, the last fallback is global_<OPTION>. And if even that is unset, the value is considered to not be specified at all. No cap.
The kickstart URL is configured by these options:
global_ksurlโ global fallback settinglive_media_ksurlimage_build_ksurl
Target is specified by these settings:
global_targetโ global fallback settinglive_media_targetimage_build_targetosbuild_target
Version is specified by these options. If no version is set, a default value will be provided according to automatic versioning.
global_versionโ global fallback settinglive_media_versionimage_build_versionosbuild_version
Release is specified by these options. If set to a magic value !RELEASE_FROM_LABEL_DATE_TYPE_RESPIN, a value will be generated according to automatic versioning. (Magic value!! She's literally doing magic. ๐ช)
global_releaseโ global fallback settinglive_media_releaseimage_build_releaseosbuild_release
Each configuration block can also optionally specify a failable key. It should be a list of strings containing architectures that are optional. If any deliverable fails on an optional architecture, it will not abort the whole compose โ she's forgiving like that. ๐ And if the list contains only "*", all arches will be substituted.
Live Media Settings ๐ธ
live_mediaโ (dict) configuration for koji spin-livemedia; format:{variant_uid_regex: [{opt:value}]}
Required options, no skipping these bestie:
name(str)version(str)arches([str]) โ what architectures to build the media for; by default uses all arches for the variant.kickstart(str) โ name of the kickstart file
Available options:
ksurl(str)ksversion(str)scratch(bool)target(str)release(str) โ a string with the release, or!RELEASE_FROM_LABEL_DATE_TYPE_RESPINto automatically generate a suitable value. See automatic versioning for details.skip_tag(bool)repo(str|[str]) โ repos specified by URL or variant UIDtitle(str)install_tree_from(str) โ variant to take install tree fromnomacboot(bool)
Image Build Settings ๐ฅ
image_buildโ (dict) config for koji image-build; format:{variant_uid_regex: [{opt: value}]}
By default, images will be built for each binary arch valid for the variant. The config can specify a list of arches to narrow this down if you're feeling picky. ๐ง
NOTE: Config can contain anything what is accepted bykoji image-build --config configfile.iniRepo can be specified either as a string or a list of strings. It will be automatically transformed into format suitable for koji. A repo for the currently built variant will be added as well. If you explicitly set release to!RELEASE_FROM_LABEL_DATE_TYPE_RESPIN, it will be replaced with a value generated as described in automatic versioning. If you explicitly set release to!RELEASE_FROM_DATE_RESPIN, it will be replaced with a value generated as described in automatic versioning. If you explicitly set version to!VERSION_FROM_VERSION, it will be replaced with a value generated as described in automatic versioning. Please don't setinstall_tree. This gets automatically set by pungi based on current variant. You can useinstall_tree_fromkey to use install tree from another variant. Like, just don't. ๐ Both the install tree and repos can use one of following formats: - URL to the location - name of variant in the current compose - absolute path on local filesystem (which will be translated using configured mappings or used unchanged, in which case you have to ensure the koji builders can access it) You can set either a single format, or a list of formats. For available values see help output forkoji image-buildcommand. Ifksurlends with#HEAD, Pungi will figure out the SHA1 hash of current HEAD and use that instead. Iconic behaviour honestly. SettingscratchtoTruewill run the koji tasks as scratch builds.
Example
image_build = {
'^Server$': [
{
'image-build': {
'format': ['docker', 'qcow2']
'name': 'fedora-qcow-and-docker-base',
'target': 'koji-target-name',
'ksversion': 'F23', # value from pykickstart
'version': '23',
# correct SHA1 hash will be put into the URL below automatically
'ksurl': 'https://git.fedorahosted.org/git/spin-kickstarts.git?somedirectoryifany#HEAD',
'kickstart': "fedora-docker-base.ks",
'repo': ["http://someextrarepos.org/repo", "ftp://rekcod.oi/repo"],
'distro': 'Fedora-20',
'disk_size': 3,
# this is set automatically by pungi to os_dir for given variant
# 'install_tree': 'http://somepath',
},
'factory-parameters': {
'docker_cmd': "[ '/bin/bash' ]",
'docker_env': "[ 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' ]",
'docker_labels': "{'Name': 'fedora-docker-base', 'License': u'GPLv2', 'RUN': 'docker run -it --rm ${OPT1} --privileged -v \`pwd\`:/atomicapp -v /run:/run -v /:/host --net=host --name ${NAME} -e NAME=${NAME} -e IMAGE=${IMAGE} ${IMAGE} -v ${OPT2} run ${OPT3} /atomicapp', 'Vendor': 'Fedora Project', 'Version': '23', 'Architecture': 'x86_64' }",
}
},
{
'image-build': {
'format': ['docker', 'qcow2']
'name': 'fedora-qcow-and-docker-base',
'target': 'koji-target-name',
'ksversion': 'F23', # value from pykickstart
'version': '23',
# correct SHA1 hash will be put into the URL below automatically
'ksurl': 'https://git.fedorahosted.org/git/spin-kickstarts.git?somedirectoryifany#HEAD',
'kickstart': "fedora-docker-base.ks",
'repo': ["http://someextrarepos.org/repo", "ftp://rekcod.oi/repo"],
'distro': 'Fedora-20',
'disk_size': 3,
# this is set automatically by pungi to os_dir for given variant
# 'install_tree': 'http://somepath',
}
},
{
'image-build': {
'format': 'qcow2',
'name': 'fedora-qcow-base',
'target': 'koji-target-name',
'ksversion': 'F23', # value from pykickstart
'version': '23',
'ksurl': 'https://git.fedorahosted.org/git/spin-kickstarts.git?somedirectoryifany#HEAD',
'kickstart': "fedora-docker-base.ks",
'distro': 'Fedora-23',
# only build this type of image on x86_64
'arches': ['x86_64']
# Use install tree and repo from Everything variant.
'install_tree_from': 'Everything',
'repo': ['Everything'],
# Set release automatically.
'release': '!RELEASE_FROM_LABEL_DATE_TYPE_RESPIN',
}
}
]
}
๐ฅ KiwiBuild Settings ๐ฅ
kiwibuild
(dict) โ bestie, this is the config for building images with kiwi by way of a Koji plugin. Pungi will trigger a Koji task that delegates to kiwi, kiwi builds the image, and then it gets imported into Koji via content generators. slay. ๐
Format: {variant_uid_regex: [{...}]}.
Required keys in the configuration dict โ like, actually mandatory, no skipping:
kiwi_profileโ (str) select profile from description file.
Description scm, description path and target have to be provided too ๐ซถ but instead of typing them out for each image separately, you can just use the kiwibuild_* options or global_target. iykyk.
Optional keys โ the little extras โจ:
description_scmโ (str) scm URL of description kiwi description.description_pathโ (str) path to kiwi description inside the scm repo.reposโ additional repos used to install RPMs in the image. The compose repository for the enclosing variant is added automatically. Either variant name or a URL is supported. ๐targetโ (str) which build target to use for the task. If not provided, then eitherkiwibuild_targetorglobal_targetis needed.releaseโ (str) release of the output image.archesโ ([str]) List of architectures to build for. If not provided, all variant architectures will be built.failableโ ([str]) List of architectures for which this deliverable is not release blocking.typeโ (str) override default type from the bundle with this value.type_attrโ ([str]) override default attributes for the build type from description.bundle_name_formatโ (str) override default bundle format name.versionโ (str) override version. Follows the same rules as described in automatic versioning.repo_releaseverโ (str) Override default releasever of the output image.manifest_typeโ the image type that is put into the manifest by pungi. If not supplied, an autodetected value will be provided. It may or may not make sense. ๐use_buildroot_repo = Falseโ (bool) whether the task should automatically enable buildroot repository corresponding to the used target.
The options can be set either for the specific image, or at the phase level (see below ๐). Version also falls back to global_version.
kiwibuild_description_scm
(str) โ URL for scm containing the description files
kiwibuild_description_path
(str) โ path to a description file within the description scm
kiwibuild_type
(str) โ override default type from the bundle with this value.
kiwibuild_type_attr
([str]) โ override default attributes for the build type from description.
kiwibuild_bundle_name_format
(str) โ override default bundle format name.
kiwibuild_version
(str) โ overide version for all kiwibuild tasks.
not me leaving the typo in there. it's overide in the original, and we respect the original, fr fr. ๐
kiwibuild_repo_releasever
(str) โ override releasever for all kiwibuild tasks.
kiwibuild_use_buildroot_repo
(bool) โ set enablement of a buildroot repo for all kiwibuild tasks.
๐ Trigger Konflux Pipeline ๐
The config for Konflux pipeline execution lives in the trigger_konflux_pipeline setting, which takes a list where each item specifies a regex for a variant and nests the different pipeline-runs for it. it's giving nesting dolls ๐ช
The tool processes the entire trigger_konflux_pipeline config, initiating all defined pipeline executions in series and then monitoring them. main character energy, honestly. ๐
There are two main pieces of config per pipeline-run:
- Template Specification โ this block defines the exact blueprint for the pipeline, specifying the pipeline name, the necessary bundle reference, and any base parameters required for the pipeline's operation.
- Konflux Instance Configuration โ this identifies the specific Konflux instance where the pipeline will be executed, along with any additional instance-level settings for authentication. ๐
The tool waits for the completion of the concurrently triggered pipelines. It continuously monitors their status, and progress. Final outcome of each pipeline is tracked and reported in the logs. ๐
NOTE: ๐ซถ This phase was initially designed with the creation of bootc images in mind. As a result, the number of parameters available for injection into the PipelineRun manifest is tightly coupled to this specific use case, which may cause problems when running other pipeline types. so like... heads up bestie. ๐
๐ Options ๐
trigger_konflux_pipelineโ (dict) Dictionary compose of{variant_uid_regex: [{...}]}variant_uid_regexโ (list) An list of dictionaries, where each item specifies one pipeline to be run.config_nameโ (str) Logical name for this pipeline configuration. Used to generate the PipelineRun resource name ({config_name}-{variant}-{compose_id}) and to organize artifacts under<config_name>/<variant>/subdirectories in the work and metadata directories. Must be unique across all pipeline configurations. โญpipelinerun_templateโ (scm_dict) Configuration specifying the source and location of the PipelineRun template.konfluxโ (str) Configuration specifying the target Konflux instance and environment for the PipelineRun.kubeconfig_fileโ (str) The Kubernetes/OpenShift configuration file for authentication.kube_contextโ (str) A Kubernetes/OpenShift context from the kubeconfig file.
๐ ๏ธ Prerequisites and Setup ๐ ๏ธ
This phase requires the OpenShift CLI (oc) command to be available in the execution environment. The oc command is used to create and monitor the PipelineRun manifests on the target Konflux instance. no oc, no vibes. ๐ญ
For this task, the phase consumes the following critical parameters:
kubeconfig_filekube_context
The necessary Kube Config file can be easily generated using the utility script found in the Pungi repository:
contrib/oc-config4konflux/gen_new_kubeconf.sh
๐ Execution Flow ๐
- For each variant and pipeline configuration, the phase sources the PipelineRun template, injects parameters, and renders the manifest. โจ
- Rendered manifests are grouped by Kubernetes context and submitted to the cluster using
oc create. If the cluster assigns a different resource name than the one generated by Pungi, the actual name is used for all subsequent monitoring. ๐ - A monitoring thread is spawned per PipelineRun. Each thread polls the PipelineRun status every 30 seconds. If the resource is not found during the initial checks, the thread retries up to 5 times before giving up. Once the resource is available, polling continues until the PipelineRun reaches a terminal state (
SucceededorFailed). ๐ฆ - On success, the phase queries the associated Snapshot resource (linked via the
appstudio.openshift.io/build-pipelinerunlabel) and saves the full YAML to the compose metadata directory, making it available for downstream promotion pipelines (e.g., to create aReleaseCR). ๐
๐ Artifact Directory Structure ๐
Artifacts are organized under structured subdirectories in the work directory and in the compose metadata:
Work directory (templates and rendered manifests):
work/global/konflux/
โโโ <variant>/
โโโ <config_name>/
โโโ <template_file> # sourced template
โโโ rendered-<pipelinerun_name>.yaml # rendered manifest
Compose metadata (Snapshots):
compose/metadata/konflux.json
The konflux.json file contains Snapshot data for all successful PipelineRuns, keyed by variant and config name:
{
"<variant>": {
"<config_name>": {
"pipelinerun_name": "<actual-name>",
"snapshot": { ... full Snapshot resource ... }
}
}
}
๐ง Template Variables ๐ง
The PipelineRun template is a Jinja2 template. The following variables are automatically injected and available for use โ like little gifts, no cap ๐:
compose_idโ (str) The unique identifier for the compose (e.g.,Fedora-40-20250204.0).pipelinerun_nameโ (str) The generated PipelineRun resource name, built from the config name, variant, and compose ID in the format{config_name}-{variant}-{compose_id}.compose_urlโ (str) The translated URL path to the compose top directory, or the local file system path when translation is not configured (e.g.,https://example.com/composes/Fedora-40-20250204.0when translated, or/mnt/koji/compose/Fedora-40-20250204.0when not). ๐release_versionโ (str) The full release version string from therelease_versionconfiguration setting (e.g.,8.4,40).version_majorโ (str) The major version component extracted fromrelease_version(e.g.,8from8.4).version_minorโ (str) The minor version component extracted fromrelease_version(e.g.,4from8.4). Empty string if the version has no minor component.
Additionally, all configuration fields from the trigger_konflux_pipeline item (such as config_name, konflux, etc.) are also available in the template context. so you literally have everything you need in there, bestie. ๐
โจ Example Config โจ
ok so when trigger_konflux_pipeline is all dressed up and ready for the group chat, it looks like this ๐:
trigger_konflux_pipeline = {
"variant_uid_regex": [
{
"config_name": "my-pipeline",
"pipelinerun_template": {
"scm": "git",
"repo": "https://repo/containing/template/file",
"branch": "main",
"file": "path/to/template/inside/repo",
},
"konflux": {
"kubeconfig_file": "path/to/kube/config",
"kube_context": "some-kube-context",
},
},
],
}
โจ Example Template โจ
and here's a whole example Jinja2 template for a PipelineRun resource โ it's giving very much infrastructure bestie energy ๐:
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: {{ pipelinerun_name }}
labels:
app.kubernetes.io/name: compose-pipeline
spec:
pipelineRef:
name: compose-pipeline
params:
- name: compose_id
value: "{{ compose_id }}"
- name: compose_url
value: "{{ compose_url }}"
- name: release_version
value: "{{ release_version }}"
- name: version_major
value: "{{ version_major }}"
{% if version_minor %}
- name: version_minor
value: "{{ version_minor }}"
{% endif %}
๐ OSBuild Composer for building images ๐
osbuildโ (dict) โ the config for building images in the OSBuild Composer service that's fronted by a Koji plugin. Pungi will trigger a Koji task that delegates to the OSBuild Composer, and that Composer builds the image and imports it into Koji via content generators. no cap ๐ซถ
Format: {variant_uid_regex: [{...}]}.
Required keys in the configuration dict (you literally can't skip these, bestie):
nameโ name of the Koji packagedistroโ image for which distribution should be buildTODO examplesimage_typesโ a list with a single image type string, or just a string, representing the image type to build (e.g.qcow2). Either way, only a single image type can be provided as an argument. one (1) image type. that's the vibe ๐
Optional keys:
targetโ which build target to use for the task. Either this option or the globalosbuild_targetis required.versionโ version for the final build (as a string). This option is required if the globalosbuild_versionis not specified.releaseโ release part of the final NVR. If neither this option nor the globalosbuild_releaseis set, Koji will automatically generate a value for you โจrepoโ a list of repositories from which to consume packages for building the image. By default only the variant repository is used. The list items may use one of the following formats:- String with just the repository URL.
- Dictionary with the following keys:
baseurlโ URL of the repository.package_setsโ a list of package set names to use for this repository. Package sets are an internal concept of Image Builder and are used in image definitions. If specified, the repository is used by Image Builder only for the pipeline with the same name. For example, specifying the build package set name will make the repository to be used only for the build environment in which the image will be built. (optional)
archesโ list of architectures for which to build the image. By default, the variant arches are used. This option can only restrict it, not add a new one. so it can shrink the guest list but never expand it, iykyk ๐manifest_typeโ the image type that is put into the manifest by pungi. If not supplied then it is autodetected from the Koji output.ostree_urlโ URL of the repository that's used to fetch the parent commit from.ostree_refโ name of the ostree branchostree_parentโ commit hash or a branch-like reference to the parent commit.customizationsโ a dictionary with customizations to use for the image build. For the list of supported customizations, see the hosted variants in the Image Builder documentationhttps://osbuild.org/docs/user-guide/blueprint-reference#installation-device.upload_optionsโ a dictionary with upload options specific to the target cloud environment. If provided, the image will be uploaded to the cloud environment, in addition to the Koji server. One can't combine arbitrary image types with arbitrary upload options. The dictionary keys differ based on the target cloud environment. The following keys are supported:- AWS EC2 upload options โ upload to Amazon Web Services.
regionโ AWS region to upload the image toshare_with_accountsโ list of AWS account IDs to share the image withsnapshot_nameโ Snapshot name of the uploaded EC2 image (optional)
- AWS S3 upload options โ upload to Amazon Web Services S3.
regionโ AWS region to upload the image to
- Azure upload options โ upload to Microsoft Azure.
tenant_idโ Azure tenant ID to upload the image tosubscription_idโ Azure subscription ID to upload the image toresource_groupโ Azure resource group to upload the image tolocationโ Azure location of the resource group (optional)image_nameโ Image name of the uploaded Azure image (optional)
- GCP upload options โ upload to Google Cloud Platform.
regionโ GCP region to upload the image tobucketโ GCP bucket to upload the image to (optional)share_with_accountsโ list of GCP accounts to share the image withimage_nameโ Image name of the uploaded GCP image (optional)
- Container upload options โ upload to a container registry.
nameโ name of the container image (optional)tagโ container tag to upload the image to (optional)
- AWS EC2 upload options โ upload to Amazon Web Services.
NOTE: There is initial support for having this task as failable without aborting the whole compose. This can be enabled by setting "failable": ["*"] in the config for the image. It is an on/off switch without granularity per arch. so it's all-or-nothing, no per-arch slay ๐ญ
๐ธ Image Builder Settings ๐ธ
imagebuilderโ (dict) โ the config for building images with thekoji-image-builderKoji plugin. Pungi will trigger a Koji task which will build the image with the given configuration using theimage-builderexecutable in the build root. iconic ๐
Format: {variant_uid_regex: [{...}]}.
Required keys in the configuration dict:
nameโ name of the Koji packagetypesโ a list with a single image type string representing the image type to build (e.g.qcow2). Only a single image type can be provided as an argument. one type, that's the rule ๐
Optional keys:
targetโ which build target to use for the task. Either this option, the globalimagebuilder_target, or globalglobal_targetis required.versionโ version for the final build (as a string). This option is required if the globalimagebuilder_versionor itsglobal_versionequivalent are not specified.releaseโ release part of the final NVR. If neither this option nor the globalimagebuilder_releasenor itsglobal_releaseequivalent are set, Koji will automatically generate a value.reposโ a list of repositories from which to consume packages for building the image. By default only the variant repository is used. The list items use the following formats:- String with just the repository URL.
- Variant ID in the current compose.
archesโ list of architectures for which to build the image. By default, the variant arches are used. This option can only restrict it, not add a new one.seedโ An integer that can be used to make builds more reproducible. Whenimage-builderbuilds images various bits and bobs are generated with a PRNG (partition uuids, etc). Pinning the seed with this argument orimagebuilder_seedto do so globally will make builds use the same random values each time. Note that using seed requires the Koji side to have at leastkoji-image-builder >= 7deployed. reproducibility era โจscratchโ A boolean to instructkoji-image-builderto perform scratch builds. This might have implications on garbage collection within the koji instance you're targeting. Can also be set globally throughimagebuilder_scratch.ostreeโ A dictionary describing where to get ostree content when applicable. The dictionary contains the following keys:urlโ URL of the repository that's used to fetch the parent commit from.refโ Name of an ostree branch or tag
blueprintโ A dictionary with a blueprint to use for the image build. Blueprints can customize images beyond their initial definition. For the list of supported customizations, see external Documentation
NOTE: There is initial support for having this task as failable without aborting the whole compose. This can be enabled by setting "failable": ["*"] in the config for the image. It is an on/off switch without granularity per arch. same energy as above, bestie ๐
โจ Example Config โจ
and the moment you've been waiting for, the actual config snippet, looking cute ๐:
imagebuilder_target = 'f43-image-builder'
imagebuilder_seed = 43
imagebuilder_scratch = True
imagebuilder = {
"^IoT$": [
{
"name": "%s-raw" % release_name,
"types": ["iot-raw-xz"],
"arches": ["x86_64"], #, "aarch64"],
"repos": ["https://kojipkgs.fedoraproject.org/compose/rawhide/latest-Fedora-Rawhide/compose/Everything/$arch/os/"],
"ostree": {
"url": "https://kojipkgs.fedoraproject.org/compose/iot/repo/",
"ref": "fedora/rawhide/$arch/iot",
},
"subvariant": "IoT",
"failable": ["*"],
},
]
}
๐ฆ OSTree Settings ๐ฆ
The ostree phase of Pungi can create and update ostree repos, bestie. It does this by running rpm-ostree compose inside a Koji runroot environment. Heads up though: the ostree repo itself is not part of the compose โ it lives in its own separate directory. Any shiny new packages that show up in the compose get added to the repo with a brand new commit. โจ
ostreeโ (dict) a mapping of configuration for each variant. The format should be{variant_uid_regex: config_dict}. And yes, you can pass a list of config dicts too, flexible queen. ๐
The config dict for each variant/arch pair must have these keys:
treefileโ (str) filename of configuration forrpm-ostree.config_urlโ (str) URL for the Git repository with the treefile.repoโ (str | dict | [str | dict]) repos specified by URL, or a dict of repo options.baseurlis required in the dict.ostree_repoโ (str) where to put the ostree repository.
These keys are optional:
keep_original_sourcesโ (bool) keep the existing source repos in the tree config file. If it's not enabled, all the original source repos get yeeted from the tree config file. ๐ซถconfig_branchโ (str) Git branch of the repo to use. Defaults tomaster.archesโ ([str]) list of architectures to update ostree for. There will be one task per architecture. By default, all architectures in the variant are used.failableโ ([str]) list of architectures for which this deliverable is not release blocking.update_summaryโ (bool) update summary metadata after tree composing. Defaults toFalse.force_new_commitโ (bool) do not use rpm-ostree's built-in change detection. Defaults toFalse.unified_coreโ (bool) use rpm-ostree in unified core mode for composes. Defaults toFalse.versionโ (str) version string to be added as versioning metadata. If this option is set to!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN, a value will be generated automatically as$VERSION.$RELEASE. If this option is set to!VERSION_FROM_VERSION_DATE_RESPIN, a value will be generated automatically as$VERSION.$DATE.$RESPIN. See how those values are created.tag_refโ (bool, defaultTrue) if set toFalse, a git reference will not be created.ostree_refโ (str) to override the valuereffrom treefile.runroot_packagesโ (list) a list of additional package names to be installed in the runroot environment in Koji.
๐ธ Example config ๐ธ
ostree = {
"^Atomic$": {
"treefile": "fedora-atomic-docker-host.json",
"config_url": "https://git.fedorahosted.org/git/fedora-atomic.git",
"keep_original_sources": True,
"repo": [
"http://example.com/repo/x86_64/os",
{"baseurl": "http://example.com/linux/repo", "exclude": "systemd-container"},
],
"ostree_repo": "/mnt/koji/compose/atomic/Rawhide/",
"update_summary": True,
# Automatically generate a reasonable version
"version": "!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN",
# Only run this for x86_64 even if Atomic has more arches
"arches": ["x86_64"],
}
}
ostree_use_koji_plugin = Falseโ (bool) when set toTrue, the Kojipungi_ostreetask will be used to executerpm-ostreeinstead of runroot. โ ๏ธ Use this only if the Koji instance actually has thepungi_ostreeplugin installed, no cap.
๐ OSTree Native Container Settings ๐
The ostree_container phase of Pungi can create an ostree native container image as an OCI archive. It does this by running rpm-ostree compose image in a Koji runroot environment.
So here's the tea, bestie: rpm-ostree could use info from previously built images to make the container layer split better, but we can't use that functionality until https://github.com/containers/skopeo/pull/2114 gets resolved. ๐ญ So every single invocation just creates a brand new OCI archive image from scratch. Fr fr.
ostree_containerโ (dict) a mapping of configuration for each variant. The format should be{variant_uid_regex: config_dict}. A list of config dicts works too, iykyk.
The config dict for each variant/arch pair must have these keys:
treefileโ (str) filename of configuration forrpm-ostree.config_urlโ (str) URL for the Git repository with the treefile.
These keys are optional:
repoโ (str | dict | [str | dict]) repos specified by URL, or a dict of repo options.baseurlis required in the dict.keep_original_sourcesโ (bool) keep the existing source repos in the tree config file. If it's not enabled, all the original source repos get removed from the tree config file.config_branchโ (str) Git branch of the repo to use. Defaults tomain. (yes,main, notmasterโ it's different from the ostree one, don't @ me)archesโ ([str]) list of architectures to generate ostree native container images for. There will be one task per architecture. By default, all architectures in the variant are used.failableโ ([str]) list of architectures for which this deliverable is not release blocking.versionโ (str) version string to be added to the OCI archive name. If this option is set to!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN, a value will be generated automatically as$VERSION.$RELEASE. If this option is set to!VERSION_FROM_VERSION_DATE_RESPIN, a value will be generated automatically as$VERSION.$DATE.$RESPIN. See how those values are created.tag_refโ (bool, defaultTrue) if set toFalse, a git reference will not be created.runroot_packagesโ (list) a list of additional package names to be installed in the runroot environment in Koji.subvariantโ (str) the subvariant value to be used in the metadata for the image. Also used in the image's filename, unless it's overridden byname. Defaults to being the same as the variant. โ ๏ธ If you're building more than one ostree container in a variant, each one must have a unique subvariant.nameโ (str) the base for the image's filename. To produce the complete filename, the image's architecture, the version string, and the format suffix get appended to this. Defaults to the value ofrelease_shortand the subvariant, joined by a dash.
๐ Example config ๐
ostree_container = {
"^Sagano$": {
"treefile": "fedora-tier-0-38.yaml",
"config_url": "https://gitlab.com/CentOS/cloud/sagano.git",
"config_branch": "main",
"repo": [
"http://example.com/repo/x86_64/os",
{"baseurl": "http://example.com/linux/repo", "exclude": "systemd-container"},
],
# Automatically generate a reasonable version
"version": "!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN",
# Only run this for x86_64 even if Sagano has more arches
"arches": ["x86_64"],
}
}
ostree_container_use_koji_plugin = Falseโ (bool) when set toTrue, the Kojipungi_ostreetask will be used to executerpm-ostreeinstead of runroot. โ ๏ธ Use it only if the Koji instance has thepungi_ostreeplugin installed.
๐ง Ostree Installer Settings ๐ง
The ostree_installer phase of Pungi can produce an installer image that bundles an OSTree repository. This one always runs in Koji as a runroot task. Periodt.
ostree_installerโ (dict) a variant/arch mapping of configuration. The format should be[(variant_uid_regex, {arch|*: config_dict})].
The config dict for each variant/arch pair must have this key:
These keys are optional:
repoโ (str | [str]) repos specified by URL or variant UID.releaseโ (str) release value to set for the installer image. Set it to!RELEASE_FROM_LABEL_DATE_TYPE_RESPINto generate the value automatically.failableโ ([str]) list of architectures for which this deliverable is not release blocking.
These optional keys get passed to lorax to customize the build. ๐
installpkgsโ ([str])add_templateโ ([str])add_arch_templateโ ([str])add_template_varโ ([str])add_arch_template_varโ ([str])rootfs_sizeโ ([str])template_repoโ (str) Git repository with extra templates.template_branchโ (str) branch to use fromtemplate_repo.
So the templates can either be absolute paths โ in which case they'll be used exactly as configured โ or they can be relative paths, in which case template_repo needs to point to a Git repository to take the templates from. Slay. โจ
If the templates need to run with additional dependencies, that's configurable with the optional key:
extra_runroot_pkgsโ ([str])skip_brandingโ (bool) stopsloraxfrom installing packages with branding. Defaults toFalse.ostree_installer_overwrite = Falseโ (bool) by default, if a variant that includes an OSTree installer also creates regular installer images in thebuildinstallphase, there will be conflicts (because the files get put in the same place) and Pungi will report an error and fail the compose. ๐
With this option you can opt in to the overwriting. The traditional boot.iso will be in the iso/ subdirectory.
ostree_installer_use_koji_plugin = Falseโ (bool) โ set this toTrueand the Kojipungi_buildinstalltask runs Lorax instead of runroot. Only use it if your Koji instance actually has thepungi_buildinstallplugin installed, bestie ๐ซถ
๐ซ Example config
ostree_installer = [
("^Atomic$", {
"x86_64": {
"repo": [
"Everything",
"https://example.com/extra-repo1.repo",
"https://example.com/extra-repo2.repo",
],
"release": "!RELEASE_FROM_LABEL_DATE_TYPE_RESPIN",
"installpkgs": ["fedora-productimg-atomic"],
"add_template": ["atomic-installer/lorax-configure-repo.tmpl"],
"add_template_var": [
"ostree_osname=fedora-atomic",
"ostree_ref=fedora-atomic/Rawhide/x86_64/docker-host",
],
"add_arch_template": ["atomic-installer/lorax-embed-repo.tmpl"],
"add_arch_template_var": [
"ostree_repo=https://kojipkgs.fedoraproject.org/compose/atomic/Rawhide/",
"ostree_osname=fedora-atomic",
"ostree_ref=fedora-atomic/Rawhide/x86_64/docker-host",
]
'template_repo': 'https://git.fedorahosted.org/git/spin-kickstarts.git',
'template_branch': 'f24',
}
})
]
๐ Extra ISOs
Okay so picture this: you want one ISO image that pulls packages from multiple variants. Iconic, right? That ISO still belongs to exactly one variant, and it gets stored in that variant's ISO directory. No cap.
The ISO will be bootable if the buildinstall or boot_iso phase runs for the parent variant โ and it reuses that variant's boot configuration. โจ
extra_isosโ (dict) โ a mapping from variant UID regex to a list of configuration blocks. ๐include_variantsโ (list) list of variant UIDs whose content should get added to the ISO; the variant of this image is added automatically.
All the other config keys are optional, bestie.
filenameโ (str) template for naming the image. On top of the regular placeholders,filenameis available with the name generated using theimage_name_formatoption.volidโ (str) template for generating the volume ID. Again thevolidplaceholder can be used just like for the file name. This can also be a list of templates that get tried one after another until one generates a volume ID that fits inside the 32 character limit.extra_filesโ (list) a list ofscm_dictobjects. These files get dropped into the top level directory of the image.archesโ (list) a list of architectures to build this image for. By default all arches from the variant get used. This option lets you limit them.failable_archesโ (list) a list of architectures where the image can fail to be generated without failing the entire compose. ๐ฎโ๐จskip_srcโ (bool) lets you disable creating an image with source packages.inherit_extra_filesโ (bool) by default extra files in variants are ignored. If you want them in the ISO, set this toTrue.max_size(DEPRECATED) โ (int) This option has been removed. Size checks are no longer performed. Periodt.
โจ Example config
extra_isos = {
'Server': [{
# Will generate foo-DP-1.0-20180510.t.43-Server-x86_64-dvd1.iso
'filename': 'foo-{filename}',
'volid': 'foo-{arch}',
'extra_files': [{
'scm': 'git',
'repo': 'https://pagure.io/pungi.git',
'file': 'setup.py'
}],
'include_variants': ['Client']
}]
}
# This should create image with the following layout:
# .
# โโโ Client
# โ โโโ Packages
# โ โ โโโ a
# โ โ โโโ b
# โ โโโ repodata
# โโโ Server
# โ โโโ Packages
# โ โ โโโ a
# โ โ โโโ b
# โ โโโ repodata
# โโโ setup.py
๐ธ Media Checksums Settings
media_checksumsโ (list) โ list of checksum types to compute. Allowed values are anything supported by Python'shashlibmodule (see the docs for the deets). ๐ฅmedia_checksum_one_fileโ (bool) โ whenTrue, only oneCHECKSUMfile gets created per directory; this option requiresmedia_checksumsto specify only one type.media_checksum_base_filenameโ (str) โ when this isn't set, all checksums get saved to a file named eitherCHECKSUMor based on the digest type; this option lets you add any prefix to that name.
You can use format strings that get replaced by actual values. The allowed keys are:
archcompose_iddatelabellabel_major_versionrelease_shortrespintypetype_suffixversiondirname(only ifmedia_checksum_one_fileis enabled)
For example, for Fedora the prefix should be %(release_short)s-%(variant)s-%(version)s-%(date)s%(type_suffix)s.%(respin)s. ๐
๐ฆ Translate Paths Settings
translate_pathsโ (list) โ list of paths to translate; format:[(path, translated_path)]
NOTE: This feature gets useful when you need to transform a compose location into, say, an HTTP repo that can be passed to kojiimage-build. The path part is normalized viaos.path.normpath(). ๐ง
โจ Example config
translate_paths = [
("/mnt/a", "http://b/dir"),
]
๐ Example usage
>>> from pungi.util import translate_paths
>>> print translate_paths(compose_object_with_mapping, "/mnt/a/c/somefile")
http://b/dir/c/somefile
๐ Miscellaneous Settings
paths_moduleโ (str) โ Name of a Python module implementing the same interface aspungi.paths. This module can be used to override where things are placed. ๐link_type = hardlink-or-copyโ (str) โ Method of putting packages into the compose directory.
Available options:
hardlink-or-copyhardlinkcopysymlinkabspath-symlink
skip_phasesโ (list) โ List of phase names that should be skipped. Same functionality is also available via a command line option.release_discinfo_descriptionโ (str) โ Override the description in.discinfofiles. The value is a format string accepting%(variant_name)sand%(arch)splaceholders.symlink_isos_toโ (str) โ If set, the ISO files from thebuildinstallandcreateisophases get put into this destination, and a symlink pointing to this location gets created in the actual compose directory. ๐dogpile_cache_backendโ (str) โ If set, Pungi uses the configured Dogpile cache backend to cache various data between multiple Pungi calls. This can make Pungi faster if you're running more similar composes regularly in a short time. โก
For the list of available backends, please see the https://dogpilecache.readthedocs.io documentation.
Most typical configuration uses the dogpile.cache.dbm backend.
dogpile_cache_argumentsโ (dict) โ Arguments to be used when creating the Dogpile cache backend. See that particular backend's configuration for the list of possible key/value pairs.
For the dogpile.cache.dbm backend, the value can be for example the following:
{
"filename": "/tmp/pungi_cache_file.dbm"
}
dogpile_cache_expiration_timeโ (int) โ Defines the default expiration time in seconds of data stored in the Dogpile cache. Defaults to3600seconds. ๐ซ
๐ BIG PICTURE EXAMPLES
Real Pungi config files get very large, bestie. This page brings two examples of (almost) full configuration for two different composes. Let's gooo. ๐ซถ
โญ Fedora Rawhide compose
This is a shortened configuration for Fedora Radhide compose as of 2019-10-14.
release_name = 'Fedora'
release_short = 'Fedora'
release_version = 'Rawhide'
release_is_layered = False
bootable = True
comps_file = {
'scm': 'git',
'repo': 'https://pagure.io/fedora-comps.git',
'branch': 'master',
'file': 'comps-rawhide.xml',
# Merge translations by running make. This command will generate the file.
'command': 'make comps-rawhide.xml'
}
module_defaults_dir = {
'scm': 'git',
'repo': 'https://pagure.io/releng/fedora-module-defaults.git',
'branch': 'main',
'dir': '.'
}
# Optional module obsoletes configuration which is merged
# into the module index and gets resolved
module_obsoletes_dir = {
'scm': 'git',
'repo': 'https://pagure.io/releng/fedora-module-defaults.git',
'branch': 'main',
'dir': 'obsoletes'
}
variants_file='variants-fedora.xml'
sigkeys = ['12C944D0']
# Put packages into subdirectories hashed by their initial letter.
hashed_directories = True
# There is a special profile for use with compose. It makes Pungi
# authenticate automatically as rel-eng user.
koji_profile = 'compose_koji'
# RUNROOT settings
runroot = True
runroot_channel = 'compose'
runroot_tag = 'f32-build'
# PKGSET
pkgset_source = 'koji'
pkgset_koji_tag = 'f32'
pkgset_koji_inherit = False
filter_system_release_packages = False
# GATHER
gather_method = {
'^.*': { # For all variants
'comps': 'deps', # resolve dependencies for packages from comps file
'module': 'nodeps', # but not for packages from modules
}
}
gather_backend = 'dnf'
gather_profiler = True
check_deps = False
greedy_method = 'build'
repoclosure_backend = 'dnf'
# CREATEREPO
createrepo_deltas = False
createrepo_database = True
createrepo_use_xz = True
createrepo_extra_args = ['--zck', '--zck-dict-dir=/usr/share/fedora-repo-zdicts/rawhide']
...and then it just keeps going. Bestie, this config is a whole personality and we're still in it. ๐ Let's keep reading the example, chunk by chunk, because the comments in here are basically the manpage explaining itself.
# CHECKSUMS
media_checksums = ['sha256']
media_checksum_one_file = True
media_checksum_base_filename = '%(release_short)s-%(variant)s-%(version)s-%(arch)s-%(date)s%(type_suffix)s.%(respin)s'
# CREATEISO
iso_hfs_ppc64le_compatible = False
# BUILDINSTALL
buildinstall_method = 'lorax'
buildinstall_skip = [
# No installer for Modular variant
('^Modular$', {'*': True}),
# No 32 bit installer for Everything.
('^Everything$', {'i386': True}),
]
Ok so media_checksums says ['sha256'], media_checksum_one_file is True, and the base filename template is '%(release_short)s-%(variant)s-%(version)s-%(arch)s-%(date)s%(type_suffix)s.%(respin)s'. Then under the # CREATEISO comment, iso_hfs_ppc64le_compatible is False.
For # BUILDINSTALL: buildinstall_method is 'lorax', and buildinstall_skip skips things by pattern โ no installer for the ^Modular$ variant (everything, {'*': True}) and no 32-bit installer for ^Everything$ ({'i386': True}). Slay.
Next up, the lorax corner of the config:
# Enables macboot on x86_64 for all variants and disables upgrade image building
# everywhere.
lorax_options = [
('^.*$', {
'x86_64': {
'nomacboot': False
},
'ppc64le': {
# Use 3GB image size for ppc64le.
'rootfs_size': 3
},
'*': {
'noupgrade': True
}
})
]
So lorax_options here matches every variant ('^.*$') and, per the comment, enables macboot on x86_64 for all variants and disables upgrade image building everywhere. For x86_64 it sets 'nomacboot': False, for ppc64le it sets 'rootfs_size': 3 (use 3GB image size for ppc64le), and for '*' it sets 'noupgrade': True.
And the package-tweaking block, which is so much:
additional_packages = [
('^(Server|Everything)$', {
'*': [
# Add all architectures of dracut package.
'dracut.*',
# All all packages matching this pattern
'autocorr-*',
],
}),
('^Everything$', {
# Everything should include all packages from the tag. This only
# applies to the native arch. Multilib will still be pulled in
# according to multilib rules.
'*': ['*'],
}),
]
additional_packages: for ^(Server|Everything)$, on '*' arches it adds 'dracut.*' (add all architectures of dracut package) and 'autocorr-*' (all all packages matching this pattern โ that's the original's wording, we keep it fr fr). For ^Everything$, '*': ['*'] means Everything should include all packages from the tag, and per the comment this only applies to the native arch. Multilib will still be pulled in according to multilib rules.
Then the filtering, multilib rules, and blacklist/whitelist:
filter_packages = [
("^.*$", {"*": ["glibc32", "libgcc32"]}),
('(Server)$', {
'*': [
'kernel*debug*',
'kernel-kdump*',
]
}),
]
multilib = [
('^Everything$', {
'x86_64': ['devel', 'runtime'],
})
]
# These packages should never be multilib on any arch.
multilib_blacklist = {
'*': [
'kernel', 'kernel-PAE*', 'kernel*debug*', 'java-*', 'php*', 'mod_*', 'ghc-*'
],
}
# These should be multilib even if they don't match the rules defined above.
multilib_whitelist = {
'*': ['wine', '*-static'],
}
filter_packages filters "glibc32" and "libgcc32" for every variant ("^.*$" on "*"), and for '(Server)$' filters out 'kernel*debug*' and 'kernel-kdump*'. multilib gives ^Everything$ the 'x86_64' groups ['devel', 'runtime'].
multilib_blacklist is packages that should never be multilib on any arch: 'kernel', 'kernel-PAE*', 'kernel*debug*', 'java-*', 'php*', 'mod_*', 'ghc-*' for '*'. multilib_whitelist is the ones that should be multilib even if they don't match the rules defined above: 'wine' and '*-static' for '*'. Obsessed with how tidy that is. โจ
createiso_skip = [
# Keep binary ISOs for Server, but not source ones.
('^Server$', {'src': True}),
# Remove all other ISOs.
('^Everything$', {'*': True, 'src': True}),
('^Modular$', {'*': True, 'src': True}),
]
createiso_skip: keep binary ISOs for Server, but not source ones (('^Server$', {'src': True})), then remove all other ISOs โ ('^Everything$', {'*': True, 'src': True}) and ('^Modular$', {'*': True, 'src': True}).
Now the naming block, where the comment spills the whole tea about volume IDs:
# Image name respecting Fedora's image naming policy
image_name_format = '%(release_short)s-%(variant)s-%(disc_type)s-%(arch)s-%(version)s-%(date)s%(type_suffix)s.%(respin)s.iso'
# Use the same format for volume id
image_volid_formats = [
'%(release_short)s-%(variant)s-%(disc_type)s-%(arch)s-%(version)s'
]
# Used by Pungi to replace 'Cloud' with 'C' (etc.) in ISO volume IDs.
# There is a hard 32-character limit on ISO volume IDs, so we use
# these to try and produce short enough but legible IDs. Note this is
# duplicated in Koji for live images, as livemedia-creator does not
# allow Pungi to tell it what volume ID to use. Note:
# https://fedoraproject.org/wiki/User:Adamwill/Draft_fedora_image_naming_policy
volume_id_substitutions = {
'Beta': 'B',
'Rawhide': 'rawh',
'Silverblue': 'SB',
'Cinnamon': 'Cinn',
'Cloud': 'C',
'Design_suite': 'Dsgn',
'Electronic_Lab': 'Elec',
'Everything': 'E',
'Scientific_KDE': 'SciK',
'Security': 'Sec',
'Server': 'S',
'Workstation': 'WS',
}
disc_types = {
'boot': 'netinst',
'live': 'Live',
}
translate_paths = [
('/mnt/koji/compose/', 'https://kojipkgs.fedoraproject.org/compose/'),
]
image_name_format is the image name respecting Fedora's image naming policy: '%(release_short)s-%(variant)s-%(disc_type)s-%(arch)s-%(version)s-%(date)s%(type_suffix)s.%(respin)s.iso'. image_volid_formats uses the same format for volume id: ['%(release_short)s-%(variant)s-%(disc_type)s-%(arch)s-%(version)s'].
volume_id_substitutions is used by Pungi to replace 'Cloud' with 'C' (etc.) in ISO volume IDs. Per the comment: there is a hard 32-character limit on ISO volume IDs, so we use these to try and produce short enough but legible IDs. Note this is duplicated in Koji for live images, as livemedia-creator does not allow Pungi to tell it what volume ID to use. And there's a link note: https://fedoraproject.org/wiki/User:Adamwill/Draft_fedora_image_naming_policy. The substitutions are: 'Beta': 'B', 'Rawhide': 'rawh', 'Silverblue': 'SB', 'Cinnamon': 'Cinn', 'Cloud': 'C', 'Design_suite': 'Dsgn', 'Electronic_Lab': 'Elec', 'Everything': 'E', 'Scientific_KDE': 'SciK', 'Security': 'Sec', 'Server': 'S', 'Workstation': 'WS'.
disc_types maps 'boot': 'netinst' and 'live': 'Live'. translate_paths maps ('/mnt/koji/compose/', 'https://kojipkgs.fedoraproject.org/compose/'). Iconic.
The globals that get inherited everywhere (iykyk):
# These will be inherited by live_media, live_images and image_build
global_ksurl = 'git+https://pagure.io/fedora-kickstarts.git?#HEAD'
global_release = '!RELEASE_FROM_LABEL_DATE_TYPE_RESPIN'
global_version = 'Rawhide'
# live_images ignores this in favor of live_target
global_target = 'f32'
These will be inherited by live_media, live_images and image_build: global_ksurl is 'git+https://pagure.io/fedora-kickstarts.git?#HEAD', global_release is '!RELEASE_FROM_LABEL_DATE_TYPE_RESPIN', global_version is 'Rawhide', and global_target is 'f32' โ with the note that live_images ignores this in favor of live_target.
Container image building time:
image_build = {
'^Container$': [
{
'image-build': {
'format': [('docker', 'tar.xz')],
'name': 'Fedora-Container-Base',
'kickstart': 'fedora-container-base.ks',
'distro': 'Fedora-22',
'disk_size': 5,
'arches': ['armhfp', 'aarch64', 'ppc64le', 's390x', 'x86_64'],
'repo': 'Everything',
'install_tree_from': 'Everything',
'subvariant': 'Container_Base',
'failable': ['*'],
},
'factory-parameters': {
'dockerversion': "1.10.1",
'docker_cmd': '[ "/bin/bash" ]',
'docker_env': '[ "DISTTAG=f32container", "FGC=f32", "container=oci" ]',
'docker_label': '{ "name": "fedora", "license": "MIT", "vendor": "Fedora Project", "version": "32"}',
},
},
],
}
For '^Container$', image_build has an 'image-build' dict: 'format': [('docker', 'tar.xz')], 'name': 'Fedora-Container-Base', 'kickstart': 'fedora-container-base.ks', 'distro': 'Fedora-22', 'disk_size': 5, 'arches': ['armhfp', 'aarch64', 'ppc64le', 's390x', 'x86_64'], 'repo': 'Everything', 'install_tree_from': 'Everything', 'subvariant': 'Container_Base', 'failable': ['*']. Plus 'factory-parameters': 'dockerversion': "1.10.1", 'docker_cmd': '[ "/bin/bash" ]', 'docker_env': '[ "DISTTAG=f32container", "FGC=f32", "container=oci" ]', 'docker_label': '{ "name": "fedora", "license": "MIT", "vendor": "Fedora Project", "version": "32"}'. Ate, no crumbs. ๐
Live media, where the comments really spill:
live_media = {
'^Workstation$': [
{
'name': 'Fedora-Workstation-Live',
'kickstart': 'fedora-live-workstation.ks',
# Variants.xml also contains aarch64 and armhfp, but there
# should be no live media for those arches.
'arches': ['x86_64', 'ppc64le'],
'failable': ['ppc64le'],
# Take packages and install tree from Everything repo.
'repo': 'Everything',
'install_tree_from': 'Everything',
}
],
'^Spins': [
# There are multiple media for Spins variant. They use subvariant
# field so that they can be identified in the metadata.
{
'name': 'Fedora-KDE-Live',
'kickstart': 'fedora-live-kde.ks',
'arches': ['x86_64'],
'repo': 'Everything',
'install_tree_from': 'Everything',
'subvariant': 'KDE'
},
{
'name': 'Fedora-Xfce-Live',
'kickstart': 'fedora-live-xfce.ks',
'arches': ['x86_64'],
'failable': ['*'],
'repo': 'Everything',
'install_tree_from': 'Everything',
'subvariant': 'Xfce'
},
],
}
For '^Workstation$': 'name': 'Fedora-Workstation-Live', 'kickstart': 'fedora-live-workstation.ks', and per the comment Variants.xml also contains aarch64 and armhfp, but there should be no live media for those arches โ so 'arches': ['x86_64', 'ppc64le'], 'failable': ['ppc64le'], and (take packages and install tree from Everything repo) 'repo': 'Everything' and 'install_tree_from': 'Everything'.
For '^Spins' โ per the comment there are multiple media for Spins variant. They use subvariant field so that they can be identified in the metadata. โ there's 'Fedora-KDE-Live' with 'kickstart': 'fedora-live-kde.ks', 'arches': ['x86_64'], 'repo': 'Everything', 'install_tree_from': 'Everything', 'subvariant': 'KDE'. And 'Fedora-Xfce-Live' with 'kickstart': 'fedora-live-xfce.ks', 'arches': ['x86_64'], 'failable': ['*'], 'repo': 'Everything', 'install_tree_from': 'Everything', 'subvariant': 'Xfce'. The range bestie, the range. ๐
failable_deliverables = [
# Installer and ISOs for server failing do not abort the compose.
('^Server$', {
'*': ['buildinstall', 'iso'],
}),
('^.*$', {
# Buildinstall is not blocking
'src': ['buildinstall'],
# Nothing on i386, ppc64le blocks the compose
'i386': ['buildinstall', 'iso'],
'ppc64le': ['buildinstall', 'iso'],
's390x': ['buildinstall', 'iso'],
})
]
failable_deliverables: for '^Server$', '*': ['buildinstall', 'iso'] โ installer and ISOs for server failing do not abort the compose. For '^.*$': 'src': ['buildinstall'] (buildinstall is not blocking), and 'i386': ['buildinstall', 'iso'], 'ppc64le': ['buildinstall', 'iso'], 's390x': ['buildinstall', 'iso'] โ nothing on i386, ppc64le blocks the compose.
Now the ostree trio, and honestly it's giving main character energy:
ostree = {
"^Silverblue$": {
"version": "!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN",
# To get config, clone master branch from this repo and take
# treefile from there.
"treefile": "fedora-silverblue.yaml",
"config_url": "https://pagure.io/workstation-ostree-config.git",
"config_branch": "master",
# Consume packages from Everything
"repo": "Everything",
# Don't create a reference in the ostree repo (signing automation does that).
"tag_ref": False,
# Don't use change detection in ostree.
"force_new_commit": True,
# Use unified core mode for rpm-ostree composes
"unified_core": True,
# This is the location for the repo where new commit will be
# created. Note that this is outside of the compose dir.
"ostree_repo": "/mnt/koji/compose/ostree/repo/",
"ostree_ref": "fedora/rawhide/${basearch}/silverblue",
"arches": ["x86_64", "ppc64le", "aarch64"],
"failable": ['*'],
}
}
ostree for "^Silverblue$": "version": "!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN"; "treefile": "fedora-silverblue.yaml" (to get config, clone master branch from this repo and take treefile from there); "config_url": "https://pagure.io/workstation-ostree-config.git"; "config_branch": "master"; "repo": "Everything" (consume packages from Everything); "tag_ref": False (don't create a reference in the ostree repo (signing automation does that)); "force_new_commit": True (don't use change detection in ostree); "unified_core": True (use unified core mode for rpm-ostree composes); "ostree_repo": "/mnt/koji/compose/ostree/repo/" (this is the location for the repo where new commit will be created. Note that this is outside of the compose dir.); "ostree_ref": "fedora/rawhide/${basearch}/silverblue"; "arches": ["x86_64", "ppc64le", "aarch64"]; "failable": ['*'].
ostree_container = {
"^Sagano$": {
"treefile": "fedora-tier-0-38.yaml",
"config_url": "https://gitlab.com/CentOS/cloud/sagano.git",
"config_branch": "main",
# Consume packages from Everything
"repo": "Everything",
# Automatically generate a reasonable version
"version": "!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN",
# Only run this for x86_64 even if Sagano has more arches
"arches": ["x86_64"],
}
}
ostree_container for "^Sagano$": "treefile": "fedora-tier-0-38.yaml", "config_url": "https://gitlab.com/CentOS/cloud/sagano.git", "config_branch": "main", "repo": "Everything" (consume packages from Everything), "version": "!OSTREE_VERSION_FROM_LABEL_DATE_TYPE_RESPIN" (automatically generate a reasonable version), and "arches": ["x86_64"] (only run this for x86_64 even if Sagano has more arches).
And the ostree installer, which is the longest of them, of course it is:
ostree_installer = [
("^Silverblue$", {
"x86_64": {
"repo": "Everything",
"release": None,
"rootfs_size": "8",
# Take templates from this repository.
'template_repo': 'https://pagure.io/fedora-lorax-templates.git',
'template_branch': 'master',
# Use following templates.
"add_template": ["ostree-based-installer/lorax-configure-repo.tmpl",
"ostree-based-installer/lorax-embed-repo.tmpl",
"ostree-based-installer/lorax-embed-flatpaks.tmpl"],
# And add these variables for the templates.
"add_template_var": [
"ostree_install_repo=https://kojipkgs.fedoraproject.org/compose/ostree/repo/",
"ostree_update_repo=https://ostree.fedoraproject.org",
"ostree_osname=fedora",
"ostree_oskey=fedora-32-primary",
"ostree_contenturl=mirrorlist=https://ostree.fedoraproject.org/mirrorlist",
"ostree_install_ref=fedora/rawhide/x86_64/silverblue",
"ostree_update_ref=fedora/rawhide/x86_64/silverblue",
"flatpak_remote_name=fedora",
"flatpak_remote_url=oci+https://registry.fedoraproject.org",
"flatpak_remote_refs=runtime/org.fedoraproject.Platform/x86_64/f30 app/org.gnome.Baobab/x86_64/stable",
],
'failable': ['*'],
},
})
]
ostree_installer is a list, and for "^Silverblue$" under "x86_64": "repo": "Everything", "release": None, "rootfs_size": "8"; 'template_repo': 'https://pagure.io/fedora-lorax-templates.git' (take templates from this repository) with 'template_branch': 'master'; "add_template" (use following templates) = ["ostree-based-installer/lorax-configure-repo.tmpl", "ostree-based-installer/lorax-embed-repo.tmpl", "ostree-based-installer/lorax-embed-flatpaks.tmpl"]; and "add_template_var" (and add these variables for the templates) = "ostree_install_repo=https://kojipkgs.fedoraproject.org/compose/ostree/repo/", "ostree_update_repo=https://ostree.fedoraproject.org", "ostree_osname=fedora", "ostree_oskey=fedora-32-primary", "ostree_contenturl=mirrorlist=https://ostree.fedoraproject.org/mirrorlist", "ostree_install_ref=fedora/rawhide/x86_64/silverblue", "ostree_update_ref=fedora/rawhide/x86_64/silverblue", "flatpak_remote_name=fedora", "flatpak_remote_url=oci+https://registry.fedoraproject.org", "flatpak_remote_refs=runtime/org.fedoraproject.Platform/x86_64/f30 app/org.gnome.Baobab/x86_64/stable". Plus 'failable': ['*'].
Whew. And we are still not done, bestie โ keep scrolling, more is coming. ๐ฆ
RCM Tools compose ๐
This one's a lil compose used to deliver packages to Red Hat internal users, and the config is split across two files, okay?
# rcmtools-common.conf
release_name = "RCM Tools"
release_short = "RCMTOOLS"
release_version = "2.0"
release_type = "updates"
release_is_layered = True
createrepo_c = True
createrepo_checksum = "sha256"
# PKGSET
pkgset_source = "koji"
koji_profile = "brew"
pkgset_koji_inherit = True
# GENERAL SETTINGS
bootable = False
comps_file = "rcmtools-comps.xml"
variants_file = "rcmtools-variants.xml"
sigkeys = ["3A3A33A3"]
# RUNROOT settings
runroot = False
# GATHER
gather_method = "deps"
check_deps = True
additional_packages = [
('.*', {
'*': ['puddle', 'rcm-nexus'],
}
),
]
# Set repoclosure_strictness to fatal to avoid installation dependency
# issues in production composes
repoclosure_strictness = [
("^.*$", {
"*": "fatal"
})
]
And then the config that's specific to different base products gets split into its own separate files, like so:
# rcmtools-common.conf
from rcmtools-common import *
# BASE PRODUCT
base_product_name = "Red Hat Enterprise Linux"
base_product_short = "RHEL"
base_product_version = "7"
# PKGSET
pkgset_koji_tag = "rcmtools-rhel-7-compose"
# remove i386 arch on rhel7
tree_arches = ["aarch64", "ppc64le", "s390x", "x86_64"]
check_deps = False
# Packages in these repos are available to satisfy dependencies inside the
# compose, but will not be pulled in.
gather_lookaside_repos = [
("^Client|Client-optional$", {
"x86_64": [
"http://example.redhat.com/rhel/7/Client/x86_64/os/",
"http://example.redhat.com/rhel/7/Client/x86_64/optional/os/",
],
}),
("^Workstation|Workstation-optional$", {
"x86_64": [
"http://example.redhat.com/rhel/7/Workstation/x86_64/os/",
"http://example.redhat.com/rhel/7/Workstation/x86_64/optional/os/",
],
}),
("^Server|Server-optional$", {
"aarch64": [
"http://example.redhat.com/rhel/7/Server/aarch64/os/",
"http://example.redhat.com/rhel/7/Server/aarch64/optional/os/",
],
"ppc64": [
"http://example.redhat.com/rhel/7/Server/ppc64/os/",
"http://example.redhat.com/rhel/7/Server/ppc64/optional/os/",
],
"ppc64le": [
"http://example.redhat.com/rhel/7/Server/ppc64le/os/",
"http://example.redhat.com/rhel/7/Server/ppc64le/optional/os/",
],
"s390x": [
"http://example.redhat.com/rhel/7/Server/s390x/os/",
"http://example.redhat.com/rhel/7/Server/s390x/optional/os/",
],
"x86_64": [
"http://example.redhat.com/rhel/7/Server/x86_64/os/",
"http://example.redhat.com/rhel/7/Server/x86_64/optional/os/",
],
})
]
๐ฆ EXPORTING FILES FROM SCM ๐ฆ
Okay bestie, listen โ multiple places in Pungi can pull files from external storage. The config stays basically the same no matter which backend you're using, even though some features might differ a lil between them.
The so-called scm_dict always gets put into the configuration as a dictionary, and it can hold these keys:
scmโ tells you which SCM system is in play. This one is always required, no skips. Allowed values are:fileโ copies files from the local filesystemgitโ copies files from a Git repositorycvsโ copies files from a CVS repositoryrpmโ copies files from a package in the composekojiโ downloads archives from a given build in the Koji build systemcontainer-imageโ downloads an artifact from a container registry
repo- for Git and CVS backends this should be the URL to the repository
- for the RPM backend this should be a shell style glob matching package names (or a list of such globs)
- for the file backend this should be empty
- for the Koji backend this should be an NVR or package name
branch- branch name for Git and CVS backends, with
masterandHEADas defaults - Koji tag for the koji backend if only a package name is given
- otherwise it should not be specified
- branch name for Git and CVS backends, with
fileโ a list of files that should be exported.dirโ a directory that should be exported. All its contents will be exported. This option is mutually exclusive withfile.commandโ defines a shell command to run after Git clone to generate the needed file (for example, to runmake). Only supported in the Git backend.optionsโ a dictionary of additional configuration options. These are specific to the different backends.
Currently supported values for Git:
credential_helperโ path to a credential helper used to supply username/password for remotes that require authentication.
Koji examples โจ
There are two different ways to configure the Koji backend, and honestly both are giving.
{
# Download all *.tar files from build my-image-1.0-1.
"scm": "koji",
"repo": "my-image-1.0-1",
"file": "*.tar",
}
{
# Find latest build of my-image in tag my-tag and take files from
# there.
"scm": "koji",
"repo": "my-image",
"branch": "my-tag",
"file": "*.tar",
}
Real talk though: using both a tag name and an exact NVR will result in an error. The NVR would get interpreted as a package name, and it would not match anything. So don't do that. ๐
file vs. dir ๐ธ
Exactly one of these two options has to be specified. No both, no neither, pick one. The docs for each configuration option should tell you whether it expects a file or a dir.
For the extra_files phase, either key is valid, so just choose based on what your actual use case is.
container-image example ๐
Here's an example of pulling a container image into the compose:
{
# Pull a container into an oci-archive tar file
"scm": "container-image",
# This is the pull spec including tag. It is passed directly to skopeo
# copy with no modification.
"repo": "docker://registry.access.redhat.com/ubi9/ubi-minimal:latest",
# Key `file` is required, but the value is ignored.
"file": "",
# Optional subdirectory under Server/<arch>/os
"target": "containers",
}
Caveats ๐
- The
rpmbackend can only be used in phases that would extract the files after thepkgsetphase finished. You can't get a comps file from a package, bestie. It's just not the vibe. - Depending on the Git repository URL configuration, Pungi can only export the requested content using
git archive. When acommandshould run, this isn't possible, and a clone is always needed. - When using the
kojibackend, it's required that you provide configuration for the Koji profile to be used (koji_profile). It is not possible to contact multiple different Koji instances.
๐ PROGRESS NOTIFICATION ๐
Pungi has the ability to emit notification messages about the progress and general status of the compose. These can be used to, like, send messages to fedmsg. This is implemented by actually calling a separate script.
The script will be called with one argument describing the action that just happened. A JSON-encoded object gets passed to standard input to give more info about the event. At the very least, the object will contain a compose_id key.
The notification script inherits the working directory from the parent process, and it can be called from the same directory pungi-koji is called from. That working directory is listed at the start of the main log. ๐
Currently these messages are sent:
status-changeโ when composing starts, finishes or fails; astatuskey is provided to indicate detailsphase-startโ on start of a phasephase-stopโ when a phase is finishedcreateiso-targetsโ with a list of images to be createdcreateiso-imagedoneโ when any single image is finishedcreateiso-imagefailโ when any single image fails to createfail-to-startโ when there are incorrect CLI options or errors in the configuration file; this message does not containcompose_id, nor is it started in the compose directory (which does not exist yet)ostreeโ when a new commit is created, this message will announce its hash and the name of the ref it is meant for.
For phase related messages, a phase_name key is provided as well. ๐
A pungi-fedmsg-notification script is provided and understands this interface.
Setting it up ๐
The script should be provided as a command line argument --notification-script, like this:
--notification-script=pungi-fedmsg-notification
๐ GATHERING PACKAGES ๐
okay so a compose that Pungi makes? it's made of one or more variants. and a variant is just a lil subset of the content, aimed at one specific use case. that's it, that's the vibe โจ
there are different types of variants, and the type literally changes how packages get gathered into the variant. so pay attention bestie, this part matters ๐ซถ
the inputs for gathering are defined by various gather sources. packages from all the sources get collected together to build one big list of:
- package names
- comps group names
- a list of packages that should be filtered out
NOTE: the inputs for both the explicit package list and the comps file are interpreted as RPM names. not any arbitrary provides, and not source package names. iykyk ๐
next, gather_method decides how that list gets processed:
- for
nodeps, the results from source are used pretty much as-is <sup>[1]</sup> - for the
depsmethod, a whole process gets launched to figure out which dependencies are needed, and those get pulled in too
[1] the lists are filtered based on what packages are actually available in the package set, but nothing else will be pulled in. periodt.
๐ Variant types ๐
- Variant โ is the base type. it has no special behaviour, it's just vibing.
- Addon โ is built on top of a regular variant. any packages that should go to both the addon and its parent get removed from the addon. and packages that are only in the addon but got pulled in because of the
gather_fulltreeoption? those get moved to the parent. - Integrated Layered Product โ works similarly to addon. additionally, all packages from addons on the same parent variant are removed from integrated layered products.
the main difference between an addon and an integrated layered product is that an integrated layered product has its own identity in the metadata (defined with product name and version).
NOTE: there's also Layered Product as a term, but that is not related to variants. it's used to describe a product that is not a standalone operating system and is instead meant to be used on some other base system. don't mix them up bestie ๐ญ
- Optional โ contains packages that complete the base variant's package set. it always has
fulltreeandselfhostingenabled, so it contains build dependencies and packages which were not specifically requested for the base variant.
some configuration options get overridden for particular variant types. slay ๐
โญ Depsolving configuration โญ
here's how the overrides shake out, per variant type:
- base โ Fulltree: configurable ยท Selfhosting: configurable
- addon/ILP โ Fulltree: enabled ยท Selfhosting: disabled
- optional โ Fulltree: enabled ยท Selfhosting: enabled
๐ฅ Profiling ๐ฅ
profiling data on the pungi-gather tool can be enabled by setting the gather_profiler configuration option to True. go off, measure your performance king ๐
๐ฆ Modular compose ๐ฆ
a compose with gather_source set to module is called modular. the package list is determined by a list of modules.
the list of modules that will be put into a variant is defined in the variants.xml file. that file can contain either Name:Stream or Name:Stream:Version references. see Module Naming Policy for details. when Version is missing from the specification, Pungi will ask PDC for the latest one.
the module metadata in PDC contains a list of RPMs in the module, as well as the Koji tag from which the packages can be retrieved. iconic.
Restrictions
- a modular compose must always use Koji as a package set source.
๐ธ GETTING DATA FROM KOJI ๐ธ
when Pungi is configured to get packages from a Koji tag, it somehow needs to access the actual RPM files. (yes, somehow. the drama ๐ญ)
historically, this required the storage used by Koji to be directly available on the host where Pungi was running. this was usually achieved by using NFS for the Koji volume, and mounting it on the compose host.
the compose could be created directly on the same volume. in that case the packages would be hardlinked, significantly reducing space consumption. big brain energy.
the compose could also be created on a different storage, in which case the packages would either need to be copied over or symlinked. using symlinks requires that anything that accesses the compose (e.g. a download server) would also need to mount the Koji volume in the same location. so likeโฆ coordinate, besties ๐ซถ
there is also a risk with symlinks that the package in Koji can change (due to being resigned for example), which would invalidate composes linking to it. ๐
๐ Using Koji without direct mount ๐
it is possible now to run a compose from a Koji tag without direct access to Koji storage. we love to see it โจ
Pungi can download the packages over HTTP protocol, store them in a local cache, and consume them from there. to enable this behavior, set the koji_cache option in the compose configuration.
the local cache has a similar structure to what's on the Koji volume.
when Pungi needs some package, it has a path on Koji volume. it will replace the topdir with the cache location. if such file exists, it will be used. if it doesn't exist, it will be downloaded from Koji (by replacing the topdir with topurl).
Koji path /mnt/koji/packages/foo/1/1.fc38/data/signed/abcdef/noarch/foo-1-1.fc38.noarch.rpm
Koji URL https://kojipkgs.fedoraproject.org/packages/foo/1/1.fc38/data/signed/abcdef/noarch/foo-1-1.fc38.noarch.rpm
Local path /mnt/compose/cache/packages/foo/1/1.fc38/data/signed/abcdef/noarch/foo-1-1.fc38.noarch.rpm
the packages can be hard- or softlinked from this cache directory (/mnt/compose/cache in the example).
๐ง Cleanup ๐ง
while the approach above allows each RPM to be downloaded only once, it will eventually result in the Koji volume being mirrored locally. most of the packages will however no longer be needed. classic hoarding behaviour ngl ๐
there is a script pungi-cache-cleanup that can help with that. it can find and remove files from the cache that are no longer needed.
a file is no longer needed if:
- it has a single link (meaning it is only in the cache, not in any compose), and
- it has
mtimeolder than a given threshold.
it doesn't make sense to delete files that are hardlinked in an existing compose, as it would not save any space anyway. no cap.
the mtime check is meant to preserve files that are downloaded but not actually used in a compose โ like a subpackage that is not included in any variant. every time its existence in the local cache is checked, the mtime is updated. cute lil timestamp refresh ๐
๐ Race conditions? ๐
it should be safe to have multiple compose hosts share the same storage volume for generated composes and local cache. yes queen, share the storage.
if a cache file is accessed and it exists, there's no risk of race condition. we're chilling.
if two composes need the same file at the same time and it is not present yet, one of them will take a lock on it and start downloading. the other will wait until the download is finished.
the lock is only valid for a set amount of time (5 minutes) to avoid issues where the downloading process is killed in a way that blocks it from releasing the lock.
if the file is large and network slow, the limit may not be enough to finish downloading. in that case the second process will steal the lock while the first process is still downloading. this will result in the same file being downloaded twice. ๐ not ideal but here we are.
when the first process finishes the download, it will put the file into the local cache location. when the second process finishes, it will atomically replace it, but since it's the same file it will be the same file. (the way that's justโฆ the same file ๐ญ)
if the first compose already managed to hardlink the file before it gets replaced, there will be two copies of the file present locally.
๐ Integrity checking ๐
there is minimal integrity checking. RPM packages belonging to real builds will be checked to match the checksum provided by Koji hub.
there is no checking for scratch builds or any images. fr fr, don't expect it.
โจ PROCESSING COMPS FILES โจ
the comps file that Pungi takes as input is not really pure comps as used by tools like DNF. there are extensions used to customize how the file is processed. it's giving comps but make it extra ๐
step one: Pungi retrieves the actual file. this can use anything that Exporting files from SCM supports.
the Pungi extensions are the arch attribute on packageref, group and environment tags. the value of this attribute is a comma separated list of architectures.
step two: Pungi creates a file for each architecture. this is done by removing all elements with an incompatible arch attribute. no additional clean up is performed on this file. the resulting file is only used internally for the rest of the compose process.
step three (and final): create a comps file for each Variant.Arch combination. this is the actual file that will be included in the compose. the start file is the original input file, from which all elements with incompatible architecture are removed. then clean up is performed by:
- removing all empty groups,
- removing non-existing groups from environments and categories,
- and finally removing empty environments and categories.
as a last step, groups not listed in the variants file are removed. and that's the whole glow-up routine ๐โจ
๐ CONTRIBUTING TO PUNGI ๐
okay bestie, you wanna actually work on Pungi? iconic. let's get you set up so you can slay. ๐ซถ
๐ธ Set up development environment
To work on Pungi, you should install a recent version of Fedora.
๐ Python2
Fedora 29 is recommended, because some packages are just not available in newer Fedora releases โ like python2-libcomps. (the struggle is real ๐ญ)
Install the required packages:
$ sudo dnf install -y krb5-devel gcc make libcurl-devel python2-devel python2-createrepo_c kobo-rpmlib yum python2-libcomps python2-libselinx
๐ Python3
Install the required packages:
$ sudo dnf install -y krb5-devel gcc make libcurl-devel python3-devel python3-createrepo_c python3-libcomps
โจ Developing
Currently the development workflow for Pungi is on the master branch. here's the whole vibe:
- Make your own fork at
https://forge.fedoraproject.org/pungi/pungi - Clone your fork locally
cdinto your local clone and add the remote upstream so you can rebase:
cd pungi
git remote add upstream https://forge.fedoraproject.org/pungi/pungi.git
NOTE: this workflow assumes you nevergit commitdirectly to themasterbranch of your fork. it'll make way more sense once we get to the rebasing part below, promise. ๐
- create a topic branch based on
master:
git branch my_topic_branch master
git checkout my_topic_branch
- make edits, changes, add new features, etc., and then make sure to pull from upstream
masterand rebase before submitting a pull request:
# lets just say you edited setup.py for sake of argument
git checkout my_topic_branch
# make changes to setup.py
black setup.py
tox
git add setup.py
git commit -s -m "added awesome feature to setup.py"
# now we rebase
git checkout master
git pull --rebase upstream master
git push origin master
git push origin --tags
git checkout my_topic_branch
git rebase master
# resolve merge conflicts if any as a result of your development in
# your topic branch
git push origin my_topic_branch
NOTE: in order for your commit to be merged: - you must sign-off on it. use the-soption when runninggit commit. - the code must be well formatted viablackand passflake8checking. runtox -e black,flake8to do the check.
- create a pull request in the Forge web UI
- for convenience, here's a bash shell function you can drop into your
~/.bashrcand call likepullupstream pungi-4-develโ it automates a large portion of the rebase steps from above:
pullupstream () {
if [[ -z "$1" ]]; then
printf "Error: must specify a branch name (e.g. - master, devel)\n"
else
pullup_startbranch=$(git describe --contains --all HEAD)
git checkout $1
git pull --rebase upstream master
git push origin $1
git push origin --tags
git checkout ${pullup_startbranch}
fi
}
๐งช Testing
You must write unit tests for any new code โ unless it's a trivial change, no exceptions. any code without sufficient test coverage may not be merged. periodt. ๐
To run all the existing tests, the suggested method is to use tox.
$ sudo dnf install python3-tox -y
$ tox -e py3
$ tox -e py27
Alternatively, you could create a vitualenv, install deps and run tests manually if you don't want to use tox.
$ sudo dnf install python3-virtualenvwrapper -y
$ mkvirtualenv --system-site-packages py3
$ workon py3
$ pip install -r requirements.txt -r test-requirements.txt
$ make test
# or with coverage
$ make test-coverage
If you need to run specified tests, pytest is recommended.
# Activate virtualenv first
# Run tests
$ pytest tests/test_config.py
$ pytest tests/test_config.py -k test_pkgset_mismatch_repos
In the tests/ directory there's a shell script test_compose.sh that you can use to try and create a miniature compose on dummy data. the actual data gets created by running make test-data in the project root. ๐
$ sudo dnf -y install rpm-build createrepo_c isomd5sum genisoimage syslinux
# Activate virtualenv (the one created by tox could be used)
$ source .tox/py3/bin/activate
$ python setup.py develop
$ make test-data
$ make test-compose
This testing compose does not actually use all the phases that are available, and there is no checking that the result is correct. it only tells you whether it crashed or not. fr fr. ๐
NOTE: even when it finishes successfully, it may print errors aboutrepoclosureonServer-Gluster.x86_64in the test phase. this is not a bug. don't come for it.
๐ Documenting
You must write documentation for any new features and functional changes. any code without sufficient documentation may not be merged. same energy as the tests rule, bestie. โ๏ธ
To generate the documentation, run make doc in the project root.
๐ฅ TESTING PUNGI ๐ฅ
๐ Test Data
Tests require test data and not all of it is available in git. you must create test repositories before running the tests:
make test-data
Requirements: createrepo_c, rpmbuild
โญ Unit Tests
Unit tests cover functionality of Pungi python modules. you can run all of them at once:
make test
which is a shortcut to:
python2 setup.py test
python3 setup.py test
You can alternatively run individual tests:
cd tests
./<test>.py [<class>[.<test>]]
๐ฆ Functional Tests
Because a compose is quite a complex process and not everything is covered with unit tests yet, the easiest way to test whether your changes broke anything badly is to start a compose on a relatively small and well-defined package set:
cd tests
./test_compose.sh
๐ AUTHOR
Pungi maintainers
๐ COPYRIGHT
2016, Red Hat, Inc.
and that's a wrap on the whole page, bestie!! ๐ the source/version line at the bottom is Aug 31, 2026 and the version tag is PUNGI(1) โ so yeah, go forth, write your tests, sign off your commits, and slay. the maintainers are watching and they're rooting for you. mwah. ๐โจ๐ฆ
๐ฆ where this page comes from
- fedora 43: pungi 4.14.0-2.fc43 ยท GPL-2.0-only
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms