Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite openqa-advanced-retrigger-jobs in python #332

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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: 98 additions & 16 deletions openqa-advanced-retrigger-jobs
Original file line number Diff line number Diff line change
@@ -1,16 +1,98 @@
#!/bin/sh -e
#worker="${worker:-"openqaworker4"}"
host="${host:-"openqa.opensuse.org"}"
failed_since="${failed_since:-"$(date -I)"}"
instance_string="${INSTANCE+" and instance='$INSTANCE'"}"
worker_string="${WORKER+"assigned_worker_id in (select id from workers where (host='$WORKER'$instance_string)) and "}"
result="${result:-"result='incomplete'"}"
additional_filters="${additional_filters+" and $additional_filters"}"
comment="${comment:-""}"
dry_run="${dry_run:-"0"}"
[ "$dry_run" = "1" ] && client_prefix="echo"
# shellcheck disable=SC2029
for i in $(ssh "$host" "sudo -u geekotest psql --no-align --tuples-only --command=\"select id from jobs where (${worker_string}${result} and clone_id is null and t_finished >= '$failed_since'$additional_filters);\" openqa"); do
$client_prefix openqa-cli api --host "$host" -X POST jobs/"$i"/restart
[ -n "$comment" ] && $client_prefix openqa-cli api --host "$host" -X POST jobs/"$i"/comments text="$comment"
done
#!/usr/bin/env python3

"""
Retrigger openQA jobs based on database queries.

Needs SSH access to the specified target openQA host.

Simple example call retriggering all recent incompletes on the default host:
%(prog)s

Advanced example retriggering failed instead of incompletes, verbose output, with custom starting date,
custom host and excluding jobs with \":investigate:\" in the name, executed as dry-run:
%(prog)s -vvvv --host openqa.example.org --failed-since '2000-01-01T10:00' --result failed \
--additional-filters \"test not like '%%:investigate:%%'\" --dry-run
"""

import argparse
import logging
import subprocess
import sys
from datetime import datetime

logging.basicConfig()
log = logging.getLogger(sys.argv[0] if __name__ == "__main__" else __name__)


class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
"""Preserve multi-line __doc__ and provide default arguments in help strings."""

pass


def parse_args():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=CustomFormatter)
parser.add_argument(
"-v",
"--verbose",
help="Increase verbosity level, specify multiple times to increase verbosity",
action="count",
default=1,
)
parser.add_argument("-H", "--host", default="openqa.opensuse.org", help="Target openQA host")
parser.add_argument(
"-s",
"--failed-since",
default=datetime.today().isoformat(),
help="Filter jobs failed since this date",
)
parser.add_argument("-w", "--worker", default=None, help="Filter jobs assigned to this worker")
parser.add_argument("-i", "--instance", default=None, help="Instance of the worker")
parser.add_argument("-r", "--result", default="incomplete", help="Filter jobs with this result")
parser.add_argument(
"-a",
"--additional-filters",
default=None,
help="Additional filters for the SQL query",
)
parser.add_argument("-c", "--comment", default=None, help="Comment to add to the retriggered jobs")
parser.add_argument(
"-d",
"--dry-run",
action="store_true",
help="If set, only print the actions without executing",
)
args = parser.parse_args()
logging_level = (5 - min(args.verbose, 4)) * 10
log.setLevel(logging_level)
return args

def post(dry_run: bool, host: str, route: str, *args):
cmd=('openqa-cli', 'api', '--host', host, '-X', 'POST', route, *args)
if dry_run:
return print(f"dry run: {cmd}")
subprocess.run(cmd, check=True)

def main(args):
log.debug(args)
instance_string = f" and instance='{args.instance}'" if args.instance else ""
worker_string = f"assigned_worker_id in (select id from workers where (host='{args.worker}'{instance_string})) and " if args.worker else ""
additional_filters = f" and {args.additional_filters}" if args.additional_filters else ""

query = (
f"select id from jobs where ({worker_string}result='{args.result}' "
f"and clone_id is null and t_finished >= '{args.failed_since}'{additional_filters});"
)

Comment on lines +82 to +85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally I'd say this needs escaping but of course the previous script also didn't have that…

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what additional escaping you mean. Why do you mean we need escaping?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think he means something in the lines of https://www.psycopg.org/psycopg3/docs/basic/params.html#execute-arguments

Currently e.g. args.result could be used to insert arbitrary SQL statements

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While malice is very unlikely (given you'd need access anyway) it could help if people accidentally pass arguments which cause the query to break

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And one can also (accidentally) break out of the whole psql invocation.

log.debug(f"Using SQL query: '{query}' on {args.host}")
ssh_command = ('ssh', args.host, f"sudo -u geekotest psql --no-align --tuples-only --command=\"{query}\" openqa")
job_ids = subprocess.check_output(ssh_command).decode().splitlines()

for job_id in job_ids:
post(args.dry_run, args.host, f"jobs/{job_id}/restart")
if args.comment:
post(args.dry_run, args.host, f"jobs/{job_id}/comments", f'text={args.comment}')


if __name__ == "__main__":
main(parse_args())
Loading