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
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,17 @@ You also need:
- A **Fedora account** with permission to run builds against the Koji
targets referenced in your test configuration (the sample config uses
staging-oriented settings and `scratch_build: true`).
- **Kerberos credentials** for Koji. EBS inside the container uses the
host's KCM socket:
- **Kerberos credentials** for Koji. Local testing uses the host KCM
socket after you obtain a ticket:

```bash
kinit your_fedora_username@FEDORAPROJECT.ORG
koji hello
```

Production/`run.sh` authenticates in-process via python-gssapi when
`--krb5-keytab-file` is set; otherwise an existing TGT in `$KRB5CCNAME`
(or the system default ccache) is used. There is no background `kinit`
or `koji hello` readiness loop.
- **Test configuration** under `tests/etc/`, mirroring the container layout
at `/etc/elnbuildsync/`:

Expand Down Expand Up @@ -316,14 +319,22 @@ Useful options:

# Use production Fedora Messaging broker config (instead of staging)
./tests/local_test_daemon.sh --environment prod

# Optional Kerberos overrides for keytab-based TGT acquisition. Without a
# keytab, the host KCM / existing ccache is used after kinit.
./tests/local_test_daemon.sh --krb5-keytab-file /path/to/krb5.keytab
./tests/local_test_daemon.sh \
--krb5-keytab-file /path/to/krb5.keytab \
--krb5-keytab-principal 'eln-buildsync@FEDORAPROJECT.ORG'
```

When you stop the script (Ctrl+C), the ephemeral PostgreSQL container is
removed.

### Verify it is working

1. Confirm `koji hello` works on the host before starting the daemon.
1. Confirm you have a valid Kerberos TGT on the host (`klist`) before
starting the local test daemon.
2. After startup, open
[http://localhost:8080/status.html](http://localhost:8080/status.html).
3. Watch `/tmp/elnbuildsync.log` for batch activity when Rawhide tag
Expand Down Expand Up @@ -353,8 +364,12 @@ git repository (see `run.sh --dynamic-config-url`) or
SMTP, and OIDC client secrets mount at `/etc/elnbuildsync/secrets/`
(`ebs_db_pw`, `ebs_smtp_pw`, `ebs_oidc_client_secret`). Pass
`--openid-client-secret-file` when the secret is mounted elsewhere, and
`--openid-ca-file` when the OIDC provider uses a non-public CA. A
service keytab is used for `eln-buildsync@FEDORAPROJECT.ORG`. OpenShift
`--openid-ca-file` when the OIDC provider uses a non-public CA. Kerberos
is handled in-process with python-gssapi: optionally pass
`--krb5-keytab-file` for TGT acquisition and `--krb5-keytab-principal`
(or rely on guessing `koji.username` plus realm from `koji.profile`) for
that keytab-based TGT acquisition only. Without a keytab, the daemon uses
an existing TGT from `$KRB5CCNAME` or the system default ccache. OpenShift
deployment is managed via
[infra-ansible](https://forge.fedoraproject.org/infra/ansible)
(`playbooks/openshift-apps/elnbuildsync.yml`).
Expand Down
6 changes: 2 additions & 4 deletions elnbuildsync/batching.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,9 @@ async def rebuild_from_components(downstream_components):
"""Takes an iterable of downstream component names and rebuilds them."""

# Fake up a TagMessage for each of these to enqueue into the next batch
bsys = kojihelpers.connection.get_buildsys()

src_tag = config.control["trigger_tag"]
latest_tagged_rawhide_pkgs = await call_koji(
bsys.listTagged, src_tag, latest=True, inherit=True
"listTagged", src_tag, latest=True, inherit=True
)
latest_tagged_rawhide_table = {
pkg["name"]: pkg for pkg in latest_tagged_rawhide_pkgs
Expand All @@ -109,7 +107,7 @@ async def rebuild_from_components(downstream_components):
dest_tag,
) = await kojihelpers.tags.get_tags_for_target(config.main["koji"]["build_target"])
latest_tagged_eln_pkgs = await call_koji(
bsys.listTagged, dest_tag, latest=True, inherit=True
"listTagged", dest_tag, latest=True, inherit=True
)
latest_tagged_eln_table = {pkg["name"]: pkg for pkg in latest_tagged_eln_pkgs}

Expand Down
5 changes: 2 additions & 3 deletions elnbuildsync/cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,13 @@ async def periodic_cleanup():
return

logger.debug("Starting periodic cleanup.")
bsys = kojihelpers.connection.get_buildsys()

# We have the set of desired packages from Content Resolver
desired_pkg_names = set(config.comps["downstream_components"].keys())

# Get the list of packages currently tagged into the stable tag
latest_tagged_dest_pkgs = await call_koji(
bsys.listTagged, config.main["koji"]["stable_tag"], latest=True
"listTagged", config.main["koji"]["stable_tag"], latest=True
)

# Get the list of up-to-date packages in the destination tag
Expand All @@ -52,7 +51,7 @@ async def periodic_cleanup():

# Get the complete list of builds tagged into the stable tag
all_tagged_dest_pkgs = await call_koji(
bsys.listTagged, config.main["koji"]["stable_tag"], latest=False
"listTagged", config.main["koji"]["stable_tag"], latest=False
)
all_tagged_dest_nvrs = {pkg["nvr"] for pkg in all_tagged_dest_pkgs}

Expand Down
43 changes: 43 additions & 0 deletions elnbuildsync/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
status,
web,
)
from .config import ConfigError
from .kojihelpers import connection as koji_connection

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -126,6 +128,24 @@ def _resolve_dynamic_source(dynamic_config_url, dynamic_config_file):
default=False,
help="Untag all but the most recent builds in the destination target",
)
@click.option(
"--krb5-keytab-file",
default=None,
type=click.Path(dir_okay=False),
help=(
"Kerberos keytab for in-process TGT acquisition (kinit). "
"If omitted, use an existing TGT from $KRB5CCNAME or the system default ccache"
),
)
@click.option(
"--krb5-keytab-principal",
default=None,
show_default="automatic, guessed from static configuration when using a keytab",
help=(
"Kerberos principal to use when acquiring a TGT from --krb5-keytab-file. "
"Ignored unless a keytab is specified"
),
)
def main(
log_level,
dry_run,
Expand All @@ -138,6 +158,8 @@ def main(
openid_client_secret_file,
openid_ca_file,
untagging,
krb5_keytab_file,
krb5_keytab_principal,
):
logging.basicConfig(
format="%(asctime)s : %(name)s : %(levelname)s : %(message)s",
Expand All @@ -156,6 +178,9 @@ def main(
config.do_untagging = untagging
config.message_batch_timer = lull_time

if krb5_keytab_principal and not krb5_keytab_file:
raise click.UsageError("--krb5-keytab-principal requires --krb5-keytab-file")

dynamic_url, dynamic_file = _resolve_dynamic_source(
dynamic_config_url, dynamic_config_file
)
Expand All @@ -172,6 +197,8 @@ def main(
dynamic_file,
openid_client_secret_file,
openid_ca_file,
krb5_keytab_file,
krb5_keytab_principal,
)
)
)
Expand All @@ -186,6 +213,8 @@ async def _main(
dynamic_config_file=None,
openid_client_secret_file=None,
openid_ca_file=None,
krb5_keytab_file=None,
krb5_keytab_principal=None,
) -> None:
auth.openid_ca_file = openid_ca_file
config.terminator = Deferred()
Expand All @@ -206,6 +235,20 @@ async def _main(
db_pw,
oidc_client_secret_file=openid_client_secret_file,
)
keytab_principal = None
if krb5_keytab_file:
try:
keytab_principal = koji_connection.resolve_krb5_keytab_principal(
krb5_keytab_principal,
config.main["koji"]["profile"],
config.main["koji"].get("username"),
)
except ValueError as e:
raise ConfigError(str(e)) from e
koji_connection.configure_kerberos(
keytab_file=krb5_keytab_file,
keytab_principal=keytab_principal,
)
await config.load_dynamic_config(
dynamic_config_git_url=dynamic_config_url,
dynamic_config_file=dynamic_config_file,
Expand Down
23 changes: 8 additions & 15 deletions elnbuildsync/kojihelpers/builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from twisted.internet.defer import DeferredList

from .. import config
from .connection import call_koji, get_buildsys
from .connection import call_koji
from .errors import InfoUnavailableError

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -59,10 +59,8 @@ async def get_buildinfo(build_id, **kwargs):
:param build_id: The ID of the build (likely retrieved from a tagging message)
:returns: A dictionary of information about the build
"""
bsys = get_buildsys()

try:
buildinfo = await call_koji(bsys.getBuild, build_id, **kwargs)
buildinfo = await call_koji("getBuild", build_id, **kwargs)
except koji.GenericError as e:
logger.exception(f"Could not retrieve information for build {build_id}")
raise InfoUnavailableError(
Expand All @@ -72,8 +70,7 @@ async def get_buildinfo(build_id, **kwargs):
return buildinfo


def _get_multi_buildinfo_thread(build_ids, **kwargs):
bsys = get_buildsys()
def _get_multi_buildinfo_thread(bsys, build_ids, **kwargs):
build_vcalls = {}

with bsys.multicall(batch=config.koji_batch) as mc:
Expand Down Expand Up @@ -108,10 +105,8 @@ async def get_multi_buildinfo(build_ids, **kwargs):


async def get_taskinfo(task_id, **kwargs):
bsys = get_buildsys()

try:
taskinfo = await call_koji(bsys.getTaskInfo, task_id, **kwargs)
taskinfo = await call_koji("getTaskInfo", task_id, **kwargs)
except koji.GenericError as e:
logger.exception(f"Could not retrieve information for task {task_id}")
raise InfoUnavailableError(
Expand All @@ -128,7 +123,7 @@ async def get_taskinfo(task_id, **kwargs):

try:
children = await call_koji(
bsys.getTaskChildren, task_id, request=True, strict=True
"getTaskChildren", task_id, request=True, strict=True
)
except koji.GenericError as e:
logger.exception(f"Could not retrieve child information for task {task_id}")
Expand All @@ -148,7 +143,7 @@ async def get_taskinfo(task_id, **kwargs):
for child in children:
if child["method"] == "buildSRPMFromSCM":
try:
child["result"] = await call_koji(bsys.getTaskResult, child["id"])
child["result"] = await call_koji("getTaskResult", child["id"])
except koji.GenericError as e:
raise InfoUnavailableError(
f"SRPM build failed for {task_id}"
Expand Down Expand Up @@ -176,8 +171,7 @@ async def start_builds(target, scm_urls, scratch=False, fail_fast=False):
return task_index


def _start_builds_thread(target, scm_urls, scratch=False, fail_fast=False):
bsys = get_buildsys()
def _start_builds_thread(bsys, target, scm_urls, scratch=False, fail_fast=False):
build_vcalls = {}
try:
with bsys.multicall(batch=config.koji_batch) as mc:
Expand Down Expand Up @@ -234,8 +228,7 @@ async def wait_for_tasks(task_ids, timeout=config.task_timeout):
async def cancel_task(task_id):
logger.debug(f"Canceling task {task_id}")
try:
bsys = get_buildsys()
await call_koji(bsys.cancelTask, task_id, recurse=True)
await call_koji("cancelTask", task_id, recurse=True)
except Exception:
# Cancellation is best-effort
logger.exception("Could not cancel task %s. Ignoring.", task_id)
Loading