fx
This commit is contained in:
parent
251acb9b17
commit
cb1e195a38
@ -4,8 +4,8 @@
|
||||
|
||||
当前实现对应代码:
|
||||
|
||||
- HTTP 入口校验:[src/transport/http/container_handler.erl](/usr/local/code/cloudkit/iot/src/transport/http/container_handler.erl:63)
|
||||
- 请求构造器:[src/host/iot_container_request_builder.erl](/usr/local/code/cloudkit/iot/src/host/iot_container_request_builder.erl:25)
|
||||
- HTTP 入口:[src/transport/http/container_handler.erl](/usr/local/code/cloudkit/iot/src/transport/http/container_handler.erl:63)
|
||||
- 请求构造器与 deploy 配置校验:[src/host/iot_container_request_builder.erl](/usr/local/code/cloudkit/iot/src/host/iot_container_request_builder.erl:25)
|
||||
- protobuf 定义:[proto/message.proto](/usr/local/code/cloudkit/iot/proto/message.proto:37)
|
||||
|
||||
## 1. HTTP 请求格式
|
||||
@ -395,7 +395,13 @@ RestartPolicy {
|
||||
|
||||
## 5. 参数校验规则
|
||||
|
||||
在 HTTP 层,`config` 会先经过基础类型校验。
|
||||
当前实现里:
|
||||
|
||||
- `container_handler` 只校验顶层请求结构:
|
||||
- `uuid` 必须是 `binary`
|
||||
- `task_id` 必须是 `integer`
|
||||
- `config` 必须是 `map`
|
||||
- `config` 内部字段的必填项、类型检查、格式解析和不支持字段判断,全部在 `iot_container_request_builder:deploy_request/2` 中完成
|
||||
|
||||
必填字段:
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ config_request(ContainerName, ConfigJson) when is_binary(ContainerName), is_bina
|
||||
deploy_request(TaskId, Config) when is_integer(TaskId), is_map(Config), TaskId >= 0 ->
|
||||
try
|
||||
ensure_supported_deploy_config(Config),
|
||||
validate_deploy_config(Config),
|
||||
Params = build_container_deploy_params(Config),
|
||||
{ok, #'ContainerRequest'{action = {deploy, #'ContainerRequest.Deploy'{task_id = TaskId, params = Params}}}}
|
||||
catch
|
||||
@ -75,6 +76,135 @@ ensure_supported_deploy_config(Config) when is_map(Config) ->
|
||||
throw({error, <<"unsupported container config keys: ", Unsupported/binary>>})
|
||||
end.
|
||||
|
||||
-spec validate_deploy_config(Config :: map()) -> ok.
|
||||
validate_deploy_config(Config) when is_map(Config) ->
|
||||
Required = [
|
||||
{<<"image">>, binary},
|
||||
{<<"container_name">>, binary},
|
||||
{<<"command">>, {list, binary}},
|
||||
{<<"restart">>, binary}
|
||||
],
|
||||
Optional = [
|
||||
{<<"privileged">>, boolean},
|
||||
{<<"entrypoint">>, {list, binary}},
|
||||
{<<"envs">>, {list, binary}},
|
||||
{<<"ports">>, {list, binary}},
|
||||
{<<"expose">>, {list, binary}},
|
||||
{<<"volumes">>, {list, binary}},
|
||||
{<<"networks">>, {list, binary}},
|
||||
{<<"labels">>, {map, {binary, binary}}},
|
||||
{<<"user">>, binary},
|
||||
{<<"working_dir">>, binary},
|
||||
{<<"hostname">>, binary},
|
||||
{<<"container_dir">>, binary},
|
||||
{<<"network_mode">>, binary},
|
||||
{<<"cap_add">>, {list, binary}},
|
||||
{<<"cap_drop">>, {list, binary}},
|
||||
{<<"devices">>, {list, binary}},
|
||||
{<<"mem_limit">>, binary},
|
||||
{<<"mem_reservation">>, binary},
|
||||
{<<"cpu_shares">>, integer},
|
||||
{<<"cpus">>, number},
|
||||
{<<"ulimits">>, {map, {binary, binary}}},
|
||||
{<<"sysctls">>, {map, {binary, binary}}},
|
||||
{<<"tmpfs">>, {list, binary}},
|
||||
{<<"extra_hosts">>, {list, binary}},
|
||||
{<<"healthcheck">>, {map, {binary, any}}}
|
||||
],
|
||||
Errors = check_required(Config, Required) ++ check_optional(Config, Optional),
|
||||
case Errors of
|
||||
[] ->
|
||||
ok;
|
||||
_ ->
|
||||
throw({error, iolist_to_binary(lists:join(<<"|||">>, Errors))})
|
||||
end.
|
||||
|
||||
-spec check_required(map(), list()) -> [binary()].
|
||||
check_required(Config, Fields) ->
|
||||
lists:foldl(
|
||||
fun({Key, Type}, ErrAcc) ->
|
||||
case maps:get(Key, Config, undefined) of
|
||||
undefined ->
|
||||
[iolist_to_binary(io_lib:format("miss requied parameter: ~p", [Key])) | ErrAcc];
|
||||
Value ->
|
||||
case check_type(Value, Type) of
|
||||
true ->
|
||||
ErrAcc;
|
||||
false ->
|
||||
[iolist_to_binary(io_lib:format("required parameter: ~p, type must be: ~ts", [Key, type_name(Type)])) | ErrAcc]
|
||||
end
|
||||
end
|
||||
end,
|
||||
[], Fields).
|
||||
|
||||
-spec check_optional(map(), list()) -> [binary()].
|
||||
check_optional(Config, Fields) ->
|
||||
lists:foldl(
|
||||
fun({Key, Type}, ErrAcc) ->
|
||||
case maps:get(Key, Config, undefined) of
|
||||
undefined ->
|
||||
ErrAcc;
|
||||
Value ->
|
||||
case check_type(Value, Type) of
|
||||
true ->
|
||||
ErrAcc;
|
||||
false ->
|
||||
[iolist_to_binary(io_lib:format("optional parameter: ~p, type must be: ~ts", [Key, type_name(Type)])) | ErrAcc]
|
||||
end
|
||||
end
|
||||
end,
|
||||
[], Fields).
|
||||
|
||||
-spec type_name(tuple() | atom()) -> binary().
|
||||
type_name(binary) ->
|
||||
<<"string">>;
|
||||
type_name(integer) ->
|
||||
<<"integer">>;
|
||||
type_name(number) ->
|
||||
<<"number">>;
|
||||
type_name(list) ->
|
||||
<<"list">>;
|
||||
type_name({list, binary}) ->
|
||||
<<"list of string">>;
|
||||
type_name({list, number}) ->
|
||||
<<"list of number">>;
|
||||
type_name({list, integer}) ->
|
||||
<<"list of integer">>;
|
||||
type_name(map) ->
|
||||
<<"map">>;
|
||||
type_name({map, {binary, binary}}) ->
|
||||
<<"map of string:string">>;
|
||||
type_name({map, {binary, any}}) ->
|
||||
<<"map of string:any">>;
|
||||
type_name(boolean) ->
|
||||
<<"boolean">>.
|
||||
|
||||
-spec check_type(Value :: any(), any()) -> boolean().
|
||||
check_type(Value, binary) ->
|
||||
is_binary(Value);
|
||||
check_type(Value, integer) ->
|
||||
is_integer(Value);
|
||||
check_type(Value, number) ->
|
||||
is_number(Value);
|
||||
check_type(Value, list) when is_list(Value) ->
|
||||
true;
|
||||
check_type(Value, {list, binary}) when is_list(Value) ->
|
||||
lists:all(fun(E) -> is_binary(E) end, Value);
|
||||
check_type(Value, {list, number}) when is_list(Value) ->
|
||||
lists:all(fun(E) -> is_number(E) end, Value);
|
||||
check_type(Value, {list, integer}) when is_list(Value) ->
|
||||
lists:all(fun(E) -> is_integer(E) end, Value);
|
||||
check_type(Value, map) when is_map(Value) ->
|
||||
true;
|
||||
check_type(Value, {map, {binary, binary}}) when is_map(Value) ->
|
||||
lists:all(fun({K, V}) -> is_binary(K) andalso is_binary(V) end, maps:to_list(Value));
|
||||
check_type(Value, {map, {binary, any}}) when is_map(Value) ->
|
||||
lists:all(fun({K, _}) -> is_binary(K) end, maps:to_list(Value));
|
||||
check_type(Value, boolean) ->
|
||||
is_boolean(Value);
|
||||
check_type(_, _) ->
|
||||
false.
|
||||
|
||||
-spec build_container_deploy_params(Config :: map()) -> message_pb:'ContainerDeployParams'().
|
||||
build_container_deploy_params(Config) when is_map(Config) ->
|
||||
ContainerName = maps:get(<<"container_name">>, Config),
|
||||
|
||||
@ -62,28 +62,21 @@ handle_request("POST", "/container/push_config", _,
|
||||
%% 部署微服务
|
||||
handle_request("POST", "/container/deploy", _, #{<<"uuid">> := UUID, <<"task_id">> := TaskId, <<"config">> := Config})
|
||||
when is_binary(UUID), is_integer(TaskId), is_map(Config) ->
|
||||
|
||||
case validate_config(Config) of
|
||||
ok ->
|
||||
case iot_host:get_pid(UUID) of
|
||||
undefined ->
|
||||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||||
Pid when is_pid(Pid) ->
|
||||
case iot_host:deploy_container(Pid, TaskId, Config) of
|
||||
{ok, Ref} ->
|
||||
case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
|
||||
{ok, Result} ->
|
||||
{ok, 200, rpc_success_response(Result)};
|
||||
{error, Code, Reason} ->
|
||||
rpc_error_http_response(Code, Reason)
|
||||
end;
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(400, Reason)}
|
||||
end
|
||||
end;
|
||||
{error, Errors} ->
|
||||
Reason = iolist_to_binary(lists:join(<<"|||">>, Errors)),
|
||||
{ok, 200, iot_util:json_error(400, Reason)}
|
||||
case iot_host:get_pid(UUID) of
|
||||
undefined ->
|
||||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||||
Pid when is_pid(Pid) ->
|
||||
case iot_host:deploy_container(Pid, TaskId, Config) of
|
||||
{ok, Ref} ->
|
||||
case iot_host:await_reply(Pid, Ref, ?REQ_TIMEOUT) of
|
||||
{ok, Result} ->
|
||||
{ok, 200, rpc_success_response(Result)};
|
||||
{error, Code, Reason} ->
|
||||
rpc_error_http_response(Code, Reason)
|
||||
end;
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(400, Reason)}
|
||||
end
|
||||
end;
|
||||
|
||||
%% 启动服务
|
||||
@ -165,152 +158,6 @@ handle_request(_, Path, _, _) ->
|
||||
Path1 = list_to_binary(Path),
|
||||
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
validate_config(Config) when is_map(Config) ->
|
||||
%% 必选参数
|
||||
Required = [
|
||||
{<<"image">>, binary},
|
||||
{<<"container_name">>, binary},
|
||||
{<<"command">>, {list, binary}},
|
||||
{<<"restart">>, binary}
|
||||
],
|
||||
|
||||
%% 可选参数(附带默认值)
|
||||
Optional = [
|
||||
{<<"privileged">>, boolean},
|
||||
{<<"entrypoint">>, {list, binary}},
|
||||
{<<"envs">>, {list, binary}},
|
||||
{<<"ports">>, {list, binary}},
|
||||
{<<"expose">>, {list, binary}},
|
||||
{<<"volumes">>, {list, binary}},
|
||||
{<<"networks">>, {list, binary}},
|
||||
{<<"labels">>, {map, {binary, binary}}},
|
||||
{<<"user">>, binary},
|
||||
{<<"working_dir">>, binary},
|
||||
{<<"hostname">>, binary},
|
||||
{<<"container_dir">>, binary},
|
||||
{<<"network_mode">>, binary},
|
||||
{<<"cap_add">>, {list, binary}},
|
||||
{<<"cap_drop">>, {list, binary}},
|
||||
{<<"devices">>, {list, binary}},
|
||||
{<<"mem_limit">>, binary},
|
||||
{<<"mem_reservation">>, binary},
|
||||
{<<"cpu_shares">>, integer},
|
||||
{<<"cpus">>, number},
|
||||
{<<"ulimits">>, {map, {binary, binary}}},
|
||||
{<<"sysctls">>, {map, {binary, binary}}},
|
||||
{<<"tmpfs">>, {list, binary}},
|
||||
{<<"extra_hosts">>, {list, binary}},
|
||||
{<<"healthcheck">>, {map, {binary, any}}}
|
||||
],
|
||||
|
||||
Errors1 = check_required(Config, Required),
|
||||
Errors2 = check_optional(Config, Optional),
|
||||
|
||||
Errors = Errors1 ++ Errors2,
|
||||
case Errors of
|
||||
[] ->
|
||||
ok;
|
||||
_ ->
|
||||
{error, lists:map(fun erlang:iolist_to_binary/1, Errors)}
|
||||
end.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% 校验必选项
|
||||
%%------------------------------------------------------------------------------
|
||||
check_required(Config, Fields) ->
|
||||
lists:foldl(
|
||||
fun({Key, Type}, ErrAcc) ->
|
||||
case maps:get(Key, Config, undefined) of
|
||||
undefined ->
|
||||
[io_lib:format("miss requied parameter: ~p", [Key]) | ErrAcc];
|
||||
Value ->
|
||||
case check_type(Value, Type) of
|
||||
true ->
|
||||
ErrAcc;
|
||||
false ->
|
||||
[io_lib:format("required parameter: ~p, type must be: ~p", [Key, type_name(Type)]) | ErrAcc]
|
||||
end
|
||||
end
|
||||
end,
|
||||
[], Fields).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% 校验可选项(支持默认值填充)
|
||||
%%------------------------------------------------------------------------------
|
||||
check_optional(Config, Fields) ->
|
||||
lists:foldl(
|
||||
fun({Key, Type}, ErrAcc) ->
|
||||
case maps:get(Key, Config, undefined) of
|
||||
undefined ->
|
||||
ErrAcc;
|
||||
Value ->
|
||||
case check_type(Value, Type) of
|
||||
true ->
|
||||
ErrAcc;
|
||||
false ->
|
||||
[io_lib:format("optional parameter: ~p, type must be: ~p", [Key, type_name(Type)]) | ErrAcc]
|
||||
end
|
||||
end
|
||||
end,
|
||||
[], Fields).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% 类型检查辅助函数(binary版)
|
||||
%%------------------------------------------------------------------------------
|
||||
-spec type_name(tuple() | atom()) -> binary().
|
||||
type_name(binary) ->
|
||||
<<"string">>;
|
||||
type_name(integer) ->
|
||||
<<"integer">>;
|
||||
type_name(number) ->
|
||||
<<"number">>;
|
||||
type_name(list) ->
|
||||
<<"list">>;
|
||||
type_name({list, binary}) ->
|
||||
<<"list of string">>;
|
||||
type_name({list, number}) ->
|
||||
<<"list of number">>;
|
||||
type_name({list, integer}) ->
|
||||
<<"list of integer">>;
|
||||
type_name(map) ->
|
||||
<<"map">>;
|
||||
type_name({map, {binary, binary}}) ->
|
||||
<<"map of string:string">>;
|
||||
type_name({map, {binary, any}}) ->
|
||||
<<"map of string:any">>;
|
||||
type_name(boolean) ->
|
||||
<<"boolean">>.
|
||||
|
||||
-spec check_type(Value :: any(), any()) -> boolean().
|
||||
check_type(Value, binary) ->
|
||||
is_binary(Value);
|
||||
check_type(Value, integer) ->
|
||||
is_integer(Value);
|
||||
check_type(Value, number) ->
|
||||
is_number(Value);
|
||||
check_type(Value, list) when is_list(Value) ->
|
||||
true;
|
||||
check_type(Value, {list, binary}) when is_list(Value) ->
|
||||
lists:all(fun(E) -> is_binary(E) end, Value);
|
||||
check_type(Value, {list, number}) when is_list(Value) ->
|
||||
lists:all(fun(E) -> is_number(E) end, Value);
|
||||
check_type(Value, {list, integer}) when is_list(Value) ->
|
||||
lists:all(fun(E) -> is_integer(E) end, Value);
|
||||
check_type(Value, map) when is_map(Value) ->
|
||||
true;
|
||||
check_type(Value, {map, {binary, binary}}) when is_map(Value) ->
|
||||
lists:all(fun({K, V}) -> is_binary(K) andalso is_binary(V) end, maps:to_list(Value));
|
||||
check_type(Value, {map, {binary, any}}) when is_map(Value) ->
|
||||
lists:all(fun({K, _}) -> is_binary(K) end, maps:to_list(Value));
|
||||
check_type(Value, boolean) ->
|
||||
is_boolean(Value);
|
||||
check_type(_, _) ->
|
||||
false.
|
||||
|
||||
rpc_success_response(Result) when is_binary(Result) ->
|
||||
case decode_json_bytes(Result) of
|
||||
{ok, Data} ->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user