inject-relup.escript 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/env escript
  2. %% This script injects implicit relup instructions for emqx applications.
  3. -mode(compile).
  4. -define(ERROR(FORMAT, ARGS), io:format(standard_error, "[inject-relup] " ++ FORMAT ++ "~n", ARGS)).
  5. -define(INFO(FORMAT, ARGS), io:format(user, "[inject-relup] " ++ FORMAT ++ "~n", ARGS)).
  6. usage() ->
  7. "Usage: " ++ escript:script_name() ++ " <path-to-relup-file>".
  8. main([RelupFile]) ->
  9. ok = inject_relup_file(RelupFile);
  10. main(_Args) ->
  11. ?ERROR("~s", [usage()]),
  12. erlang:halt(1).
  13. inject_relup_file(File) ->
  14. case file:script(File) of
  15. {ok, {CurrRelVsn, UpVsnRUs, DnVsnRUs}} ->
  16. ?INFO("Injecting instructions to: ~p", [File]),
  17. UpdatedContent =
  18. {CurrRelVsn, inject_relup_instrs(up, UpVsnRUs),
  19. inject_relup_instrs(down, DnVsnRUs)},
  20. file:write_file(File, term_to_text(UpdatedContent));
  21. {ok, _BadFormat} ->
  22. ?ERROR("Bad formatted relup file: ~p", [File]),
  23. error({bad_relup_format, File});
  24. {error, enoent} ->
  25. ?INFO("Cannot find relup file: ~p", [File]),
  26. ok;
  27. {error, Reason} ->
  28. ?ERROR("Read relup file ~p failed: ~p", [File, Reason]),
  29. error({read_relup_error, Reason})
  30. end.
  31. inject_relup_instrs(Type, RUs) ->
  32. lists:map(
  33. fun({Vsn, Desc, Instrs}) ->
  34. {Vsn, Desc, append_emqx_relup_instrs(Type, Vsn, Instrs)}
  35. end,
  36. RUs
  37. ).
  38. append_emqx_relup_instrs(up, FromRelVsn, Instrs0) ->
  39. {{UpExtra, _}, Instrs1} = filter_and_check_instrs(up, Instrs0),
  40. Instrs1 ++
  41. [
  42. {load, {emqx_release, brutal_purge, soft_purge}},
  43. {load, {emqx_relup, brutal_purge, soft_purge}},
  44. {apply, {emqx_relup, post_release_upgrade, [FromRelVsn, UpExtra]}}
  45. ];
  46. append_emqx_relup_instrs(down, ToRelVsn, Instrs0) ->
  47. {{_, DnExtra}, Instrs1} = filter_and_check_instrs(down, Instrs0),
  48. %% NOTE: When downgrading, we apply emqx_relup:post_release_downgrade/2 before reloading
  49. %% or removing the emqx_relup module.
  50. Instrs2 =
  51. Instrs1 ++
  52. [
  53. {load, {emqx_release, brutal_purge, soft_purge}},
  54. {apply, {emqx_relup, post_release_downgrade, [ToRelVsn, DnExtra]}},
  55. {load, {emqx_relup, brutal_purge, soft_purge}}
  56. ],
  57. Instrs2.
  58. filter_and_check_instrs(Type, Instrs) ->
  59. case filter_fetch_emqx_mods_and_extra(Instrs) of
  60. {_, DnExtra, _, _} when Type =:= up, DnExtra =/= undefined ->
  61. ?ERROR(
  62. "Got '{apply,{emqx_relup,post_release_downgrade,[_,Extra]}}'"
  63. " from the upgrade instruction list, should be 'post_release_upgrade'",
  64. []
  65. ),
  66. error({instruction_not_found, load_object_code});
  67. {UpExtra, _, _, _} when Type =:= down, UpExtra =/= undefined ->
  68. ?ERROR(
  69. "Got '{apply,{emqx_relup,post_release_upgrade,[_,Extra]}}'"
  70. " from the downgrade instruction list, should be 'post_release_downgrade'",
  71. []
  72. ),
  73. error({instruction_not_found, load_object_code});
  74. {_, _, [], _} ->
  75. ?ERROR("Cannot find any 'load_object_code' instructions for app emqx", []),
  76. error({instruction_not_found, load_object_code});
  77. {UpExtra, DnExtra, EmqxMods, RemainInstrs} ->
  78. assert_mandatory_modules(Type, EmqxMods),
  79. {{UpExtra, DnExtra}, RemainInstrs}
  80. end.
  81. filter_fetch_emqx_mods_and_extra(Instrs) ->
  82. lists:foldl(fun do_filter_and_get/2, {undefined, undefined, [], []}, Instrs).
  83. %% collect modules for emqx app
  84. do_filter_and_get(
  85. {load_object_code, {emqx, _AppVsn, Mods}} = Instr,
  86. {UpExtra, DnExtra, EmqxMods, RemainInstrs}
  87. ) ->
  88. {UpExtra, DnExtra, EmqxMods ++ Mods, RemainInstrs ++ [Instr]};
  89. %% remove 'load' instrs for emqx_relup and emqx_release
  90. do_filter_and_get({load, {Mod, _, _}}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) when
  91. Mod =:= emqx_relup; Mod =:= emqx_release
  92. ->
  93. {UpExtra, DnExtra, EmqxMods, RemainInstrs};
  94. %% remove 'remove' and 'purge' instrs for emqx_relup
  95. do_filter_and_get({remove, {emqx_relup, _, _}}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
  96. {UpExtra, DnExtra, EmqxMods, RemainInstrs};
  97. do_filter_and_get({purge, [emqx_relup]}, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
  98. {UpExtra, DnExtra, EmqxMods, RemainInstrs};
  99. %% remove 'apply' instrs for upgrade, and collect the 'Extra' parameter
  100. do_filter_and_get(
  101. {apply, {emqx_relup, post_release_upgrade, [_, UpExtra0]}},
  102. {_, DnExtra, EmqxMods, RemainInstrs}
  103. ) ->
  104. {UpExtra0, DnExtra, EmqxMods, RemainInstrs};
  105. %% remove 'apply' instrs for downgrade, and collect the 'Extra' parameter
  106. do_filter_and_get(
  107. {apply, {emqx_relup, post_release_downgrade, [_, DnExtra0]}},
  108. {UpExtra, _, EmqxMods, RemainInstrs}
  109. ) ->
  110. {UpExtra, DnExtra0, EmqxMods, RemainInstrs};
  111. %% keep all other instrs unchanged
  112. do_filter_and_get(Instr, {UpExtra, DnExtra, EmqxMods, RemainInstrs}) ->
  113. {UpExtra, DnExtra, EmqxMods, RemainInstrs ++ [Instr]}.
  114. assert_mandatory_modules(_, Mods) ->
  115. MandInstrs = [
  116. {load_module, emqx_release, brutal_purge, soft_purge, []},
  117. {load_module, emqx_relup}
  118. ],
  119. assert(
  120. lists:member(emqx_relup, Mods) andalso lists:member(emqx_release, Mods),
  121. "The following instructions are mandatory in every clause of the emqx.appup.src: ~p",
  122. [MandInstrs]
  123. ).
  124. assert(true, _, _) ->
  125. ok;
  126. assert(false, Msg, Args) ->
  127. ?ERROR(Msg, Args),
  128. error(assert_failed).
  129. term_to_text(Term) ->
  130. io_lib:format("~p.", [Term]).