@@ -675,48 +675,84 @@ enable_task(FeatureNames) ->
675675 end .
676676
677677enable_default_task () ->
678- FeatureNames = get_forced_feature_flag_names (),
679- case FeatureNames of
678+ case get_forced_feature_flag_names () of
680679 undefined ->
681680 ? LOG_DEBUG (
682681 " Feature flags: starting an unclustered node for the first "
683682 " time: all stable feature flags will be enabled by default" ,
684683 #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
685684 {ok , Inventory } = collect_inventory_on_nodes ([node ()]),
686- #{feature_flags := FeatureFlags } = Inventory ,
687- StableFeatureNames =
688- maps :fold (
689- fun (FeatureName , FeatureProps , Acc ) ->
690- Stability = rabbit_feature_flags :get_stability (
691- FeatureProps ),
692- case Stability of
693- stable -> [FeatureName | Acc ];
694- _ -> Acc
695- end
696- end , [], FeatureFlags ),
685+ StableFeatureNames = get_stable_feature_flags (Inventory ),
697686 enable_many (Inventory , StableFeatureNames );
698- [] ->
687+ { abs , []} ->
699688 ? LOG_DEBUG (
700689 " Feature flags: starting an unclustered node for the first "
701690 " time: all feature flags are forcibly left disabled from "
702691 " the $RABBITMQ_FEATURE_FLAGS environment variable" ,
703692 #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
704693 ok ;
705- _ ->
694+ { abs , FeatureNames } ->
706695 ? LOG_DEBUG (
707696 " Feature flags: starting an unclustered node for the first "
708697 " time: only the following feature flags specified in the "
709698 " $RABBITMQ_FEATURE_FLAGS environment variable will be enabled: "
710- " ~tp " ,
699+ " ~0tp " ,
711700 [FeatureNames ],
712701 #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
713702 {ok , Inventory } = collect_inventory_on_nodes ([node ()]),
714- enable_many (Inventory , FeatureNames )
703+ enable_many (Inventory , FeatureNames );
704+ {rel , Plus , Minus } ->
705+ ? LOG_DEBUG (
706+ " Feature flags: starting an unclustered node for the first "
707+ " time: all stable feature flags will be enabled, after "
708+ " applying changes from $RABBITMQ_FEATURE_FLAGS: adding ~0tp , "
709+ " skipping ~0tp " ,
710+ [Plus , Minus ],
711+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
712+ {ok , Inventory } = collect_inventory_on_nodes ([node ()]),
713+ StableFeatureNames = get_stable_feature_flags (Inventory ),
714+ Unsupported = lists :filter (
715+ fun (FeatureName ) ->
716+ not is_known_and_supported (
717+ Inventory , FeatureName )
718+ end , Minus ),
719+ case Unsupported of
720+ [] ->
721+ FeatureNames = (StableFeatureNames -- Minus ) ++ Plus ,
722+ enable_many (Inventory , FeatureNames );
723+ _ ->
724+ ? LOG_DEBUG (
725+ " Feature flags: unsupported feature flags to skip in "
726+ " $RABBITMQ_FEATURE_FLAGS: ~0tp " ,
727+ [Unsupported ],
728+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
729+ {error , unsupported }
730+ end ;
731+ {error , syntax_error_in_envvar } = Error ->
732+ ? LOG_DEBUG (
733+ " Feature flags: invalid mix of `feature_flag` and "
734+ " `+/-feature_flag` in $RABBITMQ_FEATURE_FLAGS" ,
735+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
736+ Error
715737 end .
716738
739+ get_stable_feature_flags (#{feature_flags := FeatureFlags }) ->
740+ maps :fold (
741+ fun (FeatureName , FeatureProps , Acc ) ->
742+ Stability = rabbit_feature_flags :get_stability (FeatureProps ),
743+ case Stability of
744+ stable -> [FeatureName | Acc ];
745+ _ -> Acc
746+ end
747+ end , [], FeatureFlags ).
748+
717749-spec get_forced_feature_flag_names () -> Ret when
718- Ret :: FeatureNames | undefined ,
719- FeatureNames :: [rabbit_feature_flags :feature_name ()].
750+ Ret :: Abs | Rel | undefined ,
751+ Abs :: {abs , AbsNames },
752+ AbsNames :: [rabbit_feature_flags :feature_name ()],
753+ Rel :: {rel , PlusNames , MinusNames },
754+ PlusNames :: [rabbit_feature_flags :feature_name ()],
755+ MinusNames :: [rabbit_feature_flags :feature_name ()].
720756% % @doc Returns the (possibly empty) list of feature flags the user wants to
721757% % enable out-of-the-box when starting a node for the first time.
722758% %
@@ -737,59 +773,78 @@ enable_default_task() ->
737773% % @private
738774
739775get_forced_feature_flag_names () ->
740- Ret = case get_forced_feature_flag_names_from_env () of
741- undefined -> get_forced_feature_flag_names_from_config ();
742- List -> List
743- end ,
744- case Ret of
776+ Value = case get_forced_feature_flag_names_from_env () of
777+ undefined -> get_forced_feature_flag_names_from_config ();
778+ List -> List
779+ end ,
780+ case Value of
745781 undefined ->
746- ok ;
782+ Value ;
747783 [] ->
748- ? LOG_INFO (
749- " Feature flags: automatic enablement of feature flags "
750- " disabled (i.e. none will be enabled automatically)" ,
751- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS });
752- _ ->
753- ? LOG_INFO (
754- " Feature flags: automatic enablement of feature flags "
755- " limited to the following list: ~tp " ,
756- [Ret ],
757- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS })
758- end ,
759- Ret .
784+ {abs , Value };
785+ [[Op | _ ] | _ ] when Op =:= $+ orelse Op =:= $- ->
786+ lists :foldr (
787+ fun
788+ (_ , {error , _ } = Error ) ->
789+ Error ;
790+ ([$+ | NameS ], {rel , Plus , Minus }) ->
791+ Name = list_to_atom (NameS ),
792+ Plus1 = [Name | Plus ],
793+ {rel , Plus1 , Minus };
794+ ([$- | NameS ], {rel , Plus , Minus }) ->
795+ Name = list_to_atom (NameS ),
796+ Minus1 = [Name | Minus ],
797+ {rel , Plus , Minus1 };
798+ (_ , _ ) ->
799+ {error , syntax_error_in_envvar }
800+ end , {rel , [], []}, Value );
801+ _ when is_list (Value ) ->
802+ lists :foldr (
803+ fun
804+ (_ , {error , _ } = Error ) ->
805+ Error ;
806+ (Name , {abs , Abs })
807+ when is_atom (Name ) ->
808+ Abs1 = [Name | Abs ],
809+ {abs , Abs1 };
810+ ([C | _ ] = NameS , {abs , Abs })
811+ when C =/= $+ andalso C =/= $- ->
812+ Name = list_to_atom (NameS ),
813+ Abs1 = [Name | Abs ],
814+ {abs , Abs1 };
815+ (_ , _ ) ->
816+ {error , syntax_error_in_envvar }
817+ end , {abs , []}, Value )
818+ end .
760819
761820- spec get_forced_feature_flag_names_from_env () -> Ret when
762821 Ret :: FeatureNames | undefined ,
763- FeatureNames :: [rabbit_feature_flags :feature_name ()].
822+ FeatureNames :: [rabbit_feature_flags :feature_name () | string () ].
764823% % @private
765824
766825get_forced_feature_flag_names_from_env () ->
767826 case rabbit_prelaunch :get_context () of
768827 #{forced_feature_flags_on_init := ForcedFFs }
769828 when is_list (ForcedFFs ) ->
770829 ForcedFFs ;
830+ #{forced_feature_flags_on_init := {'+' , List } = ForcedFFs }
831+ when is_list (List ) ->
832+ ForcedFFs ;
771833 _ ->
772834 undefined
773835 end .
774836
775837- spec get_forced_feature_flag_names_from_config () -> Ret when
776838 Ret :: FeatureNames | undefined ,
777- FeatureNames :: [rabbit_feature_flags :feature_name ()].
839+ FeatureNames :: [rabbit_feature_flags :feature_name () | string () ].
778840% % @private
779841
780842get_forced_feature_flag_names_from_config () ->
781843 Value = application :get_env (
782844 rabbit , forced_feature_flags_on_init , undefined ),
783845 case Value of
784- undefined ->
785- Value ;
786- _ when is_list (Value ) ->
787- case lists :all (fun is_atom /1 , Value ) of
788- true -> Value ;
789- false -> undefined
790- end ;
791- _ ->
792- undefined
846+ undefined -> Value ;
847+ _ when is_list (Value ) -> Value
793848 end .
794849
795850- spec sync_cluster_task () -> Ret when
0 commit comments