-module(rebar_hex_repos).
|
|
|
|
-export([from_state/2,
|
|
get_repo_config/2,
|
|
auth_config/1,
|
|
remove_from_auth_config/2,
|
|
update_auth_config/2,
|
|
format_error/1]).
|
|
|
|
-ifdef(TEST).
|
|
%% exported for test purposes
|
|
-export([repos/1, merge_repos/1]).
|
|
-endif.
|
|
|
|
-include("rebar.hrl").
|
|
-include_lib("providers/include/providers.hrl").
|
|
|
|
-export_type([repo/0]).
|
|
|
|
-type repo() :: #{name => unicode:unicode_binary(),
|
|
api_url => binary(),
|
|
api_key => binary(),
|
|
repo_url => binary(),
|
|
repo_key => binary(),
|
|
repo_public_key => binary(),
|
|
repo_verify => binary(),
|
|
repo_verify_origin => binary()}.
|
|
|
|
from_state(BaseConfig, State) ->
|
|
HexConfig = rebar_state:get(State, hex, []),
|
|
Repos = repos(HexConfig),
|
|
%% auth is stored in a separate config file since the plugin generates and modifies it
|
|
Auth = ?MODULE:auth_config(State),
|
|
%% add base config entries that are specific to use by rebar3 and not overridable
|
|
Repos1 = merge_with_base_and_auth(Repos, BaseConfig, Auth),
|
|
%% merge organizations parent repo options into each oraganization repo
|
|
update_organizations(Repos1).
|
|
|
|
-spec get_repo_config(unicode:unicode_binary(), rebar_state:t() | [repo()])
|
|
-> {ok, repo()} | error.
|
|
get_repo_config(RepoName, Repos) when is_list(Repos) ->
|
|
case ec_lists:find(fun(#{name := N}) -> N =:= RepoName end, Repos) of
|
|
error ->
|
|
throw(?PRV_ERROR({repo_not_found, RepoName}));
|
|
{ok, RepoConfig} ->
|
|
{ok, RepoConfig}
|
|
end;
|
|
get_repo_config(RepoName, State) ->
|
|
Resources = rebar_state:resources(State),
|
|
#{repos := Repos} = rebar_resource_v2:find_resource_state(pkg, Resources),
|
|
get_repo_config(RepoName, Repos).
|
|
|
|
merge_with_base_and_auth(Repos, BaseConfig, Auth) ->
|
|
[maps:merge(maps:merge(Repo, BaseConfig),
|
|
maps:get(maps:get(name, Repo), Auth, #{})) || Repo <- Repos].
|
|
|
|
%% A user's list of repos are merged by name while keeping the order
|
|
%% intact. The order is based on the first use of a repo by name in the
|
|
%% list. The default repo is appended to the user's list.
|
|
repos(HexConfig) ->
|
|
HexDefaultConfig = default_repo(),
|
|
case [R || R <- HexConfig, element(1, R) =:= repos] of
|
|
[] ->
|
|
[HexDefaultConfig];
|
|
%% we only care if the first element is a replace entry
|
|
[{repos, replace, Repos} | _]->
|
|
merge_repos(Repos);
|
|
Repos ->
|
|
RepoList = repo_list(Repos),
|
|
merge_repos(RepoList ++ [HexDefaultConfig])
|
|
end.
|
|
|
|
%% merge repos must add a field repo_name to work with r3_hex_core 0.5.0
|
|
-spec merge_repos([repo()]) -> [repo()].
|
|
merge_repos(Repos) ->
|
|
lists:foldl(fun(R = #{name := Name}, ReposAcc) ->
|
|
%% private orgs are in the format of <<"parent:org">>
|
|
case rebar_string:split(Name, <<":">>) of
|
|
[Repo, Org] ->
|
|
|
|
%% We set the repo_organization and api_organization to org
|
|
%% for fetching and publishing private packages.
|
|
update_repo_list(R#{name => Name,
|
|
repo_name => Org,
|
|
repo_organization => Org,
|
|
api_organization => Org,
|
|
api_repository => Org,
|
|
parent => Repo}, ReposAcc);
|
|
_ ->
|
|
update_repo_list(R#{repo_name => Name}, ReposAcc)
|
|
end
|
|
end, [], Repos).
|
|
|
|
update_organizations(Repos) ->
|
|
lists:map(fun(Repo=#{repo_name := RepoName,
|
|
parent := ParentName}) ->
|
|
{ok, Parent} = get_repo_config(ParentName, Repos),
|
|
ParentRepoUrl = rebar_utils:to_list(maps:get(repo_url, Parent)),
|
|
{ok, _RepoUrl} =
|
|
rebar_uri:append_path(ParentRepoUrl,
|
|
filename:join("repos", rebar_utils:to_list(RepoName))),
|
|
%% still let the organization config override this constructed repo url
|
|
maps:merge(Parent#{repo_url => rebar_utils:to_binary(ParentRepoUrl)}, Repo);
|
|
(Repo) ->
|
|
Repo
|
|
end, Repos).
|
|
|
|
update_repo_list(R=#{name := N}, [H=#{name := HN} | Rest]) when N =:= HN ->
|
|
[maps:merge(R, H) | Rest];
|
|
update_repo_list(R, [H | Rest]) ->
|
|
[H | update_repo_list(R, Rest)];
|
|
update_repo_list(R, []) ->
|
|
[R].
|
|
|
|
default_repo() ->
|
|
HexDefaultConfig = r3_hex_core:default_config(),
|
|
HexDefaultConfig#{name => ?PUBLIC_HEX_REPO, repo_verify_origin => repo_verify_origin()}.
|
|
|
|
repo_verify_origin() ->
|
|
case os:getenv("REBAR_NO_VERIFY_REPO_ORIGIN") of
|
|
"1" -> false;
|
|
_ -> true
|
|
end.
|
|
|
|
repo_list([]) ->
|
|
[];
|
|
repo_list([{repos, Repos} | T]) ->
|
|
Repos ++ repo_list(T);
|
|
repo_list([{repos, replace, Repos} | T]) ->
|
|
Repos ++ repo_list(T).
|
|
|
|
format_error({repo_not_found, RepoName}) ->
|
|
io_lib:format("The repo ~ts was not found in the configuration.", [RepoName]).
|
|
|
|
%% auth functions
|
|
|
|
%% authentication is in a separate config file because the hex plugin updates it
|
|
|
|
-spec auth_config_file(rebar_state:t()) -> file:filename_all().
|
|
auth_config_file(State) ->
|
|
filename:join(rebar_dir:global_config_dir(State), ?HEX_AUTH_FILE).
|
|
|
|
-spec auth_config(rebar_state:t()) -> map().
|
|
auth_config(State) ->
|
|
AuthFile = auth_config_file(State),
|
|
case file:consult(AuthFile) of
|
|
{ok, [Config]} ->
|
|
Config;
|
|
{error, Reason} when is_atom(Reason) ->
|
|
case Reason of
|
|
enoent ->
|
|
#{};
|
|
_ ->
|
|
% TODO: map to an english reason
|
|
?ABORT("Error reading repos auth config (~ts) : ~ts", [AuthFile, atom_to_list(Reason)])
|
|
end;
|
|
{error, {_Line, _Mod, _Term} = Err} ->
|
|
Reason = file:format_error(Err),
|
|
?ABORT("Error found in repos auth config (~ts) at line ~ts", [AuthFile, Reason])
|
|
end.
|
|
|
|
-spec remove_from_auth_config(term(), rebar_state:t()) -> ok.
|
|
remove_from_auth_config(Key, State) ->
|
|
Updated = maps:remove(Key, auth_config(State)),
|
|
write_auth_config(Updated, State).
|
|
|
|
-spec update_auth_config(map(), rebar_state:t()) -> ok.
|
|
update_auth_config(Updates, State) ->
|
|
Updated = maps:merge(auth_config(State), Updates),
|
|
write_auth_config(Updated, State).
|
|
|
|
write_auth_config(Config, State) ->
|
|
AuthConfigFile = auth_config_file(State),
|
|
ok = filelib:ensure_dir(AuthConfigFile),
|
|
NewConfig = iolist_to_binary(["%% coding: utf-8", io_lib:nl(),
|
|
io_lib:print(Config), ".", io_lib:nl()]),
|
|
ok = file:write_file(AuthConfigFile, NewConfig, [{encoding, utf8}]).
|