main: add support for CLI extensions via external binaries
authorLuca BRUNO <luca.bruno@coreos.com>
Mon, 20 Dec 2021 10:00:02 +0000 (10:00 +0000)
committerLuca BRUNO <luca.bruno@coreos.com>
Mon, 20 Dec 2021 10:00:02 +0000 (10:00 +0000)
This adds some logic to detect and dispatch unknown subcommands to
extensions available in `$PATH`. Additional commands can be
implemented by adding relevant `ostree-$verb` binaries to the system.

As an example, if a `/usr/bin/ostree-extcommand` extension is provided,
the execution of `ostree extcommand --help` will be dispatched to that
as `ostree-extcommand extcommand --help`.

Makefile-tests.am
src/ostree/main.c
src/ostree/ot-main.c
src/ostree/ot-main.h
tests/test-cli-extensions.sh [new file with mode: 0755]

index 69d3035d367c5479416d14276fd7d5e0d9022ae9..6bae65cf9021d52fb834076338dbcc0ce12c9b8f 100644 (file)
@@ -62,6 +62,7 @@ _installed_or_uninstalled_test_scripts = \
        tests/test-basic-user.sh \
        tests/test-basic-user-only.sh \
        tests/test-basic-root.sh \
+       tests/test-cli-extensions.sh \
        tests/test-pull-subpath.sh \
        tests/test-archivez.sh \
        tests/test-remote-add.sh \
index 0e47ede316e30cfc0c6f64c4b03abedcf7fc6482..7d17080cf400089e5332d7c37a04610c988ecec2 100644 (file)
@@ -26,7 +26,6 @@
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
-#include <locale.h>
 
 #include "ot-main.h"
 #include "ot-builtins.h"
@@ -131,22 +130,16 @@ int
 main (int    argc,
       char **argv)
 {
-  g_autoptr(GError) error = NULL;
-  int ret;
+  g_assert (argc > 0);
 
-  setlocale (LC_ALL, "");
-
-  g_set_prgname (argv[0]);
-
-  ret = ostree_run (argc, argv, commands, &error);
-
-  if (error != NULL)
+  g_autofree gchar *ext_command = ostree_command_lookup_external (argc, argv, commands);
+  if (ext_command != NULL)
     {
-      g_printerr ("%s%serror:%s%s %s\n",
-                  ot_get_red_start (), ot_get_bold_start (),
-                  ot_get_bold_end (), ot_get_red_end (),
-                  error->message);
+      argv[0] = ext_command;
+      return ostree_command_exec_external (argv);
+    }
+  else
+    {
+      return ostree_main (argc, argv, commands);
     }
-
-  return ret;
 }
index 017a65a1ce2b8d4438393bf96ef5adc1d7312491..8ee73038295bd673270d4b266f16c9ea4bda921d 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <gio/gio.h>
 
+#include <locale.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/statvfs.h>
@@ -140,6 +141,102 @@ message_handler (const gchar *log_domain,
     g_printerr ("%s: %s\n", g_get_prgname (), message);
 }
 
+int
+ostree_main (int    argc,
+             char **argv,
+             OstreeCommand *commands)
+{
+  g_autoptr(GError) error = NULL;
+
+  setlocale (LC_ALL, "");
+
+  g_set_prgname (argv[0]);
+
+  int ret = ostree_run (argc, argv, commands, &error);
+
+  if (error != NULL)
+    {
+      g_printerr ("%s%serror:%s%s %s\n",
+                  ot_get_red_start (), ot_get_bold_start (),
+                  ot_get_bold_end (), ot_get_red_end (),
+                  error->message);
+    }
+
+  return ret;
+}
+
+
+/**
+ * ostree_command_lookup_external:
+ * @argc: number of entries in @argv
+ * @argv: array of command-line arguments
+ * @commands: array of hardcoded internal commands
+ *
+ * Search for a relevant ostree extension binary in $PATH. Given a verb
+ * from argv, if it is not an internal command, it tries to locate a
+ * corresponding 'ostree-$verb' executable on the system.
+ *
+ * Returns: (transfer full) (nullable): callname (i.e. argv[0]) for the
+ * external command if found, or %NULL otherwise.
+ */
+gchar *
+ostree_command_lookup_external (int argc,
+                                char **argv,
+                                OstreeCommand *commands)
+{
+  g_assert (commands != NULL);
+
+  // Find the first verb (ignoring all earlier flags), then
+  // check if it is a known native command. Otherwise, try to look it
+  // up in $PATH.
+  // We ignore argv[0] here, the ostree binary itself is not multicall.
+  for (guint arg_index = 1; arg_index < argc; arg_index++)
+    {
+      char *current_arg = argv[arg_index];
+      if (current_arg == NULL ||
+          g_str_has_prefix (current_arg, "-") ||
+          g_strcmp0 (current_arg, "") == 0)
+          continue;
+
+      for (guint cmd_index = 0; commands[cmd_index].name != NULL; cmd_index++)
+        {
+          if (g_strcmp0 (current_arg, (commands[cmd_index]).name) == 0)
+            return NULL;
+        }
+
+      g_autofree gchar *ext_command = g_strdup_printf ("ostree-%s", current_arg);
+      if (g_find_program_in_path (ext_command) == NULL)
+          return NULL;
+
+      return g_steal_pointer (&ext_command);
+    }
+
+  return NULL;
+}
+
+/**
+ * ostree_command_exec_external:
+ * @argv: array of command-line arguments
+ *
+ * Execute an ostree extension binary.
+ *
+ * Returns: diverge on proper execution, otherwise return 1.
+ */
+int
+ostree_command_exec_external (char **argv)
+{
+  int r = execvp(argv[0], argv);
+  g_assert (r == -1);
+
+  setlocale (LC_ALL, "");
+  g_printerr ("%s%serror:%s%s: Executing %s: %s\n",
+              ot_get_red_start (), ot_get_bold_start (),
+              ot_get_bold_end (), ot_get_red_end (),
+              argv[0],
+              g_strerror (errno));
+  return 1;
+}
+
 int
 ostree_run (int    argc,
             char **argv,
index ed06e621004e13367803f8988a85baab6d6f7953..b369deb8743728b0211461582594824ecd867dce 100644 (file)
@@ -58,10 +58,16 @@ struct OstreeCommandInvocation {
   OstreeCommand *command;
 };
 
+int ostree_main (int argc, char **argv, OstreeCommand *commands);
+
 int ostree_run (int argc, char **argv, OstreeCommand *commands, GError **error);
 
 int ostree_usage (OstreeCommand *commands, gboolean is_error);
 
+char* ostree_command_lookup_external (int argc, char **argv, OstreeCommand *commands);
+
+int ostree_command_exec_external (char **argv);
+
 gboolean ostree_parse_sysroot_or_repo_option (GOptionContext *context,
                                               const char *sysroot_path,
                                               const char *repo_path,
diff --git a/tests/test-cli-extensions.sh b/tests/test-cli-extensions.sh
new file mode 100755 (executable)
index 0000000..6e483c5
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+#
+# Copyright (C) 2021 Red Hat Inc.
+# SPDX-License-Identifier: LGPL-2.0+
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+echo '1..2'
+
+mkdir -p ./localbin
+export PATH="./localbin/:${PATH}"
+ln -s /usr/bin/env ./localbin/ostree-env
+${CMD_PREFIX} ostree env --help >out.txt
+assert_file_has_content out.txt "with an empty environment"
+rm -rf -- localbin
+
+echo 'ok CLI extension localbin ostree-env'
+
+if ${CMD_PREFIX} ostree nosuchcommand 2>err.txt; then
+    assert_not_reached "missing CLI extension ostree-nosuchcommand succeeded"
+fi
+assert_file_has_content err.txt "Unknown command 'nosuchcommand'"
+rm -f -- err.txt
+
+echo 'ok CLI extension unknown ostree-nosuchcommand'