Skip to content
Open
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
114 changes: 45 additions & 69 deletions elliott/elliottlib/cli/verify_cdn_push_cli.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import json
import logging
from dataclasses import dataclass, field
from typing import Optional

import click
from artcommonlib.assembly import assembly_config_struct

from elliottlib.cli.common import cli, click_coroutine
from elliottlib.errata_async import AsyncErrataAPI
from elliottlib.verify_common import (
VerifyResultBase,
get_assembly_advisory_ids,
handle_verify_result,
verify_output_option,
)

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -54,17 +58,51 @@ def pending(self) -> bool:


@dataclass
class VerifyCdnPushResult:
class VerifyCdnPushResult(VerifyResultBase):
advisories: list[AdvisoryPushResult] = field(default_factory=list)

@property
def complete(self) -> bool:
def passed(self) -> bool:
return bool(self.advisories) and all(a.complete for a in self.advisories)

@property
def failed(self) -> bool:
return any(a.failed for a in self.advisories)

def to_dict(self) -> dict:
return {
"passed": self.passed,
"failed": self.failed,
"advisories": [
{
"advisory_id": a.advisory_id,
"impetus": a.impetus,
"complete": a.complete,
"failed": a.failed,
"push_triggered": a.push_triggered,
"error": a.error,
"push_jobs": [{"target": j.target, "job_id": j.job_id, "status": j.status} for j in a.push_jobs],
}
for a in self.advisories
],
}

def render_text(self) -> str:
lines = ["CDN staging push status", ""]
for a in self.advisories:
status = "COMPLETE" if a.complete else ("FAIL" if a.failed else "PENDING")
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): {status}")
if a.push_triggered:
lines.append(" Push re-triggered")
if a.error:
lines.append(f" Error: {a.error}")
for j in a.push_jobs:
lines.append(f" {j.target}: {j.status} (job {j.job_id})")
lines.append("")
overall = "COMPLETE" if self.passed else ("FAIL" if self.failed else "PENDING")
lines.append(f"Overall: {overall}")
return "\n".join(lines)


def parse_push_jobs(raw_jobs: list) -> list[PushJobInfo]:
latest_by_target: dict[str, PushJobInfo] = {}
Expand Down Expand Up @@ -166,74 +204,14 @@ async def verify_cdn_push(advisories: dict[str, int], do_push: bool) -> VerifyCd
return result


def render_result(result: VerifyCdnPushResult, output: str) -> str:
if output == "json":
return json.dumps(
{
"complete": result.complete,
"failed": result.failed,
"advisories": [
{
"advisory_id": a.advisory_id,
"impetus": a.impetus,
"complete": a.complete,
"failed": a.failed,
"push_triggered": a.push_triggered,
"error": a.error,
"push_jobs": [
{"target": j.target, "job_id": j.job_id, "status": j.status} for j in a.push_jobs
],
}
for a in result.advisories
],
},
indent=2,
)

lines = ["CDN staging push status", ""]
for a in result.advisories:
status = "COMPLETE" if a.complete else ("FAIL" if a.failed else "PENDING")
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): {status}")
if a.push_triggered:
lines.append(" Push re-triggered")
if a.error:
lines.append(f" Error: {a.error}")
for j in a.push_jobs:
lines.append(f" {j.target}: {j.status} (job {j.job_id})")
lines.append("")

overall = "COMPLETE" if result.complete else ("FAIL" if result.failed else "PENDING")
lines.append(f"Overall: {overall}")
return "\n".join(lines)


def get_advisory_ids(runtime) -> dict[str, int]:
releases_config = runtime.get_releases_config()
group_config = assembly_config_struct(releases_config, runtime.assembly, "group", {})
advisories = group_config.get("advisories", {})
result = {}
for impetus in CDN_PUSH_ADVISORY_TYPES:
ad_id = advisories.get(impetus)
if ad_id:
result[impetus] = int(ad_id)
return result


@cli.command("verify-cdn-push", short_help="Verify CDN staging push jobs for advisories")
@click.option(
"--push/--no-push",
default=True,
show_default=True,
help="Re-trigger CDN push for advisories with failed or missing push jobs.",
)
@click.option(
"-o",
"--output",
type=click.Choice(["text", "json"]),
default="text",
show_default=True,
help="Output format.",
)
@verify_output_option
@click.pass_obj
@click_coroutine
async def verify_cdn_push_cli(runtime, push, output):
Expand All @@ -250,12 +228,10 @@ async def verify_cdn_push_cli(runtime, push, output):
elliott --group openshift-4.18 --assembly 4.18.51 verify-cdn-push
"""
runtime.initialize()
advisories = get_advisory_ids(runtime)
advisories = get_assembly_advisory_ids(runtime, include_types=CDN_PUSH_ADVISORY_TYPES)
if not advisories:
raise click.UsageError(f"No advisory IDs found for {CDN_PUSH_ADVISORY_TYPES} in assembly config.")

LOGGER.info("Checking CDN push status for advisories: %s", advisories)
result = await verify_cdn_push(advisories=advisories, do_push=push)
click.echo(render_result(result, output))
if not result.complete:
raise SystemExit(1)
handle_verify_result(result, output)
120 changes: 49 additions & 71 deletions elliott/elliottlib/cli/verify_kernel_tag_cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
import json
import logging
import re
from dataclasses import dataclass, field
Expand All @@ -8,12 +7,17 @@
import click
import koji
import requests
from artcommonlib.assembly import assembly_config_struct
from artcommonlib.constants import BREW_DOWNLOAD_URL, BREW_HUB

from elliottlib import brew
from elliottlib.cli.common import cli, click_coroutine
from elliottlib.errata_async import AsyncErrataAPI
from elliottlib.verify_common import (
VerifyResultBase,
get_assembly_advisory_ids,
handle_verify_result,
verify_output_option,
)

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -45,7 +49,7 @@ def failed(self) -> bool:


@dataclass
class VerifyKernelTagResult:
class VerifyKernelTagResult(VerifyResultBase):
advisories: list[AdvisoryKernelResult] = field(default_factory=list)
stop_ship_tag: str = ""

Expand All @@ -57,6 +61,45 @@ def passed(self) -> bool:
def failed(self) -> bool:
return any(a.failed for a in self.advisories)

def to_dict(self) -> dict:
return {
"passed": self.passed,
"failed": self.failed,
"stop_ship_tag": self.stop_ship_tag,
"advisories": [
{
"advisory_id": a.advisory_id,
"impetus": a.impetus,
"rhcos_builds": a.rhcos_builds,
"kernel_builds": [{"nvr": k.nvr, "has_stop_ship": k.has_stop_ship} for k in a.kernel_builds],
"skipped": a.skipped,
"error": a.error,
}
for a in self.advisories
],
}

def render_text(self) -> str:
lines = [f"Kernel stop-ship tag check (tag: {self.stop_ship_tag})", ""]
for a in self.advisories:
if a.skipped:
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): SKIPPED (no RHCOS/kernel)")
elif a.failed:
status = "STOP-SHIP" if any(k.has_stop_ship for k in a.kernel_builds) else "ERROR"
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): {status}")
else:
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): OK")

if a.error:
lines.append(f" Error: {a.error}")
for k in a.kernel_builds:
tag_status = "STOP-SHIP" if k.has_stop_ship else "ok"
lines.append(f" {k.nvr}: {tag_status}")
lines.append("")
overall = "PASS" if self.passed else "FAIL"
lines.append(f"Overall: {overall}")
return "\n".join(lines)


def get_rpm_deliveries_config(runtime) -> list:
rpm_deliveries = runtime.group_config.get("rpm_deliveries")
Expand Down Expand Up @@ -196,71 +239,8 @@ async def verify_kernel_tag(
return result


def render_result(result: VerifyKernelTagResult, output: str) -> str:
if output == "json":
return json.dumps(
{
"passed": result.passed,
"failed": result.failed,
"stop_ship_tag": result.stop_ship_tag,
"advisories": [
{
"advisory_id": a.advisory_id,
"impetus": a.impetus,
"rhcos_builds": a.rhcos_builds,
"kernel_builds": [{"nvr": k.nvr, "has_stop_ship": k.has_stop_ship} for k in a.kernel_builds],
"skipped": a.skipped,
"error": a.error,
}
for a in result.advisories
],
},
indent=2,
)

lines = [f"Kernel stop-ship tag check (tag: {result.stop_ship_tag})", ""]
for a in result.advisories:
if a.skipped:
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): SKIPPED (no RHCOS/kernel)")
elif a.failed:
status = "STOP-SHIP" if any(k.has_stop_ship for k in a.kernel_builds) else "ERROR"
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): {status}")
else:
lines.append(f" Advisory {a.advisory_id} ({a.impetus}): OK")

if a.error:
lines.append(f" Error: {a.error}")
for k in a.kernel_builds:
tag_status = "STOP-SHIP" if k.has_stop_ship else "ok"
lines.append(f" {k.nvr}: {tag_status}")
lines.append("")

overall = "PASS" if result.passed else "FAIL"
lines.append(f"Overall: {overall}")
return "\n".join(lines)


def get_advisory_ids(runtime) -> dict[str, int]:
releases_config = runtime.get_releases_config()
group_config = assembly_config_struct(releases_config, runtime.assembly, "group", {})
advisories = group_config.get("advisories", {})
result = {}
for impetus in KERNEL_TAG_ADVISORY_TYPES:
ad_id = advisories.get(impetus)
if ad_id:
result[impetus] = int(ad_id)
return result


@cli.command("verify-kernel-tag", short_help="Check RHCOS kernel builds for stop-ship tags")
@click.option(
"-o",
"--output",
type=click.Choice(["text", "json"]),
default="text",
show_default=True,
help="Output format.",
)
@verify_output_option
@click.pass_obj
@click_coroutine
async def verify_kernel_tag_cli(runtime, output):
Expand Down Expand Up @@ -288,7 +268,7 @@ async def verify_kernel_tag_cli(runtime, output):
if not kernel_packages or not stop_ship_tag:
raise click.UsageError("No kernel packages or stop_ship_tag found in rpm_deliveries config.")

advisories = get_advisory_ids(runtime)
advisories = get_assembly_advisory_ids(runtime, include_types=KERNEL_TAG_ADVISORY_TYPES)
if not advisories:
raise click.UsageError(f"No advisory IDs found for {KERNEL_TAG_ADVISORY_TYPES} in assembly config.")

Expand All @@ -307,6 +287,4 @@ async def verify_kernel_tag_cli(runtime, output):
kernel_packages=kernel_packages,
stop_ship_tag=stop_ship_tag,
)
click.echo(render_result(result, output))
if not result.passed:
raise SystemExit(1)
handle_verify_result(result, output)
Loading
Loading