1## This Source Code Form is subject to the terms of the Mozilla Public 2## License, v. 2.0. If a copy of the MPL was not distributed with this 3## file, You can obtain one at https://mozilla.org/MPL/2.0/. 4## 5## Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved. 6 7defmodule RabbitMQ.CLI.Ctl.Commands.ExportDefinitionsCommand do 8 alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers} 9 10 @behaviour RabbitMQ.CLI.CommandBehaviour 11 12 def merge_defaults(["-"] = args, opts) do 13 {args, Map.merge(%{format: "json", silent: true}, Helpers.case_insensitive_format(opts))} 14 end 15 def merge_defaults(args, opts) do 16 {args, Map.merge(%{format: "json"}, Helpers.case_insensitive_format(opts))} 17 end 18 19 def switches(), do: [timeout: :integer, format: :string] 20 def aliases(), do: [t: :timeout] 21 22 def validate(_, %{format: format}) 23 when format != "json" and format != "JSON" and format != "erlang" do 24 {:validation_failure, {:bad_argument, "Format should be either json or erlang"}} 25 end 26 def validate([], _) do 27 {:validation_failure, :not_enough_args} 28 end 29 def validate(args, _) when length(args) > 1 do 30 {:validation_failure, :too_many_args} 31 end 32 # output to stdout 33 def validate(["-"], _) do 34 :ok 35 end 36 def validate([path], _) do 37 dir = Path.dirname(path) 38 case File.exists?(dir, [raw: true]) do 39 true -> :ok 40 false -> {:validation_failure, {:bad_argument, "Directory #{dir} does not exist"}} 41 end 42 end 43 def validate(_, _), do: :ok 44 45 use RabbitMQ.CLI.Core.RequiresRabbitAppRunning 46 47 def run(["-"], %{node: node_name, timeout: timeout}) do 48 case :rabbit_misc.rpc_call(node_name, :rabbit_definitions, :all_definitions, [], timeout) do 49 {:error, _} = err -> err 50 {:error, _, _} = err -> err 51 result -> {:ok, result} 52 end 53 end 54 def run([path], %{node: node_name, timeout: timeout, format: format}) do 55 case :rabbit_misc.rpc_call(node_name, :rabbit_definitions, :all_definitions, [], timeout) do 56 {:badrpc, _} = err -> err 57 {:error, _} = err -> err 58 {:error, _, _} = err -> err 59 result -> 60 # write to the file in run/2 because output/2 is not meant to 61 # produce side effects 62 body = serialise(result, format) 63 abs_path = Path.absname(path) 64 65 File.rm(abs_path) 66 case File.write(abs_path, body) do 67 # no output 68 :ok -> {:ok, nil} 69 {:error, :enoent} -> 70 {:error, ExitCodes.exit_dataerr(), "Parent directory or file #{path} does not exist"} 71 {:error, :enotdir} -> 72 {:error, ExitCodes.exit_dataerr(), "Parent directory of file #{path} is not a directory"} 73 {:error, :enospc} -> 74 {:error, ExitCodes.exit_dataerr(), "No space left on device hosting #{path}"} 75 {:error, :eacces} -> 76 {:error, ExitCodes.exit_dataerr(), "No permissions to write to file #{path} or its parent directory"} 77 {:error, :eisdir} -> 78 {:error, ExitCodes.exit_dataerr(), "Path #{path} is a directory"} 79 {:error, err} -> 80 {:error, ExitCodes.exit_dataerr(), "Could not write to file #{path}: #{err}"} 81 end 82 end 83 end 84 85 def output({:ok, nil}, _) do 86 {:ok, nil} 87 end 88 def output({:ok, result}, %{format: "json"}) when is_map(result) do 89 {:ok, serialise(result, "json")} 90 end 91 def output({:ok, result}, %{format: "erlang"}) when is_map(result) do 92 {:ok, serialise(result, "erlang")} 93 end 94 use RabbitMQ.CLI.DefaultOutput 95 96 def printer(), do: RabbitMQ.CLI.Printers.StdIORaw 97 98 def usage, do: "export_definitions <file_path | \"-\"> [--format <json | erlang>]" 99 100 def usage_additional() do 101 [ 102 ["<file>", "Local file path to export to. Pass a dash (-) for stdout."], 103 ["--format", "output format to use: json or erlang"] 104 ] 105 end 106 107 def usage_doc_guides() do 108 [ 109 DocGuide.definitions() 110 ] 111 end 112 113 def help_section(), do: :definitions 114 115 def description(), do: "Exports definitions in JSON or compressed Erlang Term Format." 116 117 def banner([path], %{format: fmt}), do: "Exporting definitions in #{human_friendly_format(fmt)} to a file at \"#{path}\" ..." 118 119 # 120 # Implementation 121 # 122 123 defp serialise(raw_map, "json") do 124 # make sure all runtime parameter values are maps, otherwise 125 # they will end up being a list of pairs (a keyword list/proplist) 126 # in the resulting JSON document 127 map = Map.update!(raw_map, :parameters, fn(params) -> 128 Enum.map(params, fn(param) -> 129 Map.update!(param, "value", &:rabbit_data_coercion.to_map/1) 130 end) 131 end) 132 {:ok, json} = JSON.encode(map) 133 json 134 end 135 136 defp serialise(map, "erlang") do 137 :erlang.term_to_binary(map, [{:compressed, 9}]) 138 end 139 140 defp human_friendly_format("JSON"), do: "JSON" 141 defp human_friendly_format("json"), do: "JSON" 142 defp human_friendly_format("erlang"), do: "Erlang term format" 143end 144