Add new stage for creating YUM / DNF repo files
Add a new stage `org.osbuild.yum.repos` for creating YUM / DNF `.repo` files in `/etc/yum.repos.d`. All repo-specific options are supported but only a subset of options which can be set for a repo as well as in the [main] section are supported. Add unit test for the new stage. Fix #907 Signed-off-by: Tomas Hozza <thozza@redhat.com>
This commit is contained in:
parent
f965ca8510
commit
cd4ac1c75a
6 changed files with 1879 additions and 0 deletions
193
stages/org.osbuild.yum.repos
Executable file
193
stages/org.osbuild.yum.repos
Executable file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/python3
|
||||
"""
|
||||
Create YUM / DNF repo file in /etc/yum.repos.d
|
||||
|
||||
All repo-specific options, except the 'type' option, are supported. The 'type'
|
||||
repo options is not supported, since it accepts only a single value, therefore
|
||||
the ability to set it adds no value.
|
||||
|
||||
Only a subset of options which can be used in both, a repo or [main] section
|
||||
configuration, is supported, specifically:
|
||||
- gpgcheck
|
||||
- repo_gpgcheck
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import configparser
|
||||
|
||||
import osbuild.api
|
||||
|
||||
|
||||
SCHEMA = r"""
|
||||
"definitions": {
|
||||
"repo": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"oneOf": [
|
||||
{
|
||||
"required": ["id", "baseurl"]
|
||||
},
|
||||
{
|
||||
"required": ["id", "metalink"]
|
||||
},
|
||||
{
|
||||
"required": ["id", "mirrorlist"]
|
||||
}
|
||||
],
|
||||
"description": "YUM / DNF repo definition.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Repository ID.",
|
||||
"pattern": "^[\\w.\\-:]+$"
|
||||
},
|
||||
"baseurl": {
|
||||
"type": "array",
|
||||
"description": "List of URLs for the repository.",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"cost": {
|
||||
"type": "integer",
|
||||
"description": "The relative cost of accessing this repository, defaulting to 1000."
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Include this repository as a package source."
|
||||
},
|
||||
"gpgkey": {
|
||||
"type": "array",
|
||||
"description": "URLs of a GPG key files that can be used for signing metadata and packages of this repository.",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"metalink": {
|
||||
"type": "string",
|
||||
"description": "URL of a metalink for the repository.",
|
||||
"minLength": 1
|
||||
},
|
||||
"mirrorlist": {
|
||||
"type": "string",
|
||||
"description": "URL of a mirrorlist for the repository.",
|
||||
"minLength": 1
|
||||
},
|
||||
"module_hotfixes": {
|
||||
"type": "boolean",
|
||||
"description": "Set this to True to disable module RPM filtering and make all RPMs from the repository available."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A human-readable name of the repository. Defaults to the ID of the repository.",
|
||||
"minLength": 1
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
"description": "The priority value of this repository."
|
||||
},
|
||||
"gpgcheck": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to perform GPG signature check on packages found in this repository."
|
||||
},
|
||||
"repo_gpgcheck": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to perform GPG signature check on this repository's metadata."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"description": "YUM / DNF repo file configuration.",
|
||||
"properties": {
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.-]{1,250}\\.repo$",
|
||||
"description": "Repo file name."
|
||||
},
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"description": "YUM / DNF repo definitions.",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/definitions/repo"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
# List of repo options which should be listed in this specific order if set
|
||||
# in the stage options.
|
||||
#
|
||||
# Reasoning: repo configurations as shipped by distributions or created by
|
||||
# various tools (COPR, RHSM) tend to order some options in a specific way,
|
||||
# therefore if we just iterated over the dictionary items, the order would
|
||||
# be different than how are repository configurations usually structured.
|
||||
SPECIFIC_ORDER_OPTIONS = [
|
||||
"name",
|
||||
"baseurl",
|
||||
"metalink",
|
||||
"mirrorlist",
|
||||
"enabled",
|
||||
"gpgcheck",
|
||||
"repo_gpgcheck",
|
||||
"gpgkey"
|
||||
]
|
||||
|
||||
|
||||
def option_value_to_str(value):
|
||||
"""
|
||||
Convert allowed types of option values to string.
|
||||
|
||||
DNF allows string lists as a option value.
|
||||
'dnf.conf' man page says:
|
||||
"list It is an option that could represent one or more strings separated by space or comma characters."
|
||||
"""
|
||||
if isinstance(value, list):
|
||||
value = " ".join(value)
|
||||
elif isinstance(value, bool):
|
||||
value = "1" if value else "0"
|
||||
elif not isinstance(value, str):
|
||||
value = str(value)
|
||||
return value
|
||||
|
||||
|
||||
def main(tree, options):
|
||||
filename = options.get("filename")
|
||||
repos = options.get("repos")
|
||||
|
||||
yum_repos_dir = f"{tree}/etc/yum.repos.d"
|
||||
os.makedirs(yum_repos_dir, exist_ok=True)
|
||||
|
||||
parser = configparser.ConfigParser()
|
||||
|
||||
for repo in repos:
|
||||
repo_id = repo.pop("id")
|
||||
parser.add_section(repo_id)
|
||||
# Set some options in a specific order in which they tend to be
|
||||
# written in repo files.
|
||||
for option in SPECIFIC_ORDER_OPTIONS:
|
||||
option_value = repo.pop(option, None)
|
||||
if option_value is not None:
|
||||
parser.set(repo_id, option, option_value_to_str(option_value))
|
||||
|
||||
for key, value in repo.items():
|
||||
parser.set(repo_id, key, option_value_to_str(value))
|
||||
|
||||
# ensure that we won't overwrite an existing file
|
||||
with open(f"{yum_repos_dir}/{filename}", "x") as f:
|
||||
parser.write(f, space_around_delimiters=False)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = osbuild.api.arguments()
|
||||
r = main(args["tree"], args["options"])
|
||||
sys.exit(r)
|
||||
Loading…
Add table
Add a link
Reference in a new issue