%% @doc %% Repo API. -module(hex_repo). -export([ get_names/1, get_versions/1, get_package/2, get_tarball/3, get_tarball_to_file/4, get_docs/3, get_docs_to_file/4, get_public_key/1, get_hex_installs/1 ]). %%==================================================================== %% API functions %%==================================================================== %% @doc %% Gets names resource from the repository. %% %% Examples: %% %% ``` %% > hex_repo:get_names(hex_core:default_config()). %% {ok,{200, ..., %% #{packages => [ %% #{name => <<"package1">>}, %% #{name => <<"package2">>}, %% ...]}}} %% ''' %% @end get_names(Config) when is_map(Config) -> Decoder = fun(Data) -> hex_registry:decode_names(Data, verify_repo(Config)) end, get_protobuf(Config, <<"names">>, Decoder). %% @doc %% Gets versions resource from the repository. %% %% Examples: %% %% ``` %% > hex_repo:get_versions(Config). %% {ok, {200, ..., %% #{packages => [ %% #{name => <<"package1">>, retired => [], %% versions => [<<"1.0.0">>]}, %% #{name => <<"package2">>, retired => [<<"0.5.0>>"], %% versions => [<<"0.5.0">>, <<"1.0.0">>]}, %% ...]}}} %% ''' %% @end get_versions(Config) when is_map(Config) -> Decoder = fun(Data) -> hex_registry:decode_versions(Data, verify_repo(Config)) end, get_protobuf(Config, <<"versions">>, Decoder). %% @doc %% Gets package resource from the repository. %% %% Examples: %% %% ``` %% > hex_repo:get_package(hex_core:default_config(), <<"package1">>). %% {ok, {200, ..., %% #{name => <<"package1">>, %% releases => [ %% #{checksum => ..., version => <<"0.5.0">>, dependencies => []}, %% #{checksum => ..., version => <<"1.0.0">>, dependencies => [ %% #{package => <<"package2">>, optional => true, requirement => <<"~> 0.1">>} %% ]}, %% ]}}} %% ''' %% @end get_package(Config, Name) when is_binary(Name) and is_map(Config) -> Verify = maps:get(repo_verify_origin, Config, true), Decoder = fun(Data) -> case Verify of true -> hex_registry:decode_package(Data, repo_name(Config), Name); false -> hex_registry:decode_package(Data, no_verify, no_verify) end end, get_protobuf(Config, <<"packages/", Name/binary>>, Decoder). %% @doc %% Gets tarball from the repository. %% %% Examples: %% %% ``` %% > {ok, {200, _, Tarball}} = hex_repo:get_tarball(hex_core:default_config(), <<"package1">>, <<"1.0.0">>), %% > {ok, #{metadata := Metadata}} = hex_tarball:unpack(Tarball, "/tmp/package"). %% ''' %% @end get_tarball(Config, Name, Version) -> ReqHeaders = make_headers(Config), case get(Config, tarball_url(Config, Name, Version), ReqHeaders) of {ok, {200, RespHeaders, Tarball}} -> {ok, {200, RespHeaders, Tarball}}; Other -> Other end. %% @doc %% Gets tarball from the repository and writes it to a file. %% %% Examples: %% %% ``` %% > {ok, {200, _}} = hex_repo:get_tarball_to_file(hex_core:default_config(), <<"package1">>, <<"1.0.0">>, "/tmp/package.tar"), %% > {ok, #{metadata := Metadata}} = hex_tarball:unpack({file, "/tmp/package.tar"}, "/tmp/package"). %% ''' %% @end get_tarball_to_file(Config, Name, Version, Filename) -> ReqHeaders = make_headers(Config), case get_to_file(Config, tarball_url(Config, Name, Version), ReqHeaders, Filename) of {ok, {200, RespHeaders}} -> {ok, {200, RespHeaders}}; Other -> Other end. %% @doc %% Gets docs tarball from the repository. %% %% Examples: %% %% ``` %% > {ok, {200, _, Docs}} = hex_repo:get_docs(hex_core:default_config(), <<"package1">>, <<"1.0.0">>), %% > hex_tarball:unpack_docs(Docs, "/tmp/docs") %% ok %% ''' get_docs(Config, Name, Version) -> ReqHeaders = make_headers(Config), case get(Config, docs_url(Config, Name, Version), ReqHeaders) of {ok, {200, RespHeaders, Docs}} -> {ok, {200, RespHeaders, Docs}}; Other -> Other end. %% @doc %% Gets docs tarball from the repository and writes it to a file. %% %% Examples: %% %% ``` %% > {ok, {200, _}} = hex_repo:get_docs_to_file(hex_core:default_config(), <<"package1">>, <<"1.0.0">>, "/tmp/docs.tar.gz"), %% > ok = hex_tarball:unpack_docs({file, "/tmp/docs.tar.gz"}, "/tmp/docs"). %% ''' get_docs_to_file(Config, Name, Version, Filename) -> ReqHeaders = make_headers(Config), case get_to_file(Config, docs_url(Config, Name, Version), ReqHeaders, Filename) of {ok, {200, RespHeaders}} -> {ok, {200, RespHeaders}}; Other -> Other end. %% @doc %% Gets the public key from the repository. %% %% Examples: %% %% ``` %% > hex_repo:get_public_key(hex_core:default_config()) %% {ok, {200, _, PublicKey}} %% ''' get_public_key(Config) -> ReqHeaders = make_headers(Config), URI = build_url(Config, <<"public_key">>), case get(Config, URI, ReqHeaders) of {ok, {200, RespHeaders, PublicKey}} -> {ok, {200, RespHeaders, PublicKey}}; Other -> Other end. %% @doc %% Gets Hex installation versions CSV from repository. %% %% Examples: %% %% ``` %% > hex_repo:get_hex_installs(hex_core:default_config()). %% {ok, {200, ..., <<"1.0.0,abc123,1.13.0\n1.1.0,def456,1.14.0\n...">>}} %% ''' %% @end get_hex_installs(Config) -> ReqHeaders = make_headers(Config), URI = build_url(Config, <<"installs/hex-1.x.csv">>), case get(Config, URI, ReqHeaders) of {ok, {200, RespHeaders, CSV}} -> {ok, {200, RespHeaders, CSV}}; Other -> Other end. %%==================================================================== %% Internal functions %%==================================================================== %% @private get(Config, URI, Headers) -> hex_http:request(Config, get, URI, Headers, undefined). %% @private get_to_file(Config, URI, Headers, Filename) -> hex_http:request_to_file(Config, get, URI, Headers, undefined, Filename). %% @private get_protobuf(Config, Path, Decoder) -> PublicKey = maps:get(repo_public_key, Config), ReqHeaders = make_headers(Config), case get(Config, build_url(Config, Path), ReqHeaders) of {ok, {200, RespHeaders, Compressed}} -> Signed = zlib:gunzip(Compressed), case decode(Signed, PublicKey, Decoder, Config) of {ok, Decoded} -> {ok, {200, RespHeaders, Decoded}}; {error, _} = Error -> Error end; Other -> Other end. %% @private decode(Signed, PublicKey, Decoder, Config) -> Verify = maps:get(repo_verify, Config, true), case Verify of true -> case hex_registry:decode_and_verify_signed(Signed, PublicKey) of {ok, Payload} -> Decoder(Payload); Other -> Other end; false -> #{payload := Payload} = hex_registry:decode_signed(Signed), Decoder(Payload) end. %% @private verify_repo(Config) -> case maps:get(repo_verify_origin, Config, true) of true -> repo_name(Config); false -> no_verify end. %% @private repo_name(#{repo_organization := Name}) when is_binary(Name) -> Name; repo_name(#{repo_name := Name}) when is_binary(Name) -> Name. %% @private tarball_url(Config, Name, Version) -> Filename = tarball_filename(Name, Version), build_url(Config, <<"tarballs/", Filename/binary>>). %% @private docs_url(Config, Name, Version) -> Filename = docs_filename(Name, Version), build_url(Config, <<"docs/", Filename/binary>>). %% @private build_url(#{repo_url := URI, repo_organization := Org}, Path) when is_binary(Org) -> <>; build_url(#{repo_url := URI, repo_organization := undefined}, Path) -> <>; build_url(Config, Path) -> build_url(Config#{repo_organization => undefined}, Path). %% @private tarball_filename(Name, Version) -> <>. %% @private docs_filename(Name, Version) -> <>. %% @private make_headers(Config) -> maps:fold(fun set_header/3, #{}, Config). %% @private set_header(http_etag, ETag, Headers) when is_binary(ETag) -> maps:put(<<"if-none-match">>, ETag, Headers); set_header(repo_key, Token, Headers) when is_binary(Token) -> maps:put(<<"authorization">>, Token, Headers); set_header(api_otp, OTP, Headers) when is_binary(OTP) -> maps:put(<<"x-hex-otp">>, OTP, Headers); set_header(_, _, Headers) -> Headers.