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 2018-2021 VMware, Inc. or its affiliates. 6%% 7%% @doc 8%% This module provides extra functions unused by the feature flags 9%% subsystem core functionality. 10 11-module(rabbit_ff_extra). 12 13-include_lib("stdout_formatter/include/stdout_formatter.hrl"). 14 15-export([cli_info/0, 16 info/1, 17 info/2, 18 format_error/1]). 19 20-type cli_info() :: [cli_info_entry()]. 21%% A list of feature flags properties, formatted for the RabbitMQ CLI. 22 23-type cli_info_entry() :: [{name, rabbit_feature_flags:feature_name()} | 24 {state, enabled | disabled | unavailable} | 25 {stability, rabbit_feature_flags:stability()} | 26 {provided_by, atom()} | 27 {desc, string()} | 28 {doc_url, string()}]. 29%% A list of properties for a single feature flag, formatted for the 30%% RabbitMQ CLI. 31 32-type info_options() :: #{colors => boolean(), 33 lines => boolean(), 34 verbose => non_neg_integer()}. 35%% Options accepted by {@link info/1} and {@link info/2}. 36 37-export_type([info_options/0]). 38 39-spec cli_info() -> cli_info(). 40%% @doc 41%% Returns a list of all feature flags properties. 42%% 43%% @returns the list of all feature flags properties. 44 45cli_info() -> 46 cli_info(rabbit_feature_flags:list(all)). 47 48-spec cli_info(rabbit_feature_flags:feature_flags()) -> cli_info(). 49%% @doc 50%% Formats a map of feature flags and their properties into a list of 51%% feature flags properties as expected by the RabbitMQ CLI. 52%% 53%% @param FeatureFlags A map of feature flags. 54%% @returns the list of feature flags properties, created from the map 55%% specified in arguments. 56 57cli_info(FeatureFlags) -> 58 lists:foldr( 59 fun(FeatureName, Acc) -> 60 FeatureProps = maps:get(FeatureName, FeatureFlags), 61 State = rabbit_feature_flags:get_state(FeatureName), 62 Stability = rabbit_feature_flags:get_stability(FeatureProps), 63 App = maps:get(provided_by, FeatureProps), 64 Desc = maps:get(desc, FeatureProps, ""), 65 DocUrl = maps:get(doc_url, FeatureProps, ""), 66 FFInfo = [{name, FeatureName}, 67 {desc, unicode:characters_to_binary(Desc)}, 68 {doc_url, unicode:characters_to_binary(DocUrl)}, 69 {state, State}, 70 {stability, Stability}, 71 {provided_by, App}], 72 [FFInfo | Acc] 73 end, [], lists:sort(maps:keys(FeatureFlags))). 74 75-spec info(info_options()) -> ok. 76%% @doc 77%% Displays an array of all supported feature flags and their properties 78%% on `stdout'. 79%% 80%% @param Options Options to tune what is displayed and how. 81 82info(Options) -> 83 %% Two tables: one for stable feature flags, one for experimental ones. 84 StableFF = rabbit_feature_flags:list(all, stable), 85 case maps:size(StableFF) of 86 0 -> 87 ok; 88 _ -> 89 stdout_formatter:display( 90 #paragraph{content = "\n## Stable feature flags:", 91 props = #{bold => true}}), 92 info(StableFF, Options) 93 end, 94 ExpFF = rabbit_feature_flags:list(all, experimental), 95 case maps:size(ExpFF) of 96 0 -> 97 ok; 98 _ -> 99 stdout_formatter:display( 100 #paragraph{content = "\n## Experimental feature flags:", 101 props = #{bold => true}}), 102 info(ExpFF, Options) 103 end, 104 case maps:size(StableFF) + maps:size(ExpFF) of 105 0 -> ok; 106 _ -> state_legend(Options) 107 end. 108 109-spec info(rabbit_feature_flags:feature_flags(), info_options()) -> ok. 110%% @doc 111%% Displays an array of feature flags and their properties on `stdout', 112%% based on the specified feature flags map. 113%% 114%% @param FeatureFlags Map of the feature flags to display. 115%% @param Options Options to tune what is displayed and how. 116 117info(FeatureFlags, Options) -> 118 Verbose = maps:get(verbose, Options, 0), 119 UseColors = use_colors(Options), 120 UseLines = use_lines(Options), 121 Title = case UseColors of 122 true -> #{title => true}; 123 false -> #{} 124 end, 125 Bold = case UseColors of 126 true -> #{bold => true}; 127 false -> #{} 128 end, 129 {Green, Yellow, Red} = case UseColors of 130 true -> 131 {#{fg => green}, 132 #{fg => yellow}, 133 #{bold => true, 134 bg => red}}; 135 false -> 136 {#{}, #{}, #{}} 137 end, 138 Border = case UseLines of 139 true -> #{border_drawing => ansi}; 140 false -> #{border_drawing => ascii} 141 end, 142 %% Table columns: 143 %% | Name | State | Provided by | Description 144 %% 145 %% where: 146 %% State = Enabled | Disabled | Unavailable (if a node doesn't 147 %% support it). 148 TableHeader = #row{cells = ["Name", 149 "State", 150 "Provided", 151 "Description"], 152 props = Title}, 153 Nodes = lists:sort([node() | rabbit_feature_flags:remote_nodes()]), 154 Rows = lists:map( 155 fun(FeatureName) -> 156 FeatureProps = maps:get(FeatureName, FeatureFlags), 157 State0 = rabbit_feature_flags:get_state(FeatureName), 158 {State, Color} = case State0 of 159 enabled -> 160 {"Enabled", Green}; 161 disabled -> 162 {"Disabled", Yellow}; 163 unavailable -> 164 {"Unavailable", Red} 165 end, 166 App = maps:get(provided_by, FeatureProps), 167 Desc = maps:get(desc, FeatureProps, ""), 168 VFun = fun(Node) -> 169 Supported = 170 rabbit_feature_flags:does_node_support( 171 Node, [FeatureName], 60000), 172 {Label, LabelColor} = 173 case Supported of 174 true -> {"supported", #{}}; 175 false -> {"unsupported", Red} 176 end, 177 #paragraph{content = 178 [rabbit_misc:format(" ~s: ", 179 [Node]), 180 #paragraph{content = Label, 181 props = LabelColor}]} 182 end, 183 ExtraLines = if 184 Verbose > 0 -> 185 NodesList = lists:join( 186 "\n", 187 lists:map( 188 VFun, Nodes)), 189 ["\n\n", 190 "Per-node support level:\n" 191 | NodesList]; 192 true -> 193 [] 194 end, 195 [#paragraph{content = FeatureName, 196 props = Bold}, 197 #paragraph{content = State, 198 props = Color}, 199 #paragraph{content = App}, 200 #paragraph{content = [Desc | ExtraLines]}] 201 end, lists:sort(maps:keys(FeatureFlags))), 202 io:format("~n", []), 203 stdout_formatter:display(#table{rows = [TableHeader | Rows], 204 props = Border#{cell_padding => {0, 1}}}). 205 206use_colors(Options) -> 207 maps:get(colors, Options, true). 208 209use_lines(Options) -> 210 maps:get(lines, Options, true). 211 212state_legend(Options) -> 213 UseColors = use_colors(Options), 214 {Green, Yellow, Red} = case UseColors of 215 true -> 216 {#{fg => green}, 217 #{fg => yellow}, 218 #{bold => true, 219 bg => red}}; 220 false -> 221 {#{}, #{}, #{}} 222 end, 223 Enabled = #paragraph{content = "Enabled", props = Green}, 224 Disabled = #paragraph{content = "Disabled", props = Yellow}, 225 Unavailable = #paragraph{content = "Unavailable", props = Red}, 226 stdout_formatter:display( 227 #paragraph{ 228 content = 229 ["\n", 230 "Possible states:\n", 231 " ", Enabled, ": The feature flag is enabled on all nodes\n", 232 " ", Disabled, ": The feature flag is disabled on all nodes\n", 233 " ", Unavailable, ": The feature flag cannot be enabled because" 234 " one or more nodes do not support it\n"]}). 235 236-spec format_error(any()) -> string(). 237%% @doc 238%% Formats the error reason term so it can be presented to human beings. 239%% 240%% @param Reason The term in the `{error, Reason}' tuple. 241%% @returns the formatted error reason. 242 243format_error(Reason) -> 244 rabbit_misc:format("~p", [Reason]). 245