diff --git a/zx b/zx index f8c5246..72dbfac 100755 --- a/zx +++ b/zx @@ -90,37 +90,17 @@ start(["set", "version", VersionString]) -> start(["list", "realms"]) -> list_realms(); start(["list", "packages", Realm]) -> - case valid_lower0_9(Realm) of - true -> - list_packages(Realm); - false -> - ok = log(error, "Bad realm name."), - halt(1) - end; + ok = valid_realm(Realm), + list_packages(Realm); start(["list", "versions", Package]) -> - case string:lexemes(Package, "-") of - [Realm, Name] -> - list_versions({Realm, Name}); - _ -> - ok = log(error, "Bad package name."), - halt(1) - end; -start(["list", "pending", Package]) -> - case string:lexemes(Package, "-") of - [Realm, Name] -> - list_pending({Realm, Name}); - _ -> - ok = log(error, "Bad package name."), - halt(1) - end; + Package = string_to_package(PackageName), + list_versions(Package); +start(["list", "pending", PackageName]) -> + Package = string_to_package(PackageName), + list_pending(Package); start(["list", "resigns", Realm]) -> - case valid_lower0_9(Realm) of - true -> - list_resigns(Realm); - false -> - ok = log(error, "Bad realm name."), - halt(1) - end; + ok = valid_realm(Realm), + list_resigns(Realm); start(["add", "realm", RealmFile]) -> add_realm(RealmFile); start(["add", "package", PackageName]) -> @@ -130,8 +110,7 @@ start(["add", "packager", Package, UserName]) -> start(["add", "maintainer", Package, UserName]) -> add_maintainer(Package, UserName); start(["review", PackageString]) -> - PackageID = package_id(PackageString), - review(PackageID); + review(PackageString); start(["approve", PackageString]) -> PackageID = package_id(PackageString), approve(PackageID); @@ -167,6 +146,8 @@ start(["submit", PackageFile]) -> submit(PackageFile); start(["dialyze"]) -> dialyze(); +start(["create", "user", Realm, Name]) -> + create_user(Realm, Name); start(["create", "keypair"]) -> create_keypair(); start(["create", "plt"]) -> @@ -634,6 +615,10 @@ list_packages(Realm) -> -spec list_versions(package()) -> no_return(). +%% @private +%% List the available versions of the package indicated. The user enters a string-form +%% package name (such as "otpr-zomp") and the return values will be full package strings +%% of the form "otpr-zomp-1.2.3", one per line printed to stdout. list_versions(Package = {Realm, Name}) -> ok = valid_package(Package), @@ -656,6 +641,10 @@ list_versions(Package = {Realm, Name}) -> -spec list_pending(package()) -> no_return(). +%% @private +%% List the versions of a package that are pending review. The package name is input by the +%% user as a string of the form "otpr-zomp" and the output is a list of full package IDs, +%% printed one per line to stdout (like "otpr-zomp-3.2.2"). list_pending(Package = {Realm, Name}) -> ok = valid_package(Package), @@ -677,7 +666,34 @@ list_pending(Package = {Realm, Name}) -> end. +-spec list_resigns(realm()) -> no_return(). +%% @private +%% List the package ids of all packages waiting in the resign queue for the given realm, +%% printed to stdout one per line. + +list_resigns(Realm) -> + Socket = connect_auth_or_die(Realm), + ok = send(Socket, {list_resigns, Realm}), + case recv_or_die(Socket) of + {ok, []} -> + Message = "No packages pending signature in ~tp.", + ok = log(info, Message, [Realm]), + halt(0); + {ok, PackageIDs} -> + Print = + fun(PackageID) -> + PackageString = package_string(PackageID), + io:format("~ts~n", [PackageString]) + end, + ok = lists:foreach(Print, PackageIDs), + halt(0) + end. + + -spec valid_package(package()) -> ok | no_return(). +%% @private +%% Test whether a package() type is a valid value or not. If not, halt execution with +%% a non-zero error code, if so then return `ok'. valid_package({Realm, Name}) -> case {valid_lower0_9(Realm), valid_lower0_9(Name)} of @@ -692,6 +708,40 @@ valid_package({Realm, Name}) -> {false, false} -> ok = log(error, "Invalid realm ~tp and package ~tp", [Realm, Name]), halt(1) + end; +valid_package(Bad) -> + ok = log(error, "Invalid package() value: ~160tp", [Bad]), + halt(1). + + +-spec string_to_package(string()) -> ok | no_return(). +%% @private +%% Convert a string to a package() type if possible. If not then halt the system. + +string_to_package(String) -> + case string:lexemes(Package, "-") of + [Realm, Name] -> + Package = {Realm, Name}, + ok = valid_package(Package), + Package; + _ -> + ok = log(error, "Bad package name."), + halt(1) + end. + + +-spec valid_realm(realm()) -> ok | no_return(). +%% @private +%% Test whether a realm name is a valid realm() type (that is, a lower0_9()) or not. If not, +%% halt execution with a non-zero error code, if so then return `ok'. + +valid_realm(Realm) -> + case valid_lower0_9(Realm) of + true -> + ok; + false -> + ok = log(error, "Bad realm name."), + halt(1) end. @@ -776,25 +826,6 @@ add_package(Realm, Name) -> halt(0). -list_resigns(Realm) -> - Socket = connect_auth_or_die(Realm), - ok = send(Socket, {list_resigns, Realm}), - case recv_or_die(Socket) of - {ok, []} -> - Message = "No packages pending signature in ~tp.", - ok = log(info, Message, [Realm]), - halt(0); - {ok, PackageIDs} -> - Print = - fun(PackageID) -> - PackageString = package_string(PackageID), - io:format("~ts~n", [PackageString]) - end, - ok = lists:foreach(Print, PackageIDs), - halt(0) - end. - - add_packager(Package, UserName) -> ok = log(info, "Would add ~ts to packagers for ~160tp now.", [UserName, Package]), halt(0). @@ -805,34 +836,34 @@ add_maintainer(Package, UserName) -> halt(0). -review(PackageID) -> - PackageString = package_string(PackageID), - ZrpPath = PackageString ++ ".zrp", - ok = log(info, "Saving to ~ts, unpacking to ./~ts/", [ZrpPath, PackageString]), - ok = - case {filelib:is_file(ZrpPath), filelib:is_file(PackageString)} of - {false, false} -> - ok; - {true, false} -> - ok = log(error, "~ts already exists. Aborting.", [ZrpPath]), - halt(1); - {false, true} -> - ok = log(error, "~ts already exists. Aborting.", [PackageString]), - halt(1); - {true true} -> - Message = "~ts and ~ts already exist. Aborting.", - ok = log(error, Message, [ZrpPath, PackageString]), - halt(1); - end, +review(PackageString) -> + PackageID = package_id(PackageString), Socket = connect_auth_or_die(), ok = send(Socket, {review, PackageID}), - ok = receive_or_die(Socket), - {ok, ZrpBin} = recieve_or_die(Socket), - ok = file:write_file(ZrpPath, ZrpBin), + sending = recv_or_die(Socket), + {ok, ZrpBin} = recv_or_die(Socket), ok = disconnect(Socket), - {"zomp.meta", MetaBin} = erl_tar:extract(ZrpBin, [memory, {files, "zomp.meta"}]), + {ok, Files} = erl_tar:extract({binary, ZrpBin}, [memory]), + {"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files), Meta = binary_to_term(MetaBin, [safe]), - + PackageID = maps:get(package_id, Meta), + {KeyID, Signature} = maps:get(sig, Meta), + {ok, PubKey} = loadkey(public, KeyID), + TgzFile = PackageString ++ ".tgz", + {TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files), + ok = verify(TgzData, Signature, PubKey), + ok = + case file:make_dir(PackageString) of + ok -> + log(info, "Will unpack to directory ./~ts", [PackageString]); + {error, Error} -> + Message = "Creating dir ./~ts failed with ~ts. Aborting." + ok = log(error, Message, [PackageString, Error]), + halt(1) + end, + ok = erl_tar:extract({binary, TgzData}, [compressed, {cwd, PackageString}]), + ok = log(info, "Unpacked and awaiting inspection."), + halt(0). approve(PackageID = {Realm, _, _}) -> @@ -851,8 +882,36 @@ reject(PackageID = {Realm, _, _}) -> halt(0). -resign(PackageID) -> - M = "Pull the indicated package", +resign(PackageString) -> + PackageID = {Realm, _, _} = package_id(PackageString), + Socket = connect_auth_or_die(Realm), + ok = send(Socket, {resign, PackageID}), + sending = recv_or_die(Socket), + {ok, ZrpBin} = recv_or_die(Socket), + {ok, Files} = erl_tar:extract({binary, ZrpBin}, [memory]), + {"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files), + Meta = binary_to_term(MetaBin, [safe]), + PackageID = maps:get(package_id, Meta), + {KeyID, Signature} = maps:get(sig, Meta), + {ok, PubKey} = loadkey(public, KeyID), + TgzFile = PackageString ++ ".tgz", + {TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files), + ok = verify(TgzData, Signature, PubKey), + RealmConf = read_realm_conf(Realm), + {package_keys, PackageKeys} = lists:keyfind(package_keys, 1, RealmConf), + PackageKeyName = select(PackageKeys), + PackageKeyID = {Realm, PackageKeyName} + {ok, PackageKey} = loadkey(private, PackageKeyID), + ReSignature = public_key:sign(TgzData, sha512, PackageKey), + FinalMeta = maps:put(sig, {PackageKeyID, ReSignature}), + NewFiles = lists:keystore("zomp.meta", 1, term_to_binary(FinalMeta)), + ResignedZrp = PackageString ++ ".zrp", + ok = erl_tar:create(ResignedZrp, NewFiles), + {ok, ResignedBin} = file:read_file(ResignedFile), + ok = send(Socket, {resigned, ResignedBin}), + ok = recv_or_die(Socket), + ok = file:delete(ResignedZrp), + ok = disconnect(Socket), ok = log(info, M, [PackageID]), halt(0). @@ -2392,7 +2451,7 @@ install(PackageID) -> {TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files), {"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files), Meta = binary_to_term(MetaBin), - {sig, {KeyID, Signature}} = lists:keyfind(sig, 1, Meta), + {KeyID, Signature} = maps:get(sig, 1, Meta), {ok, PubKey} = loadkey(public, KeyID), ok = ensure_package_dirs(PackageID), PackageDir = filename:join("lib", PackageString), @@ -3052,15 +3111,28 @@ usage() -> T = "~n" "zx~n" "~n" - "Usage:~n" + "Usage: zx [command] [object] [args]~n" + "~n" + "Examples:~n" " zx help~n" - " zx run~n" " zx run PackageID [Args]~n" " zx init Type PackageID~n" " zx install PackageID~n" " zx set dep PackageID~n" " zx set version Version~n" + " zx list realms~n" + " zx list packages Realm~n" + " zx list versions PackageName~n" + " zx list pending PackageName~n" + " zx list resigns Realm~n" " zx add realm RealmFile~n" + " zx add package PackageName~n" + " zx add packager PackageName~n" + " zx add maintainer PackageName~n" + " zx review PackageID~n" + " zx approve PackageID~n" + " zx reject PackageID~n" + " zx resign PackageID~n" " zx drop dep PackageID~n" " zx drop key Realm KeyName~n" " zx drop realm Realm~n" @@ -3068,6 +3140,7 @@ usage() -> " zx runlocal [Args]~n" " zx package [Path]~n" " zx submit Path~n" + " zx create user Realm Username~n" " zx create keypair~n" " zx create plt~n" " zx create realm~n"