From be1573f0643c6a4501c496bb587e927f969ac970 Mon Sep 17 00:00:00 2001 From: Castulo Martinez Date: Mon, 2 Dec 2019 15:17:54 -0800 Subject: [PATCH] Implementing 3rd-party bundle-list command This commit introduces a new command to list bundles from all 3rd-party repositories. At this point users cannot choose a specific repo to show its bundles but rather all repos are shown. Signed-off-by: Castulo Martinez --- Makefile.am | 1 + config | 16 ++ src/3rd_party.c | 1 + src/3rd_party_bundle_list.c | 199 ++++++++++++++++++ src/bundle_list.c | 85 +++----- src/swupd.h | 6 + src/swupd_internal.h | 1 + swupd.bash | 2 +- swupd.zsh | 1 + .../3rd-party-bundle-list-basic.bats | 105 +++++++++ .../3rd-party/3rd-party-bundle-list-deps.bats | 83 ++++++++ 11 files changed, 447 insertions(+), 53 deletions(-) create mode 100644 src/3rd_party_bundle_list.c create mode 100755 test/functional/3rd-party/3rd-party-bundle-list-basic.bats create mode 100755 test/functional/3rd-party/3rd-party-bundle-list-deps.bats diff --git a/Makefile.am b/Makefile.am index 3129e426..f181d7c2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,7 @@ AM_CPPFLAGS = $(AM_CFLAGS) $(libarchive_CFLAGS) swupd_SOURCES = \ src/3rd_party_add.c \ src/3rd_party_bundle_add.c \ + src/3rd_party_bundle_list.c \ src/3rd_party.c \ src/3rd_party_list.c \ src/3rd_party_remove.c \ diff --git a/config b/config index 2cac3660..e76c4c18 100644 --- a/config +++ b/config @@ -356,3 +356,19 @@ #skip_optional= +[3rd-party-bundle-list] + +# +# Options for the "swupd 3rd-party bundle-list" command +# + +# List all available bundles for the current version of Clear +# Linux (boolean value) +#all= + +# List bundles included by BUNDLE (string value) +#deps=[BUNDLE] + +# List dependency tree of all bundles which have BUNDLE as a +# dependency (string value) +#has_dep=[BUNDLE] diff --git a/src/3rd_party.c b/src/3rd_party.c index f41ddb09..ef4bf245 100644 --- a/src/3rd_party.c +++ b/src/3rd_party.c @@ -29,6 +29,7 @@ static struct subcmd third_party_commands[] = { { "remove", "Remove third party repository", third_party_remove_main }, { "list", "List third party repository", third_party_list_main }, { "bundle-add", "Install a bundle from a third party repository", third_party_bundle_add_main }, + { "bundle-list", "List bundles from a third party repository", third_party_bundle_list_main }, { 0 } }; diff --git a/src/3rd_party_bundle_list.c b/src/3rd_party_bundle_list.c new file mode 100644 index 00000000..c82142cd --- /dev/null +++ b/src/3rd_party_bundle_list.c @@ -0,0 +1,199 @@ +/* + * Software Updater - client side + * + * Copyright © 2019 Intel Corporation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 or later of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#define _GNU_SOURCE + +#include "3rd_party_repos.h" +#include "swupd.h" + +#ifdef THIRDPARTY + +#define FLAG_DEPS 2000 + +static bool cmdline_local = true; +static bool cmdline_option_all = false; +static char *cmdline_option_has_dep = NULL; +static char *cmdline_option_deps = NULL; + +static void free_has_dep(void) +{ + free_string(&cmdline_option_has_dep); +} + +static void free_deps(void) +{ + free_string(&cmdline_option_deps); +} + +static void print_help(void) +{ + print("Usage:\n"); + print(" swupd 3rd-party bundle-list [OPTIONS...]\n\n"); + + global_print_help(); + + print("Options:\n"); + print(" -a, --all List all available bundles for the current version of Clear Linux\n"); + print(" -D, --has-dep=[BUNDLE] List all bundles which have BUNDLE as a dependency\n"); + print(" --deps=[BUNDLE] List bundles included by BUNDLE\n"); + print("\n"); +} + +static const struct option prog_opts[] = { + { "all", no_argument, 0, 'a' }, + { "deps", required_argument, 0, FLAG_DEPS }, + { "has-dep", required_argument, 0, 'D' }, +}; + +static bool parse_opt(int opt, char *optarg) +{ + switch (opt) { + case 'a': + cmdline_option_all = optarg_to_bool(optarg); + cmdline_local = !cmdline_option_all; + return true; + case 'D': + string_or_die(&cmdline_option_has_dep, "%s", optarg); + atexit(free_has_dep); + cmdline_local = false; + return true; + case FLAG_DEPS: + string_or_die(&cmdline_option_deps, "%s", optarg); + atexit(free_deps); + cmdline_local = false; + return true; + default: + return false; + } + return false; +} + +static const struct global_options opts = { + prog_opts, + sizeof(prog_opts) / sizeof(struct option), + parse_opt, + print_help, +}; + +static bool parse_options(int argc, char **argv) +{ + int ind = global_parse_options(argc, argv, &opts); + + if (ind < 0) { + return false; + } + + if (argc > ind) { + error("unexpected arguments\n\n"); + return false; + } + + return true; +} + +static void print_repo_header(const char *repo_name) +{ + char *header = NULL; + int header_length; + + string_or_die(&header, " 3rd-Party Repo: %s", repo_name); + header_length = strlen(header); + print_pattern("_", header_length + 1); + info("%s\n", header); + print_pattern("_", header_length + 1); + info("\n"); + free_string(&header); +} + +static enum swupd_code list_bundles(struct repo *repo) +{ + enum swupd_code ret; + + if (cmdline_local) { + ret = list_local_bundles(repo->version); + } else if (cmdline_option_deps != NULL) { + ret = show_included_bundles(cmdline_option_deps, repo->version); + } else if (cmdline_option_has_dep != NULL) { + ret = show_bundle_reqd_by(cmdline_option_has_dep, cmdline_option_all, repo->version); + } else { + ret = list_installable_bundles(repo->version); + } + info("\n"); + + return ret; +} + +enum swupd_code third_party_bundle_list_main(int argc, char **argv) +{ + struct list *repos = NULL; + struct list *iter = NULL; + char *state_dir; + char *path_prefix; + enum swupd_code ret; + const int steps_in_bundlelist = 1; + + if (!parse_options(argc, argv)) { + print_help(); + return SWUPD_INVALID_OPTION; + } + progress_init_steps("3rd-party-bundle-list", steps_in_bundlelist); + + if (cmdline_local && !is_root()) { + ret = swupd_init(SWUPD_NO_ROOT); + } else { + ret = swupd_init(SWUPD_ALL); + } + + if (ret != SWUPD_OK) { + error("Failed swupd initialization, exiting now\n"); + goto finish; + } + + /* load the existing 3rd-party repos from the repo.ini config file */ + repos = third_party_get_repos(); + + /* backup the original state_dir and path_prefix values */ + state_dir = strdup_or_die(globals.state_dir); + path_prefix = strdup_or_die(globals.path_prefix); + + for (iter = repos; iter; iter = iter->next) { + struct repo *repo = iter->data; + + /* set the appropriate content_dir and state_dir for the selected 3rd-party repo */ + if (third_party_set_repo(state_dir, path_prefix, repo)) { + ret = SWUPD_COULDNT_CREATE_DIR; + goto clean_and_exit; + } + + print_repo_header(repo->name); + ret = list_bundles(repo); + } + +clean_and_exit: + free_string(&state_dir); + free_string(&path_prefix); + list_free_list_and_data(repos, repo_free_data); + swupd_deinit(); + +finish: + progress_finish_steps(ret); + return ret; +} + +#endif diff --git a/src/bundle_list.c b/src/bundle_list.c index 200e1f6e..3e244bae 100644 --- a/src/bundle_list.c +++ b/src/bundle_list.c @@ -118,7 +118,7 @@ static bool parse_options(int argc, char **argv) * /usr/share/clear/bundles/), get the list of local bundles and print * them sorted. */ -static enum swupd_code list_local_bundles() +enum swupd_code list_local_bundles(int version) { char *name; char *path = NULL; @@ -126,22 +126,17 @@ static enum swupd_code list_local_bundles() struct list *item = NULL; struct manifest *MoM = NULL; struct file *bundle_manifest = NULL; - int current_version; bool mix_exists; int count = 0; - current_version = get_current_version(globals.path_prefix); - if (current_version < 0) { - goto skip_mom; + if (version > 0) { + mix_exists = (check_mix_exists() & system_on_mix()); + MoM = load_mom(version, mix_exists, NULL); + if (!MoM) { + warn("Could not determine which installed bundles are experimental\n\n"); + } } - mix_exists = (check_mix_exists() & system_on_mix()); - MoM = load_mom(current_version, mix_exists, NULL); - if (!MoM) { - warn("Could not determine which installed bundles are experimental\n"); - } - -skip_mom: string_or_die(&path, "%s/%s", globals.path_prefix, BUNDLES_DIR); errno = 0; @@ -181,25 +176,17 @@ skip_mom: } /* Return recursive list of included bundles */ -static enum swupd_code show_included_bundles(char *bundle_name) +enum swupd_code show_included_bundles(char *bundle_name, int version) { int ret = 0; - int current_version = CURRENT_OS_VERSION; struct list *subs = NULL; struct list *deps = NULL; struct manifest *mom = NULL; int count = 0; - current_version = get_current_version(globals.path_prefix); - if (current_version < 0) { - error("Unable to determine current OS version\n"); - ret = SWUPD_CURRENT_VERSION_UNKNOWN; - goto out; - } - - mom = load_mom(current_version, false, NULL); + mom = load_mom(version, false, NULL); if (!mom) { - error("Cannot load official manifest MoM for version %i\n", current_version); + error("Cannot load official manifest MoM for version %i\n", version); ret = SWUPD_COULDNT_LOAD_MOM; goto out; } @@ -283,24 +270,17 @@ out: * Parse the full manifest for the current version of the OS and print * all available bundles. */ -static enum swupd_code list_installable_bundles() +enum swupd_code list_installable_bundles(int version) { char *name; struct list *list; struct file *file; struct manifest *MoM = NULL; - int current_version; - int count; + int count = 0; bool mix_exists; - current_version = get_current_version(globals.path_prefix); - if (current_version < 0) { - error("Unable to determine current OS version\n"); - return SWUPD_CURRENT_VERSION_UNKNOWN; - } - mix_exists = (check_mix_exists() & system_on_mix()); - MoM = load_mom(current_version, mix_exists, NULL); + MoM = load_mom(version, mix_exists, NULL); if (!MoM) { return SWUPD_COULDNT_LOAD_MOM; } @@ -322,10 +302,9 @@ static enum swupd_code list_installable_bundles() return 0; } -static enum swupd_code show_bundle_reqd_by(const char *bundle_name, bool server) +enum swupd_code show_bundle_reqd_by(const char *bundle_name, bool server, int version) { int ret = 0; - int version = CURRENT_OS_VERSION; struct manifest *current_manifest = NULL; struct list *subs = NULL; struct list *reqd_by = NULL; @@ -338,13 +317,6 @@ static enum swupd_code show_bundle_reqd_by(const char *bundle_name, bool server) goto out; } - version = get_current_version(globals.path_prefix); - if (version < 0) { - error("Unable to determine current OS version\n"); - ret = SWUPD_CURRENT_VERSION_UNKNOWN; - goto out; - } - current_manifest = load_mom(version, false, NULL); if (!current_manifest) { error("Unable to download/verify %d Manifest.MoM\n", version); @@ -411,7 +383,8 @@ out: enum swupd_code bundle_list_main(int argc, char **argv) { - int ret; + enum swupd_code ret; + int current_version; const int steps_in_bundlelist = 1; /* there is no need to report in progress for bundle-list at this time */ @@ -427,27 +400,35 @@ enum swupd_code bundle_list_main(int argc, char **argv) } else { ret = swupd_init(SWUPD_ALL); } - /* if swupd fails to initialize, the only list command we can still attempt is + + if (ret != SWUPD_OK) { + error("Failed swupd initialization, exiting now\n"); + progress_finish_steps(ret); + return ret; + } + + /* if we cannot get the current_version, the only list command we can still attempt is * listing locally installed bundles (with the limitation of not showing what * bundles are experimental) */ - if (ret != 0) { - error("Failed updater initialization. Exiting now\n"); + current_version = get_current_version(globals.path_prefix); + if (current_version < 0 && !cmdline_local) { + error("Unable to determine current OS version\n"); + ret = SWUPD_CURRENT_VERSION_UNKNOWN; goto finish; } if (cmdline_local) { - ret = list_local_bundles(); + ret = list_local_bundles(current_version); } else if (cmdline_option_deps != NULL) { - ret = show_included_bundles(cmdline_option_deps); + ret = show_included_bundles(cmdline_option_deps, current_version); } else if (cmdline_option_has_dep != NULL) { - ret = show_bundle_reqd_by(cmdline_option_has_dep, cmdline_option_all); + ret = show_bundle_reqd_by(cmdline_option_has_dep, cmdline_option_all, current_version); } else { - ret = list_installable_bundles(); + ret = list_installable_bundles(current_version); } - swupd_deinit(); - finish: + swupd_deinit(); progress_finish_steps(ret); return ret; } diff --git a/src/swupd.h b/src/swupd.h index d655790e..c1fac50c 100644 --- a/src/swupd.h +++ b/src/swupd.h @@ -344,6 +344,12 @@ extern void verify_set_extra_files_only(bool opt); /* repair.c */ extern regex_t *compile_whitelist(const char *whitelist_pattern); +/* bundle_list.c*/ +extern enum swupd_code list_local_bundles(int version); +extern enum swupd_code list_installable_bundles(int version); +extern enum swupd_code show_included_bundles(char *bundle_name, int version); +extern enum swupd_code show_bundle_reqd_by(const char *bundle_name, bool server, int version); + /* telemetry.c */ typedef enum telem_prio_t { TELEMETRY_DEBG = 1, diff --git a/src/swupd_internal.h b/src/swupd_internal.h index ae7d2183..438264ee 100644 --- a/src/swupd_internal.h +++ b/src/swupd_internal.h @@ -27,6 +27,7 @@ enum swupd_code bundle_info_main(int argc, char **argv); enum swupd_code verify_main(int argc, char **argv); enum swupd_code third_party_main(int argc, char **argv); enum swupd_code third_party_bundle_add_main(int argc, char **argv); +enum swupd_code third_party_bundle_list_main(int argc, char **argv); /** * @brief Creates a new third-party repo under THIRDPARTY_REPO_PREFIX diff --git a/swupd.bash b/swupd.bash index 0da0fc8d..81e4007c 100644 --- a/swupd.bash +++ b/swupd.bash @@ -84,7 +84,7 @@ _swupd() opts="$global --version --manifest --fix --picky --picky-tree --picky-whitelist --install --quick --force --install " break;; ("3rd-party") - opts="$global add remove list bundle-add" + opts="$global add remove list bundle-add bundle-list " break;; ("add") opts="$global --repo" diff --git a/swupd.zsh b/swupd.zsh index a4b293c1..6657c620 100644 --- a/swupd.zsh +++ b/swupd.zsh @@ -191,6 +191,7 @@ if [[ -n "$state" ]]; then '(help)remove[Remove third party repo]' '(help)add[Add third party repo]' '(help)bundle-add[Install a bundle from a third party repository]' + '(help)bundle-list[List bundles from a third party repository]' _arguments $thirdparty && ret=0 ;; add) diff --git a/test/functional/3rd-party/3rd-party-bundle-list-basic.bats b/test/functional/3rd-party/3rd-party-bundle-list-basic.bats new file mode 100755 index 00000000..3466c2c9 --- /dev/null +++ b/test/functional/3rd-party/3rd-party-bundle-list-basic.bats @@ -0,0 +1,105 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +global_setup() { + + create_test_environment "$TEST_NAME" + create_bundle -n test-bundle-upstream -f /file_upstream "$TEST_NAME" + + # create a couple of 3rd-party repos with bundles + create_third_party_repo "$TEST_NAME" 10 staging repo1 + create_bundle -n test-bundle1 -f /foo/file_1A -u repo1 "$TEST_NAME" + create_bundle -L -n test-bundle2 -f /bar/file_2 -u repo1 "$TEST_NAME" + + create_third_party_repo "$TEST_NAME" 10 staging repo2 + create_bundle -n test-bundle1 -f /baz/file_1B -u repo2 "$TEST_NAME" + create_bundle -L -n test-bundle3 -f /baz/file_3 -u repo2 "$TEST_NAME" + +} + +test_setup() { + + return + +} + +test_teardown() { + + return +} + +global_teardown() { + + destroy_test_environment "$TEST_NAME" + +} + +@test "TPR018: List installed 3rd-party bundles" { + + # When a user list 3rd-party bundles without specifiying a repo, all + # installed bundles from 3rd-party repos should be shown + + run sudo sh -c "$SWUPD 3rd-party bundle-list $SWUPD_OPTS" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________ + 3rd-Party Repo: repo1 + _______________________ + + Installed bundles: + - test-bundle2 + + Total: 1 + + _______________________ + 3rd-Party Repo: repo2 + _______________________ + + Installed bundles: + - test-bundle3 + + Total: 1 + EOM + ) + assert_is_output --identical "$expected_output" + +} + +@test "TPR019: List all available 3rd-party bundles" { + + # users should be able to list all 3rd-party bundles without specifiying a repo, + # all available bundles should be displayed + + run sudo sh -c "$SWUPD 3rd-party bundle-list --all $SWUPD_OPTS" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________ + 3rd-Party Repo: repo1 + _______________________ + + All available bundles: + - test-bundle1 + - test-bundle2 + + Total: 2 + + _______________________ + 3rd-Party Repo: repo2 + _______________________ + + All available bundles: + - test-bundle1 + - test-bundle3 + + Total: 2 + EOM + ) + assert_is_output --identical "$expected_output" + +} diff --git a/test/functional/3rd-party/3rd-party-bundle-list-deps.bats b/test/functional/3rd-party/3rd-party-bundle-list-deps.bats new file mode 100755 index 00000000..8fc7deb0 --- /dev/null +++ b/test/functional/3rd-party/3rd-party-bundle-list-deps.bats @@ -0,0 +1,83 @@ +#!/usr/bin/env bats + +# Author: Castulo Martinez +# Email: castulo.martinez@intel.com + +load "../testlib" + +global_setup() { + + create_test_environment "$TEST_NAME" + + # create a 3rd-party repo with bundles that have dependencies + create_third_party_repo "$TEST_NAME" 10 staging repo1 + create_bundle -n test-bundle1 -f /foo/file_1 -u repo1 "$TEST_NAME" + create_bundle -n test-bundle2 -f /bar/file_2 -u repo1 "$TEST_NAME" + create_bundle -n test-bundle3 -f /baz/file_3 -u repo1 "$TEST_NAME" + create_bundle -n test-bundle4 -f /file_4 -u repo1 "$TEST_NAME" + create_bundle -n test-bundle5 -f /bat/file_5 -u repo1 "$TEST_NAME" + add_dependency_to_manifest "$TPWEBDIR"/10/Manifest.test-bundle2 test-bundle3 + add_dependency_to_manifest "$TPWEBDIR"/10/Manifest.test-bundle3 test-bundle4 + add_dependency_to_manifest "$TPWEBDIR"/10/Manifest.test-bundle2 test-bundle5 + +} + +test_setup() { + + return + +} + +test_teardown() { + + return +} + +global_teardown() { + + destroy_test_environment "$TEST_NAME" + +} + +@test "TPR020: List dependencies of a 3rd-party bundle" { + + run sudo sh -c "$SWUPD 3rd-party bundle-list $SWUPD_OPTS --deps test-bundle2" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________ + 3rd-Party Repo: repo1 + _______________________ + + Bundles included by test-bundle2: + - test-bundle3 + - test-bundle4 + - test-bundle5 + + Total: 3 + EOM + ) + assert_is_output --identical "$expected_output" + +} + +@test "TPR021: List all 3rd-party bundles that have a given bundle as dependency" { + + run sudo sh -c "$SWUPD 3rd-party bundle-list $SWUPD_OPTS --has-dep test-bundle4 --all" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + _______________________ + 3rd-Party Repo: repo1 + _______________________ + + All bundles that have test-bundle4 as a dependency: + - test-bundle2 + - test-bundle3 + + Total: 2 + EOM + ) + assert_is_output --identical "$expected_output" + +}