diff --git a/src/bindings/python/fluxacct/accounting/project_subcommands.py b/src/bindings/python/fluxacct/accounting/project_subcommands.py index 1c2d2ae1..ff823815 100644 --- a/src/bindings/python/fluxacct/accounting/project_subcommands.py +++ b/src/bindings/python/fluxacct/accounting/project_subcommands.py @@ -115,3 +115,32 @@ def delete_project(conn, project): return warning_stmt return 0 + + +def list_projects(conn): + """ + List all of the available projects registered in the project_table. + """ + cur = conn.cursor() + + cur.execute("SELECT * FROM project_table") + rows = cur.fetchall() + + # fetch column names and determine width of each column + col_names = [description[0] for description in cur.description] + col_widths = [ + max(len(str(value)) for value in [col] + [row[i] for row in rows]) + for i, col in enumerate(col_names) + ] + + def format_row(row): + return " | ".join( + [f"{str(value).ljust(col_widths[i])}" for i, value in enumerate(row)] + ) + + header = format_row(col_names) + separator = "-+-".join(["-" * width for width in col_widths]) + data_rows = "\n".join([format_row(row) for row in rows]) + table = f"{header}\n{separator}\n{data_rows}" + + return table diff --git a/src/cmd/flux-account-service.py b/src/cmd/flux-account-service.py index 8cdfec94..dff81ae9 100755 --- a/src/cmd/flux-account-service.py +++ b/src/cmd/flux-account-service.py @@ -92,6 +92,7 @@ def __init__(self, flux_handle, conn): "view_job_records", "view_queue", "view_project", + "list_projects", ] privileged_endpoints = [ @@ -513,6 +514,20 @@ def delete_project(self, handle, watcher, msg, arg): msg, 0, f"a non-OSError exception was caught: {str(exc)}" ) + def list_projects(self, handle, watcher, msg, arg): + try: + val = p.list_projects(self.conn) + + payload = {"list_projects": val} + + handle.respond(msg, payload) + except KeyError as exc: + handle.respond_error(msg, 0, f"missing key in payload: {exc}") + except Exception as exc: + handle.respond_error( + msg, 0, f"a non-OSError exception was caught: {str(exc)}" + ) + def scrub_old_jobs(self, handle, watcher, msg, arg): try: val = jobs.scrub_old_jobs(self.conn, msg.payload["num_weeks"]) diff --git a/src/cmd/flux-account.py b/src/cmd/flux-account.py index 2571fb81..eb7bf407 100755 --- a/src/cmd/flux-account.py +++ b/src/cmd/flux-account.py @@ -519,6 +519,16 @@ def add_delete_project_arg(subparsers): ) +def add_list_projects_arg(subparsers): + subparser_list_projects = subparsers.add_parser( + "list-projects", + help="list all registered projects", + formatter_class=flux.util.help_formatter(), + ) + + subparser_list_projects.set_defaults(func="list_projects") + + def add_scrub_job_records_arg(subparsers): subparser = subparsers.add_parser( "scrub-old-jobs", @@ -628,6 +638,7 @@ def add_arguments_to_parser(parser, subparsers): add_add_project_arg(subparsers) add_view_project_arg(subparsers) add_delete_project_arg(subparsers) + add_list_projects_arg(subparsers) add_scrub_job_records_arg(subparsers) add_export_db_arg(subparsers) add_pop_db_arg(subparsers) @@ -805,6 +816,11 @@ def select_accounting_function(args, output_file, parser): "project": args.project, } return_val = flux.Flux().rpc("accounting.delete_project", data).get() + elif args.func == "list_projects": + data = { + "path": args.path, + } + return_val = flux.Flux().rpc("accounting.list_projects", data).get() elif args.func == "scrub_old_jobs": data = { "path": args.path, diff --git a/t/t1025-flux-account-projects.t b/t/t1025-flux-account-projects.t index 039a1333..8bb824df 100755 --- a/t/t1025-flux-account-projects.t +++ b/t/t1025-flux-account-projects.t @@ -43,6 +43,16 @@ test_expect_success 'add some queues to the DB' ' flux account add-queue special --priority=99999 ' +test_expect_success 'list contents of project_table before adding projects' ' + flux account list-projects > project_table.test && + cat <<-EOF >project_table.expected + project_id | project | usage + -----------+---------+------ + 1 | * | 0.0 + EOF + grep -f project_table.expected project_table.test +' + test_expect_success 'add some projects to the project_table' ' flux account add-project project_1 && flux account add-project project_2 && @@ -131,6 +141,18 @@ test_expect_success 'reset the projects list for an association' ' grep "\"projects\": \"*\"" user5018.json ' +test_expect_success 'list all of the projects currently registered in project_table' ' + flux account list-projects > project_table.test && + cat <<-EOF >project_table.expected + project_id | project | usage + -----------+-----------+------ + 1 | * | 0.0 + 3 | project_2 | 0.0 + 4 | project_3 | 0.0 + EOF + grep -f project_table.expected project_table.test +' + test_expect_success 'remove flux-accounting DB' ' rm $(pwd)/FluxAccountingTest.db '