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)
|
- 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)
|
- 请求构造器与 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)
|
- protobuf 定义:[proto/message.proto](/usr/local/code/cloudkit/iot/proto/message.proto:37)
|
||||||
|
|
||||||
## 1. HTTP 请求格式
|
## 1. HTTP 请求格式
|
||||||
@ -395,7 +395,13 @@ RestartPolicy {
|
|||||||
|
|
||||||
## 5. 参数校验规则
|
## 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 ->
|
deploy_request(TaskId, Config) when is_integer(TaskId), is_map(Config), TaskId >= 0 ->
|
||||||
try
|
try
|
||||||
ensure_supported_deploy_config(Config),
|
ensure_supported_deploy_config(Config),
|
||||||
|
validate_deploy_config(Config),
|
||||||
Params = build_container_deploy_params(Config),
|
Params = build_container_deploy_params(Config),
|
||||||
{ok, #'ContainerRequest'{action = {deploy, #'ContainerRequest.Deploy'{task_id = TaskId, params = Params}}}}
|
{ok, #'ContainerRequest'{action = {deploy, #'ContainerRequest.Deploy'{task_id = TaskId, params = Params}}}}
|
||||||
catch
|
catch
|
||||||
@ -75,6 +76,135 @@ ensure_supported_deploy_config(Config) when is_map(Config) ->
|
|||||||
throw({error, <<"unsupported container config keys: ", Unsupported/binary>>})
|
throw({error, <<"unsupported container config keys: ", Unsupported/binary>>})
|
||||||
end.
|
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'().
|
-spec build_container_deploy_params(Config :: map()) -> message_pb:'ContainerDeployParams'().
|
||||||
build_container_deploy_params(Config) when is_map(Config) ->
|
build_container_deploy_params(Config) when is_map(Config) ->
|
||||||
ContainerName = maps:get(<<"container_name">>, Config),
|
ContainerName = maps:get(<<"container_name">>, Config),
|
||||||
|
|||||||
@ -62,9 +62,6 @@ handle_request("POST", "/container/push_config", _,
|
|||||||
%% 部署微服务
|
%% 部署微服务
|
||||||
handle_request("POST", "/container/deploy", _, #{<<"uuid">> := UUID, <<"task_id">> := TaskId, <<"config">> := Config})
|
handle_request("POST", "/container/deploy", _, #{<<"uuid">> := UUID, <<"task_id">> := TaskId, <<"config">> := Config})
|
||||||
when is_binary(UUID), is_integer(TaskId), is_map(Config) ->
|
when is_binary(UUID), is_integer(TaskId), is_map(Config) ->
|
||||||
|
|
||||||
case validate_config(Config) of
|
|
||||||
ok ->
|
|
||||||
case iot_host:get_pid(UUID) of
|
case iot_host:get_pid(UUID) of
|
||||||
undefined ->
|
undefined ->
|
||||||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||||||
@ -81,10 +78,6 @@ handle_request("POST", "/container/deploy", _, #{<<"uuid">> := UUID, <<"task_id"
|
|||||||
{ok, 200, iot_util:json_error(400, Reason)}
|
{ok, 200, iot_util:json_error(400, Reason)}
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
{error, Errors} ->
|
|
||||||
Reason = iolist_to_binary(lists:join(<<"|||">>, Errors)),
|
|
||||||
{ok, 200, iot_util:json_error(400, Reason)}
|
|
||||||
end;
|
|
||||||
|
|
||||||
%% 启动服务
|
%% 启动服务
|
||||||
handle_request("POST", "/container/start", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
|
handle_request("POST", "/container/start", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
|
||||||
@ -165,152 +158,6 @@ handle_request(_, Path, _, _) ->
|
|||||||
Path1 = list_to_binary(Path),
|
Path1 = list_to_binary(Path),
|
||||||
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.
|
{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) ->
|
rpc_success_response(Result) when is_binary(Result) ->
|
||||||
case decode_json_bytes(Result) of
|
case decode_json_bytes(Result) of
|
||||||
{ok, Data} ->
|
{ok, Data} ->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user