%%%------------------------------------------------------------------- %%% @author anlicheng %%% @copyright (C) 2026, %%% @doc %%% %%% @end %%% Created : 20. 4月 2026 %%%------------------------------------------------------------------- -module(docker_deploy_manager). -author("anlicheng"). -include("message_pb.hrl"). -behaviour(gen_server). %% API -export([start_link/0, deploy/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -record(state, { root_dir :: string(), task_map = #{} }). %%%=================================================================== %%% API %%%=================================================================== -spec start_link() -> {ok, pid()} | ignore | {error, term()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -spec deploy(integer(), message_pb:'ContainerDeployParams'()) -> ok | {error, binary()}. deploy(TaskId, Params) when is_integer(TaskId), is_record(Params, 'ContainerDeployParams') -> gen_server:call(?SERVER, {deploy, TaskId, Params}). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== -spec init(list()) -> {ok, #state{}}. init([]) -> erlang:process_flag(trap_exit, true), {ok, RootDir} = application:get_env(efka, root_dir), {ok, #state{root_dir = RootDir}}. -spec handle_call(term(), {pid(), term()}, #state{}) -> {reply, term(), #state{}}. handle_call({deploy, TaskId, Params = #'ContainerDeployParams'{ container_name = ContainerName, container_dir = ContainerDir0 }}, _From, State = #state{root_dir = RootDir, task_map = TaskMap}) -> {ok, ContainerDir} = docker_helper:ensure_container_dir(RootDir, ContainerName, ContainerDir0), {ok, {TaskPid, _Ref}} = docker_deployer:start_monitor(TaskId, ContainerDir, Params), logger:debug("[docker_deploy_manager] start deploy task_id: ~p, params: ~p", [TaskId, Params]), {reply, ok, State#state{task_map = maps:put(TaskPid, TaskId, TaskMap)}}; handle_call(_Request, _From, State) -> {reply, ok, State}. -spec handle_cast(term(), #state{}) -> {noreply, #state{}}. handle_cast(_Request, State) -> {noreply, State}. -spec handle_info(term(), #state{}) -> {noreply, #state{}}. handle_info({'DOWN', _Ref, process, TaskPid, Reason}, State = #state{task_map = TaskMap}) -> case maps:take(TaskPid, TaskMap) of error -> {noreply, State}; {TaskId, NTaskMap} -> case Reason of normal -> logger:debug("[docker_deploy_manager] task_id: ~p, exit normal", [TaskId]); Error0 -> Error = iolist_to_binary(io_lib:format("~p", [Error0])), docker_task_reporter:stream(TaskId, <<"error">>, <<"任务失败: "/utf8, Error/binary>>), docker_task_reporter:close(TaskId, <<"task exited">>), logger:notice("[docker_deploy_manager] task_id: ~p, exit with error: ~p", [TaskId, Error]) end, {noreply, State#state{task_map = NTaskMap}} end; handle_info(_Info, State) -> {noreply, State}. -spec terminate(term(), #state{}) -> ok. terminate(_Reason, _State) -> ok. -spec code_change(term(), #state{}, term()) -> {ok, #state{}}. code_change(_OldVsn, State, _Extra) -> {ok, State}.