Functions for commands that can be ran from the command-line with the Clojure CLI or the Metabase JAR. These are ran as follows: for example, running the clojure -M:run migrate force java --add-opens java.base/java.nio=ALL-UNNAMED -jar metabase.jar migrate force Logic below translates resolves the command itself to a function marked with You can see what commands are available by running the command | (ns metabase.cmd (:refer-clojure :exclude [load import]) (:require [clojure.string :as str] [clojure.tools.cli :as cli] [environ.core :as env] [metabase.config :as config] [metabase.legacy-mbql.util :as mbql.u] [metabase.plugins.classloader :as classloader] [metabase.util :as u] [metabase.util.i18n :refer [trs]] [metabase.util.log :as log])) |
(set! *warn-on-reflection* true) | |
Command processing and option parsing utilities, etc. | |
Proxy function to System/exit to enable the use of | (defn- system-exit! [return-code] (System/exit return-code)) |
Looks up a command var by name | (defn- cmd->var [command-name] (ns-resolve 'metabase.cmd (symbol command-name))) |
Resolves enterprise command by symbol and calls with args, or else throws error if not EE | (defn- call-enterprise [symb & args] (let [f (try (classloader/require (symbol (namespace symb))) (or (resolve symb) (throw (ex-info (trs "{0} does not exist" symb) {}))) (catch Throwable e (throw (ex-info (trs "The ''{0}'' command is only available in Metabase Enterprise Edition." (name symb)) {:command symb} e))))] (apply f args))) |
(defn- get-parsed-options [iref options] (:options (cli/parse-opts options (:arg-spec (meta iref))))) | |
Command implementations | |
Run database migrations. Valid options for | (defn ^:command migrate [direction] (classloader/require 'metabase.cmd.migrate) ((resolve 'metabase.cmd.migrate/migrate!) direction)) |
Transfer data from existing H2 database to the newly created MySQL or Postgres DB specified by env vars. | (defn ^:command load-from-h2 ([] (load-from-h2 nil)) ([h2-connection-string] (classloader/require 'metabase.cmd.load-from-h2) ((resolve 'metabase.cmd.load-from-h2/load-from-h2!) h2-connection-string))) |
(defn ^:command dump-to-h2 {:doc "Transfer data from existing database to newly created H2 DB with specified filename. Target H2 file is deleted before dump, unless the --keep-existing flag is given." :arg-spec [["-k" "--keep-existing" "Do not delete target H2 file if it exists." :id :keep-existing?] ["-p" "--dump-plaintext" "Do not encrypt dumped contents." :id :dump-plaintext?]]} [h2-filename & opts] (classloader/require 'metabase.cmd.dump-to-h2) (try (let [options (get-parsed-options #'dump-to-h2 opts)] ((resolve 'metabase.cmd.dump-to-h2/dump-to-h2!) h2-filename options) (println "Dump complete") (system-exit! 0)) (catch Throwable e (log/error e "Failed to dump application database to H2 file") (system-exit! 1)))) | |
Start Metabase the usual way and exit. Useful for profiling Metabase launch time. | (defn ^:command profile [] ;; override env var that would normally make Jetty block forever (alter-var-root #'env/env assoc :mb-jetty-join "false") (u/profile "start-normally" ((resolve 'metabase.core/start-normally)))) |
Reset the password for a user with | (defn ^:command reset-password [email-address] (classloader/require 'metabase.cmd.reset-password) ((resolve 'metabase.cmd.reset-password/reset-password!) email-address)) |
Show this help message listing valid Metabase commands. | (defn ^:command help ([command-name] (let [{:keys [doc arg-spec arglists]} (meta (cmd->var command-name))] (doseq [arglist arglists] (apply println command-name arglist)) (when doc (doseq [doc-line (str/split doc #"\n\s+")] (println "\t" doc-line))) (when (seq arg-spec) (println "\t" "Options:") (doseq [opt-line (str/split (:summary (cli/parse-opts [] arg-spec)) #"\n")] (println "\t" opt-line))))) ([] (println "Valid commands are:") (doseq [[symb varr] (sort (ns-interns 'metabase.cmd)) :when (:command (meta varr))] (help symb) (println)) (println "\nSome other commands you might find useful:\n") (println "java -cp metabase.jar org.h2.tools.Shell -url jdbc:h2:/path/to/metabase.db") (println "\tOpen an SQL shell for the Metabase H2 DB"))) |
Print version information about Metabase and the current system. | (defn ^:command version [] (println "Metabase version:" config/mb-version-info) (println "\nOS:" (System/getProperty "os.name") (System/getProperty "os.version") (System/getProperty "os.arch")) (println "\nJava version:" (System/getProperty "java.vm.name") (System/getProperty "java.version")) (println "\nCountry:" (System/getProperty "user.country")) (println "System timezone:" (System/getProperty "user.timezone")) (println "Language:" (System/getProperty "user.language")) (println "File encoding:" (System/getProperty "file.encoding"))) |
Generate a markdown file containing documentation for all API endpoints. This is written to a file called
| (defn ^:command api-documentation [] (classloader/require 'metabase.cmd.endpoint-dox) ((resolve 'metabase.cmd.endpoint-dox/generate-dox!))) |
Generates a markdown file containing documentation for environment variables relevant to configuring Metabase. The command only includes environment variables registered as defsettings. For a full list of environment variables, see https://www.metabase.com/docs/latest/configuring-metabase/environment-variables. | (defn ^:command environment-variables-documentation [] (classloader/require 'metabase.cmd.env-var-dox) ((resolve 'metabase.cmd.env-var-dox/generate-dox!))) |
Generates a markdown file with some documentation and an example configuration file in YAML. The YAML template includes Metabase settings and their defaults.
Metabase will save the template as | (defn ^:command config-template [] (classloader/require 'metabase.cmd.config-file-gen) ((resolve 'metabase.cmd.config-file-gen/generate-config-file-doc!))) |
Print a list of all multimethods available for a driver to implement, optionally with their docstrings. | (defn ^:command driver-methods ([] (classloader/require 'metabase.cmd.driver-methods) ((resolve 'metabase.cmd.driver-methods/print-available-multimethods) false)) ([_docs] (classloader/require 'metabase.cmd.driver-methods) ((resolve 'metabase.cmd.driver-methods/print-available-multimethods) true))) |
(defn ^:command load {:doc "Note: this command is deprecated. Use `import` instead. Load serialized Metabase instance as created by [[dump]] command from directory `path`." :arg-spec [["-m" "--mode (skip|update)" "Update or skip on conflicts." :default :skip :default-desc "skip" :parse-fn mbql.u/normalize-token :validate [#{:skip :update} "Must be 'skip' or 'update'"]] ["-e" "--on-error (continue|abort)" "Abort or continue on error." :default :continue :default-desc "continue" :parse-fn mbql.u/normalize-token :validate [#{:continue :abort} "Must be 'continue' or 'abort'"]]]} [path & options] (log/warn (u/colorize :red "'load' is deprecated and will be removed in a future release. Please migrate to 'import'.")) (call-enterprise 'metabase-enterprise.serialization.cmd/v1-load! path (get-parsed-options #'load options))) | |
(defn ^:command ^:requires-init import {:doc "Load serialized Metabase instance as created by the [[export]] command from directory `path`." :arg-spec [["-e" "--continue-on-error" "Do not break execution on errors."] [ "--full-stacktrace" "Output full stacktraces on errors."]]} [path & options] (call-enterprise 'metabase-enterprise.serialization.cmd/v2-load! path (get-parsed-options #'import options))) | |
(defn ^:command dump {:doc "Note: this command is deprecated. Use `export` instead. Serializes Metabase instance into directory `path`." :arg-spec [["-u" "--user EMAIL" "Export collections owned by the specified user"] ["-s" "--state (active|all)" "When set to `active`, do not dump archived entities. Default behavior is `all`." :default :all :default-desc "all" :parse-fn mbql.u/normalize-token :validate [#{:active :all} "Must be 'active' or 'all'"]] [nil "--include-entity-id" "Include entity_id property in all dumped entities. Default: false."]]} [path & options] (log/warn (u/colorize :red "'dump' is deprecated and will be removed in a future release. Please migrate to 'export'.")) (call-enterprise 'metabase-enterprise.serialization.cmd/v1-dump! path (get-parsed-options #'dump options))) | |
(defn ^:command export {:doc "Serialize Metabase instance into directory at `path`." :arg-spec [["-c" "--collection ID" "Export only specified ID(s). Use commas to separate multiple IDs. Pass either PKs or entity IDs." :id :collection-ids :parse-fn (fn [raw-string] (->> (str/split raw-string #"\s*,\s*") (map (fn [v] (cond (str/starts-with? v "eid:") v (= (count v) 21) v :else (parse-long v))))))] ["-C" "--no-collections" "Do not export any content in collections."] ["-S" "--no-settings" "Do not export settings.yaml"] ["-D" "--no-data-model" "Do not export any data model entities; useful for subsequent exports."] ["-f" "--include-field-values" "Include field values along with field metadata."] ["-s" "--include-database-secrets" "Include database connection details (in plain text; use caution)."] ["-e" "--continue-on-error" "Do not break execution on errors."] [ "--full-stacktrace" "Output full stacktraces on errors."]]} [path & options] (call-enterprise 'metabase-enterprise.serialization.cmd/v2-dump! path (get-parsed-options #'export options))) | |
Add entity IDs for instances of serializable models that don't already have them. | (defn ^:command seed-entity-ids [] (when-not (call-enterprise 'metabase-enterprise.serialization.cmd/seed-entity-ids!) (throw (Exception. "Error encountered while seeding entity IDs")))) |
Drop entity IDs for instances of serializable models. Useful for migrating from v1 serialization (x.46 and earlier) to v2 (x.47+). | (defn ^:command drop-entity-ids [] (when-not (call-enterprise 'metabase-enterprise.serialization.cmd/drop-entity-ids!) (throw (Exception. "Error encountered while dropping entity IDs")))) |
Rotate the encryption key of a metabase database. The MBENCRYPTIONSECRET_KEY environment variable has to be set to
the current key, and the parameter | (defn ^:command rotate-encryption-key [new-key] (classloader/require 'metabase.cmd.rotate-encryption-key) (try ((resolve 'metabase.cmd.rotate-encryption-key/rotate-encryption-key!) new-key) (log/info "Encryption key rotation OK.") (system-exit! 0) (catch Throwable e (log/error e "ERROR ROTATING KEY.") (system-exit! 1)))) |
------------------------------------------------ Validate Commands ---------------------------------------------- | |
(defn- arg-list-count-ok? [arg-list arg-count] (if (some #{'&} arg-list) ;; subtract 1 for the & and 1 for the symbol after & ;; e.g. [a b & c] => 2 (>= arg-count (- (count arg-list) 2)) (= arg-count (count arg-list)))) | |
(defn- arg-count-errors [command-name args] (let [arg-lists (-> command-name cmd->var meta :arglists)] (when-not (some #(arg-list-count-ok? % (count args)) arg-lists) (str "The '" command-name "' command requires " (when (> 1 (count arg-lists)) "one of ") "the following arguments: " (str/join " | " (map pr-str arg-lists)) ", but received: " (pr-str (vec args)) ".")))) | |
------------------------------------------------ Running Commands ------------------------------------------------ | |
Returns [error-message] if there is an error, otherwise [nil command-fn] | (defn- validate [command-name args] (let [varr (cmd->var command-name) {:keys [command arg-spec]} (meta varr) err (arg-count-errors command-name args)] (cond (not command) [(str "Unrecognized command: '" command-name "'") (str "Valid commands: " (str/join ", " (map key (filter (comp :command meta val) (ns-interns 'metabase.cmd)))))] err [err] arg-spec (:errors (cli/parse-opts args arg-spec))))) |
(defn- requires-init? [command-name] (-> command-name cmd->var meta :requires-init)) | |
(defn- fail! [& messages] (doseq [msg messages] (println (u/format-color 'red msg))) (System/exit 1)) | |
Run | (defn run-cmd [command-name init-fn args] (if-let [errors (validate command-name args)] (do (when (cmd->var command-name) (println "Usage:") (help command-name)) (apply fail! errors)) (try (when (requires-init? command-name) (init-fn)) (apply @(cmd->var command-name) args) (catch Throwable e (when (:cmd/exit (ex-data e)) ;; fast-track for commands that have their own error handling (System/exit 1)) (.printStackTrace e) (fail! (str "Command failed with exception: " (.getMessage e)))))) (System/exit 0)) |