Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ susemanager-ci.iml
__pycache__
# vim undo files
*.un~
.DS_Store
.DS_Store
# generated by maintenance_json_generator.py
jenkins_pipelines/scripts/json_generator/custom_repositories.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,56 @@
import argparse
from functools import cache
import json
import os
import requests
import logging

from ibs_osc_client import IbsOscClient
from repository_versions import nodes_by_version
from repository_versions import VersionNodes, nodes_by_version

IBS_MAINTENANCE_URL_PREFIX: str = 'http://download.suse.de/ibs/SUSE:/Maintenance:/'
IBS_URL_PREFIX: str = 'http://download.suse.de/ibs/SUSE:'
Comment thread
ktsamis marked this conversation as resolved.
JSON_OUTPUT_FILE_NAME: str = 'custom_repositories.json'

def setup_logging():
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def _slfo_pr_id(value: str) -> str:
if not (value.isdigit() and int(value) > 0):
raise argparse.ArgumentTypeError(
f"invalid SLFO PullRequest id: {value!r} (must be a positive integer)"
)
return value

def parse_cli_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="This script reads the open qam-manager requests and creates a json file that can be fed to the BV testsuite pipeline"
)
parser.add_argument("-v", "--version", dest="version",
help="Version of SUMA/MLM you want to run this script for, options include 43, 50, 51, and 52 variations (including beta)",
choices=["43", "50-micro", "50-sles", "51-micro","51-sles", "52-micro", "52-sles", "52-micro-beta", "52-sles-beta"], default="43", action='store')
help="Version of SUMA/MLM to run this script for. Default: 51-sles.",
choices=list(nodes_by_version.keys()), default="51-sles", action='store')
parser.add_argument("-i", "--mi_ids", required=False, dest="mi_ids", help="Space separated list of MI IDs", nargs='*', action='store')
parser.add_argument("-f", "--file", required=False, dest="file", help="Path to a file containing MI IDs separated by newline character", action='store')
parser.add_argument("-e", "--no_embargo", dest="embargo_check", help="Reject MIs under embargo", action='store_true')
return parser.parse_args()

def read_mi_ids_from_file(file_path: str) -> list[str]:
with open(file_path, 'r') as file:
parser.add_argument(
"--slfo-pull-request",
required=False,
dest="slfo_pull_request",
metavar="ID",
type=_slfo_pr_id,
help="SLFO PullRequest id for sles160_minion and slmicro62_minion (stable 51-* / 52-* only; beta uses :ToTest automatically)",
)
args = parser.parse_args()
if args.slfo_pull_request is not None:
if not supports_slfo_pull_request(args.version):
parser.error("--slfo-pull-request is only supported for 51-* and 52-* versions")
if args.version.endswith("-beta"):
parser.error("--slfo-pull-request is not supported for beta versions (beta uses :ToTest automatically)")
return args

def read_mi_ids_from_file(file_path: str | os.PathLike[str]) -> list[str]:
"""Read newline-separated MI ids from a file (path may be str or pathlib.Path)."""
with open(file_path, 'r', encoding='utf-8') as file:
return file.read().strip().split()

def merge_mi_ids(args: argparse.Namespace) -> set[str]:
Expand Down Expand Up @@ -64,25 +88,56 @@ def validate_and_store_results(expected_ids: set [str], custom_repositories: dic
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(custom_repositories, f, indent=2, sort_keys=True)

def get_version_nodes(version: str):
def get_version_nodes(version: str) -> VersionNodes:
version_nodes = nodes_by_version.get(version)
if not version_nodes:
if version_nodes is None:
supported_versions = ', '.join(nodes_by_version.keys())
raise ValueError(f"No nodes for version {version} - supported versions: {supported_versions}")
return version_nodes

def init_custom_repositories(version: str, static_repos: dict[str, dict[str, str]] = None) -> dict[str, dict[str, str]]:
def init_custom_repositories(static_repos: dict[str, dict[str, str]] | None = None) -> dict[str, dict[str, str]]:
custom_repositories: dict[str, dict[str, str]] = {}

# Check for version 51 or 52 (which successfully matches "52-micro", "52-sles-beta", etc.)
if (version.startswith("51") or version.startswith("52")) and static_repos:
# Merge static named repos (full http URLs or maintenance path fragments)
if static_repos:
for node, named_urls in static_repos.items():
custom_repositories[node] = {
name: f"{IBS_MAINTENANCE_URL_PREFIX}{url}" if not url.startswith("http") else url
for name, url in named_urls.items()
}
Comment thread
ktsamis marked this conversation as resolved.
return custom_repositories


def supports_slfo_pull_request(version: str) -> bool:
return version.startswith("51") or version.startswith("52")

def slfo_pullrequest_client_tool_url(pr_id: str) -> str:
"""Return the stable SLE-16 MultiLinuxManagerTools URL for the given PullRequest id.

This URL is used for both sles160_minion and slmicro62_minion: SL Micro 6.2
consumes the same SLE-16 client-tools repo on the stable PullRequest path.
Beta client tools do not use PullRequest URLs - they are served from a static
:ToTest project baked into repository_versions/v52_nodes.py.
"""
root = "/SLFO:/Products:/MultiLinuxManagerTools:/PullRequest"
tail = f":/{pr_id}:/SLES/product/repo/Multi-Linux-ManagerTools-SLE-16-x86_64/"
return f"{IBS_URL_PREFIX}{root}{tail}"


def slfo_pullrequest_repo_key(pr_id: str) -> str:
"""Inner dict key for SLFO PullRequest client-tools repos (not an MI id)."""
return f"slfo_pr_{pr_id}"


def apply_slfo_pullrequest_client_tools(
custom_repositories: dict[str, dict[str, str]], pr_id: str
) -> None:
url = slfo_pullrequest_client_tool_url(pr_id)
repo_key = slfo_pullrequest_repo_key(pr_id)
update_custom_repositories(custom_repositories, "sles160_minion", repo_key, url)
update_custom_repositories(custom_repositories, "slmicro62_minion", repo_key, url)


def update_custom_repositories(custom_repositories: dict[str, dict[str, str]], node: str, mi_id: str, url: str):
node_ids: dict[str, str] = custom_repositories.get(node, {})
final_id: str = mi_id
Expand All @@ -94,13 +149,13 @@ def update_custom_repositories(custom_repositories: dict[str, dict[str, str]], n
custom_repositories[node] = node_ids


def find_valid_repos(mi_ids: set[str], version: str):
def find_valid_repos(mi_ids: set[str], version: str, slfo_pull_request_id: str | None = None):
version_data = get_version_nodes(version)

static_repos = version_data.get("static", {})
dynamic_nodes = version_data.get("dynamic", {})

custom_repositories = init_custom_repositories(version, static_repos)
custom_repositories = init_custom_repositories(static_repos)

for node, repositories in dynamic_nodes.items():
for mi_id in mi_ids:
Expand All @@ -109,6 +164,17 @@ def find_valid_repos(mi_ids: set[str], version: str):
if repo_url:
update_custom_repositories(custom_repositories, node, mi_id, repo_url)

if slfo_pull_request_id is not None:
if not supports_slfo_pull_request(version):
raise ValueError(
f"SLFO PullRequest id is only supported for 51-* and 52-* versions (got {version!r})"
)
if version.endswith("-beta"):
raise ValueError(
f"SLFO PullRequest id is not supported for beta versions (got {version!r}); beta uses :ToTest automatically"
)
apply_slfo_pullrequest_client_tools(custom_repositories, slfo_pull_request_id)

validate_and_store_results(mi_ids, custom_repositories)

def main():
Expand All @@ -118,14 +184,16 @@ def main():

mi_ids: set[str] = merge_mi_ids(args)
logging.info(f"MI IDs: {mi_ids}")
if args.slfo_pull_request is not None:
logging.info(f"SLFO PullRequest id: {args.slfo_pull_request}")
if not mi_ids:
mi_ids = osc_client.find_maintenance_incidents()

if args.embargo_check:
logging.info(f"Remove MIs under embargo")
mi_ids = { id for id in mi_ids if not osc_client.mi_is_under_embargo(id) }

find_valid_repos(mi_ids, args.version)
find_valid_repos(mi_ids, args.version, args.slfo_pull_request)

if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,32 @@
This Python script automates the process of gathering and processing open QAM
(Quality Assurance Maintenance) requests for SUSE Linux Enterprise Server (SLES)
that affect SUSE Manager. The output is a JSON file, which can be fed into the
BV (Build Validation) testsuite pipeline for further testing. It supports
both SUSE Manager 4.3 (SUMA 4.3) and SUSE Manager 5.0 (SUMA 5.0).
BV (Build Validation) testsuite pipeline for further testing. It supports SUSE
Manager / MLM 4.3, 5.0, 5.1 and 5.2 (including 5.2 beta).

The script allows users to input Maintenance Incident (MI) IDs and generates the
appropriate repository information for SUMA 4.3 or 5.0 nodes (servers, proxies,
and clients). It retrieves the necessary information for these nodes and their
associated repositories.
appropriate repository information for the selected version's nodes (servers,
proxies, and clients). The supported `--version` values are: `43`, `50-micro`,
`50-sles`, `51-micro`, `51-sles`, `52-micro`, `52-sles`, `52-micro-beta`,
`52-sles-beta`.

## Features

- Support for SUSE Manager 4.3 and 5.0: The script allows users to specify which
version of SUSE Manager they are working with.
- Support for SUSE Manager / MLM 4.3, 5.0, 5.1 and 5.2 (including 5.2 beta): the
version is selected via `--version`.
- Flexible MI ID Input: MI IDs can be provided via CLI arguments or by reading
from a file.
- Custom Repository Generation: Outputs a JSON file containing repository
information for the SUSE Manager BV testsuite pipeline.
- Embargo Checks: The script has an option to reject Maintenance Incidents (MIs)
that are under embargo.
- SLFO client tools for `sles160_minion` / `slmicro62_minion`:
- Stable `51-*` / `52-sles` / `52-micro`: use `--slfo-pull-request <id>` to
inject a `:PullRequest:/<id>` client-tools URL (independent of MI IDs).
- Beta `52-sles-beta` / `52-micro-beta`: a static `:ToTest` URL is baked in
and applied automatically; `--slfo-pull-request` is rejected for beta
versions because the Beta project cannot toggle maintenance on/off under
the git workflow.

## Usage

Expand All @@ -51,10 +59,11 @@ python3.11 maintenance_json_generator.py [options]
Options:

`-v`, `--version`: Specifies the SUSE Manager version. Options are `43` for SUSE
Manager 4.3, `50-micro` / `50-sles` for 5.0, `51-micro` / `51-sles` for 5.1, and `52-micro` / `52-sles` for 5.2. Default is 43.
Manager 4.3, `50-micro` / `50-sles` for 5.0, `51-micro` / `51-sles` for 5.1, and `52-micro` / `52-sles` for 5.2 (including `52-micro-beta` / `52-sles-beta`). Default is `51-sles`.
`-i`, `--mi_ids`: A space-separated list of MI IDs.
Comment thread
ktsamis marked this conversation as resolved.
`-f`, `--file`: Path to a file containing MI IDs, each on a new line.
`-e`, `--no_embargo`: Reject any MIs that are currently under embargo.
`--slfo-pull-request`: SLFO PullRequest id for `sles160_minion` and `slmicro62_minion` on stable 5.1 / 5.2 only (independent of MI ids). Rejected for `-beta` versions, which receive a fixed `:ToTest` URL automatically. In `custom_repositories.json`, those entries use the inner key pattern `slfo_pr_<id>` (not the bare PR id) so they cannot collide with MI id keys.

Example:

Expand All @@ -74,6 +83,29 @@ mi_ids.txt.
The script generates a file named custom_repositories.json, which contains the
repository data for the provided MI IDs.

For **`43`**, **`50-micro`**, and **`50-sles`**, the output always includes static Salt image
repository URLs for **`slmicro60_minion`** and **`slmicro61_minion`** (`slmicro60_salt`,
`slmicro61_salt`, `slmicro6_salt_bundle`) in addition to MI-based maintenance URLs.

For **`52-sles-beta`** and **`52-micro-beta`**, the output always includes fixed `:ToTest`
client-tools URLs independently of MI IDs. **`slmicro60_minion`** and **`slmicro61_minion`**
use the **SL-Micro-6** MultiLinuxManagerTools-Beta **`:ToTest`** path (inner key
`slmicro6_client_tools`). **`slmicro62_minion`** and **`sles160_minion`** share the same
**SLES-16** MultiLinuxManagerTools-Beta **`:ToTest`** path under the inner key
**`sles16_client_tools`**. In addition, **`52-sles-beta`** always includes fixed `http://`
`server` and `proxy` ToTest image repos; path fragments live in
**`v52_uyuni_tools_sles_static_repos_beta`** and are prefixed with **`IBS_URL_PREFIX`** in
**`get_v52_static_and_client_tools`** (they are not pre-built `http://` strings in the dict).
URLs resolve under
`SUSE:/SLE-15-SP7:/Update:/Products:/MultiLinuxManager52:/ToTest/images-SP7/repo/`
(`SUSE-Multi-Linux-Manager-Server-SLE-5.2-POOL-x86_64-Media1/` and
`SUSE-Multi-Linux-Manager-Proxy-SLE-5.2-POOL-x86_64-Media1/`). **`52-micro-beta`** pins
`server_uyuni_tools` and `proxy_uyuni_tools` to the `http://`
`SLFO:/Products:/Multi-Linux-Manager:/5.2:/ToTest/product/repo/` tree
(`Multi-Linux-Manager-Server-5.2-x86_64/` and `Multi-Linux-Manager-Proxy-5.2-x86_64/`). On the stable `51-*` / `52-sles` / `52-micro` flows,
equivalent URLs for `sles160_minion` / `slmicro62_minion` are only added when
`--slfo-pull-request <id>` is provided.

## Logging

The script includes basic logging for informational messages. To enable logging,
Expand All @@ -91,22 +123,30 @@ messages will display timestamped INFO-level messages.

### Repository Data

The script contains two main dictionaries for SUSE Manager client tools
repositories:

- `v43_client_tools`: Contains repository data for SUMA 4.3 client tools.
- `v50_client_tools_beta`: Contains repository data for SUMA 5.0 beta client tools.
- `merged_client_tools`: Merges the 4.3 and 5.0 beta client tools into a single
dictionary.

It also defines two dictionaries for SUSE Manager server and proxy
repositories:

- `v43_nodes`: Includes repository data for SUMA 4.3 server and proxy nodes.
- `v50_nodes`: Includes repository data for SUMA 5.0 server and proxy nodes.

The final repository information is stored in the nodes_by_version dictionary,
which maps SUMA version numbers (`43`, `50`) to the corresponding repository data.
Repository definitions live under
[repository_versions/](repository_versions), one module per major version:

- `v43_nodes.py`: server/proxy (`v43_nodes`), client tools (`v43_client_tools`),
static Salt image repos (`v43_static_slmicro_salt_repositories`), and the
helper `get_v43_nodes_sorted()`.
- `v50_nodes.py`: base node sets for SL Micro / SLES (`v50_micro_nodes`,
`v50_sles_nodes`) and the helper
`get_v50_nodes_sorted(v43_client_tools, variant)`.
- `v51_nodes.py` / `v52_nodes.py`: the helpers
`get_v51_static_and_client_tools(variant)` and
`get_v52_static_and_client_tools(variant, beta)`, which return a
`(static_repos, dynamic_repos)` pair per variant. For **`52-sles-beta`**, fixed
ToTest **server** / **proxy** image URLs are defined in
`v52_uyuni_tools_sles_static_repos_beta` (separate from MI-based
`v52_uyuni_tools_sles_repos_beta`). For **`52-micro-beta`** / **`52-sles-beta`**, dynamic
client minions include **`opensuse160arm_minion`** (`MultiLinuxManagerTools-Beta_SLE-16_aarch64`),
alongside **`opensuse156arm_minion`** (SLE-15 aarch64), in **`v52_nodes_dynamic_client_tools_repos_beta`**.

`repository_versions/__init__.py` aggregates everything into the
`nodes_by_version` mapping, whose keys are exactly the strings accepted by
`--version` (`43`, `50-micro`, `50-sles`, `51-micro`, `51-sles`, `52-micro`,
`52-sles`, `52-micro-beta`, `52-sles-beta`) and whose values are
`{"static": ..., "dynamic": ...}` dicts consumed by the main script.

## Error Handling

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
from .v43_nodes import get_v43_nodes_sorted
from typing import TypeAlias, TypedDict

from .v43_nodes import (
get_v43_nodes_sorted,
v43_client_tools,
v43_static_slmicro_salt_repositories,
)
from .v50_nodes import get_v50_nodes_sorted
from .v51_nodes import get_v51_static_and_client_tools
from .v52_nodes import get_v52_static_and_client_tools

StaticRepos: TypeAlias = dict[str, dict[str, str]]
DynamicRepos: TypeAlias = dict[str, list[str]]


class VersionNodes(TypedDict):
static: StaticRepos
dynamic: DynamicRepos
Comment thread
ktsamis marked this conversation as resolved.

v43_dynamic = get_v43_nodes_sorted()

static_51_micro, dynamic_51_micro = get_v51_static_and_client_tools("micro")
static_51_sles, dynamic_51_sles = get_v51_static_and_client_tools("sles")

Expand All @@ -14,10 +30,16 @@
static_52_micro_beta, dynamic_52_micro_beta = get_v52_static_and_client_tools("micro", beta=True)
static_52_sles_beta, dynamic_52_sles_beta = get_v52_static_and_client_tools("sles", beta=True)

nodes_by_version: dict[str, dict[str, dict[str, list[str]]]] = {
"43": {"dynamic": get_v43_nodes_sorted()},
"50-micro": {"dynamic": get_v50_nodes_sorted(get_v43_nodes_sorted(), "micro")},
"50-sles": {"dynamic": get_v50_nodes_sorted(get_v43_nodes_sorted(), "sles")},
nodes_by_version: dict[str, VersionNodes] = {
"43": {"static": v43_static_slmicro_salt_repositories, "dynamic": v43_dynamic},
"50-micro": {
"static": v43_static_slmicro_salt_repositories,
"dynamic": get_v50_nodes_sorted(v43_client_tools, "micro"),
},
"50-sles": {
"static": v43_static_slmicro_salt_repositories,
"dynamic": get_v50_nodes_sorted(v43_client_tools, "sles"),
},
"51-sles": {"static": static_51_sles, "dynamic": dynamic_51_sles},
"51-micro": {"static": static_51_micro, "dynamic": dynamic_51_micro},
"52-sles": {"static": static_52_sles, "dynamic": dynamic_52_sles},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@
"/SUSE_Updates_SLE-Module-Python3_15-SP5_x86_64/"},
}

# Static Salt / image repos for SL Micro 6.0 / 6.1 (SUMA 4.3 / 5.0 only; full IBS URLs)
v43_static_slmicro_salt_repositories: Dict[str, Dict[str, str]] = {
"slmicro60_minion": {
"slmicro60_salt": (
"http://download.suse.de/ibs/SUSE:/ALP:/Source:/Standard:/1.0:/Staging:/Z/images/repo/"
"SL-Micro-6.0-x86_64/"
),
"slmicro6_salt_bundle": (
"http://download.suse.de/ibs/SUSE:/ALP:/Source:/Standard:/1.0:/Staging:/Z/images/repo/"
"SUSE-Manager-Tools-For-SL-Micro-6-x86_64/"
),
},
"slmicro61_minion": {
"slmicro61_salt": (
"http://download.suse.de/ibs/SUSE:/SLFO:/1.1:/Staging:/Z/images/repo/SL-Micro-6.1-x86_64/"
),
"slmicro6_salt_bundle": (
"http://download.suse.de/ibs/SUSE:/ALP:/Source:/Standard:/1.0:/Staging:/Z/images/repo/"
Comment thread
ktsamis marked this conversation as resolved.
"SUSE-Manager-Tools-For-SL-Micro-6-x86_64/"
),
},
}

# Dictionary for SUMA 4.3 Server and Proxy
v43_nodes: Dict[str, Set[str]] = {
"server": {"/SUSE_Updates_SLE-Module-SUSE-Manager-Server_4.3_x86_64/",
Expand Down
Loading