Skip to content

Commit 8b13372

Browse files
Merge pull request #12059 from rabbitmq/mergify/bp/v3.13.x/pr-12058
Make it hard to accidentally enable an experimental feature flag (backport #11998) (backport #12058)
2 parents 9393db1 + 9bc9dd0 commit 8b13372

File tree

3 files changed

+114
-26
lines changed

3 files changed

+114
-26
lines changed

deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/enable_feature_flag_command.ex

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,54 +7,68 @@
77
defmodule RabbitMQ.CLI.Ctl.Commands.EnableFeatureFlagCommand do
88
@behaviour RabbitMQ.CLI.CommandBehaviour
99

10-
def merge_defaults(args, opts), do: {args, opts}
10+
def switches(), do: [experimental: :boolean]
11+
def aliases(), do: [e: :experimental]
1112

12-
def validate([], _), do: {:validation_failure, :not_enough_args}
13-
def validate([_ | _] = args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}
13+
def merge_defaults(args, opts), do: { args, Map.merge(%{experimental: false}, opts) }
1414

15-
def validate([""], _),
15+
def validate([], _opts), do: {:validation_failure, :not_enough_args}
16+
def validate([_ | _] = args, _opts) when length(args) > 1, do: {:validation_failure, :too_many_args}
17+
18+
def validate([""], _opts),
1619
do: {:validation_failure, {:bad_argument, "feature_flag cannot be an empty string."}}
1720

18-
def validate([_], _), do: :ok
21+
def validate([_], _opts), do: :ok
1922

2023
use RabbitMQ.CLI.Core.RequiresRabbitAppRunning
2124

22-
def run(["all"], %{node: node_name}) do
23-
case :rabbit_misc.rpc_call(node_name, :rabbit_feature_flags, :enable_all, []) do
24-
# Server does not support feature flags, consider none are available.
25-
# See rabbitmq/rabbitmq-cli#344 for context. MK.
26-
{:badrpc, {:EXIT, {:undef, _}}} -> {:error, :unsupported}
27-
{:badrpc, _} = err -> err
28-
other -> other
25+
def run(["all"], %{node: node_name, experimental: experimental}) do
26+
case experimental do
27+
true ->
28+
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(), "`--experiemntal` flag is not allowed when enabling all feature flags.\nUse --experimental with a specific feature flag if you want to enable an experimental feature."}
29+
false ->
30+
case :rabbit_misc.rpc_call(node_name, :rabbit_feature_flags, :enable_all, []) do
31+
{:badrpc, _} = err -> err
32+
other -> other
33+
end
2934
end
3035
end
3136

32-
def run([feature_flag], %{node: node_name}) do
33-
case :rabbit_misc.rpc_call(node_name, :rabbit_feature_flags, :enable, [
34-
String.to_atom(feature_flag)
35-
]) do
36-
# Server does not support feature flags, consider none are available.
37-
# See rabbitmq/rabbitmq-cli#344 for context. MK.
38-
{:badrpc, {:EXIT, {:undef, _}}} -> {:error, :unsupported}
39-
{:badrpc, _} = err -> err
40-
other -> other
37+
def run([feature_flag], %{node: node_name, experimental: experimental}) do
38+
case {experimental, :rabbit_misc.rpc_call(node_name, :rabbit_feature_flags, :get_stability, [
39+
String.to_atom(feature_flag)
40+
])} do
41+
{_, {:badrpc, _} = err} -> err
42+
{false, :experimental} ->
43+
IO.puts("Feature flag #{feature_flag} is experimental. If you understand the risk, use --experimental to enable it.")
44+
_ ->
45+
case :rabbit_misc.rpc_call(node_name, :rabbit_feature_flags, :enable, [
46+
String.to_atom(feature_flag)
47+
]) do
48+
{:badrpc, _} = err -> err
49+
other -> other
50+
end
4151
end
4252
end
4353

4454
def output({:error, :unsupported}, %{node: node_name}) do
4555
{:error, RabbitMQ.CLI.Core.ExitCodes.exit_usage(),
46-
"This feature flag is not supported by node #{node_name}"}
56+
"This feature flag is not supported by node #{node_name}"}
4757
end
4858

4959
use RabbitMQ.CLI.DefaultOutput
5060

51-
def usage, do: "enable_feature_flag <all | feature_flag>"
61+
def usage, do: "enable_feature_flag [--experimental] <all | feature_flag>"
5262

5363
def usage_additional() do
5464
[
5565
[
5666
"<feature_flag>",
5767
"name of the feature flag to enable, or \"all\" to enable all supported flags"
68+
],
69+
[
70+
"--experimental",
71+
"required to enable experimental feature flags (make sure you understand the risks!)"
5872
]
5973
]
6074
end

deps/rabbitmq_cli/test/ctl/enable_feature_flag_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ defmodule EnableFeatureFlagCommandTest do
3535

3636
{
3737
:ok,
38-
opts: %{node: get_rabbit_hostname()}, feature_flag: @feature_flag
38+
opts: %{node: get_rabbit_hostname(), experimental: false}, feature_flag: @feature_flag
3939
}
4040
end
4141

@@ -59,7 +59,7 @@ defmodule EnableFeatureFlagCommandTest do
5959
end
6060

6161
test "run: attempt to use an unreachable node returns a nodedown" do
62-
opts = %{node: :jake@thedog, timeout: 200}
62+
opts = %{node: :jake@thedog, timeout: 200, experimental: false}
6363
assert match?({:badrpc, _}, @command.run(["na"], opts))
6464
end
6565

deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</p>
1414
<% } %>
1515
<div class="section">
16-
<h2>All Feature Flags</h2>
16+
<h2>Feature Flags</h2>
1717
<div class="hider">
1818
<%= filter_ui(feature_flags) %>
1919
<div class="updatable">
@@ -30,6 +30,9 @@
3030
<%
3131
for (var i = 0; i < feature_flags.length; i++) {
3232
var feature_flag = feature_flags[i];
33+
if (feature_flag.stability == "experimental") {
34+
continue;
35+
}
3336
var state_color = "grey";
3437
if (feature_flag.state == "enabled") {
3538
state_color = "green";
@@ -76,3 +79,74 @@
7679
</div>
7780
</div>
7881
</div>
82+
83+
84+
85+
<div class="section">
86+
<h2>Experimental Feature Flags</h2>
87+
<div class="hider">
88+
<% if (feature_flags.length > 0) { %>
89+
<p class="warning">
90+
Feature flags listed below are experimental. They should not be enabled in a production deployment.
91+
</p>
92+
<table class="list">
93+
<thead>
94+
<tr>
95+
<th><%= fmt_sort('Name', 'name') %></th>
96+
<th class="c"><%= fmt_sort('State', 'state') %></th>
97+
<th>Description</th>
98+
</tr>
99+
</thead>
100+
<tbody>
101+
<%
102+
for (var i = 0; i < feature_flags.length; i++) {
103+
var feature_flag = feature_flags[i];
104+
if (feature_flag.stability != "experimental") {
105+
continue;
106+
}
107+
var state_color = "grey";
108+
if (feature_flag.state == "enabled") {
109+
state_color = "green";
110+
} else if (feature_flag.state == "disabled") {
111+
state_color = "yellow";
112+
} else if (feature_flag.state == "unsupported") {
113+
state_color = "red";
114+
}
115+
%>
116+
<tr<%= alt_rows(i)%>>
117+
<td><%= fmt_string(feature_flag.name) %></td>
118+
<td class="c">
119+
<% if (feature_flag.state == "disabled") { %>
120+
<div>
121+
<input id="<%= feature_flag.name %>" type="checkbox" class="riskCheckbox" onclick="this.parentNode.querySelector('.enable-feature-flag input[type=submit]').disabled = !this.checked;">
122+
<label for="<%= feature_flag.name %>"> I understand the risk</label><br>
123+
<br>
124+
<form action="#/feature-flags-enable" method="put" style="display: inline-block" class="enable-feature-flag">
125+
<input type="hidden" name="name" value="<%= fmt_string(feature_flag.name) %>"/>
126+
<input type="submit" value="Enable" class="c" disabled="disabled"/>
127+
</div>
128+
</form>
129+
<% } else { %>
130+
<abbr class="status-<%= fmt_string(state_color) %>"
131+
style="text-transform: capitalize"
132+
title="Feature flag state: <%= fmt_string(feature_flag.state) %>">
133+
<%= fmt_string(feature_flag.state) %>
134+
</abbr>
135+
<% } %>
136+
</td>
137+
<td>
138+
<p><%= fmt_string(feature_flag.desc) %></p>
139+
<% if (feature_flag.doc_url) { %>
140+
<p><a href="<%= fmt_string(feature_flag.doc_url) %>">[Learn more]</a></p>
141+
<% } %>
142+
</td>
143+
</tr>
144+
<% } %>
145+
</tbody>
146+
</table>
147+
<% } else { %>
148+
<p>... no feature_flags ...</p>
149+
<% } %>
150+
</div>
151+
</div>
152+
</div>

0 commit comments

Comments
 (0)